| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 <html> |  | 
| 2 |  | 
| 3 <!-- |  | 
| 4   Copyright (c) 2012 The Chromium Authors. All rights reserved. |  | 
| 5   Use of this source code is governed by a BSD-style license that can be |  | 
| 6   found in the LICENSE file. |  | 
| 7 --> |  | 
| 8 |  | 
| 9 <!-- |  | 
| 10   For testing this file locally, start a localhost server at the root of the |  | 
| 11   perf directory (e.g. with "python -m SimpleHTTPServer") and pass in a |  | 
| 12   baseUrl as a query parameter, e.g. |  | 
| 13   http://localhost:8000/dashboard/ui/generic_plotter.html?history=150&rev=-1&gra
     ph=dom&baseUrl=http://localhost:8000/data/linux-release-webkit-latest/dromaeo_do
     mcore/. |  | 
| 14 |  | 
| 15   You need a localhost server to get around Chromium's restrictions on loading |  | 
| 16   file urls in XMLHttpRequests. |  | 
| 17 |  | 
| 18   A brief note on terminology as used here: a "graph" is a plotted screenful |  | 
| 19   of data, showing the results of one type of test: for example, the |  | 
| 20   page-load-time graph.  A "trace" is a single line on a graph, showing one |  | 
| 21   one for the test: for example, the reference build trace on the |  | 
| 22   page-load-time graph. |  | 
| 23 |  | 
| 24   This page plots arbitrary numerical data loaded from files in a specific |  | 
| 25   format.  It uses two or more data files, all JSON-encoded: |  | 
| 26 |  | 
| 27     graphs.dat: a list of objects, each with these properties: name (the name |  | 
| 28         of a graph) and units (the units for the data to be read by humans). |  | 
| 29         Schematically: |  | 
| 30           [{"name": <graph_name>, "units": <units>}, ...] |  | 
| 31 |  | 
| 32     <graphname>-summary.dat: for each of the graphs listed in graphs.dat, the |  | 
| 33         corresponding summary file holds rows of data. Each row of data is an |  | 
| 34         object with several properties: |  | 
| 35           "rev": the revision number for this row of data |  | 
| 36           "traces": an object with several properties of its own. The name of |  | 
| 37               the property corresponds to a trace name, used only as an |  | 
| 38               internal identifier, and the property's value is an array of |  | 
| 39               its measurement and that measurement's standard deviation (or |  | 
| 40               other measurement error). |  | 
| 41         Schematically: |  | 
| 42           {"rev": <rev>, |  | 
| 43            "traces": {<trace_name1>: [<value1>, <stddev1>], |  | 
| 44                       <trace_name2>: [<value2>, <stddev2>], ...} |  | 
| 45           } |  | 
| 46 --> |  | 
| 47 <head> |  | 
| 48 <style> |  | 
| 49 body { |  | 
| 50   font-family: sans-serif; |  | 
| 51 } |  | 
| 52 div#output { |  | 
| 53   cursor: pointer; |  | 
| 54 } |  | 
| 55 div#switcher * { |  | 
| 56   border: 1px solid black; |  | 
| 57   border-radius: 4px 4px 0 0; |  | 
| 58   padding-left: 0.5em; |  | 
| 59   padding-right: 0.5em; |  | 
| 60 } |  | 
| 61 div#switcher a { |  | 
| 62   background: #ddd; |  | 
| 63   cursor: pointer; |  | 
| 64 } |  | 
| 65 canvas.plot { |  | 
| 66   border: 1px solid black; |  | 
| 67 } |  | 
| 68 div.plot-coordinates { |  | 
| 69   font-family: monospace; |  | 
| 70 } |  | 
| 71 iframe { |  | 
| 72   display: none; |  | 
| 73   width: 100%; |  | 
| 74   height: 100%; |  | 
| 75   border: none; |  | 
| 76 } |  | 
| 77 .selector { |  | 
| 78   border: solid 1px black; |  | 
| 79   cursor: pointer; |  | 
| 80   padding-left: 0.3em; |  | 
| 81   background-color: white; |  | 
| 82   width: 80px; |  | 
| 83   display: inline-block; |  | 
| 84 } |  | 
| 85 .selector:hover { |  | 
| 86   background-color: rgb(200,200,250); |  | 
| 87 } |  | 
| 88 div#selectors { |  | 
| 89   display: none; |  | 
| 90   right: 6px; |  | 
| 91   position: absolute; |  | 
| 92 } |  | 
| 93 #explain { |  | 
| 94   font-size: 0.75em; |  | 
| 95   font-style: italic; |  | 
| 96   color: rgb(100,100,100); |  | 
| 97 } |  | 
| 98 #views { |  | 
| 99   border: 1px solid black; |  | 
| 100   width: 100%; |  | 
| 101   display: none; |  | 
| 102 } |  | 
| 103 #webkit-tab { |  | 
| 104   border-left: none; |  | 
| 105   display: none; |  | 
| 106 } |  | 
| 107 </style> |  | 
| 108 |  | 
| 109 <script src="js/common.js"></script> |  | 
| 110 <script src="js/plotter.js"></script> |  | 
| 111 <script src="js/coordinates.js"></script> |  | 
| 112 <script src="config.js"></script> |  | 
| 113 <script> |  | 
| 114 document.title = Config.title + ' - ' + Config.buildslave; |  | 
| 115 |  | 
| 116 var did_position_details = false; |  | 
| 117 var units = 'thing-a-ma-bobs'; |  | 
| 118 var graph_list = []; |  | 
| 119 var first_trace = ''; |  | 
| 120 |  | 
| 121 var refresh_params = false; |  | 
| 122 var params = ParseParams(); |  | 
| 123 if (!('history' in params)) { |  | 
| 124   params.history = 150; |  | 
| 125   refresh_params = true; |  | 
| 126 } |  | 
| 127 if (!('rev' in params)) { |  | 
| 128   params.rev = -1;  // -1 specifies the latest revision. |  | 
| 129   refresh_params = true; |  | 
| 130 } |  | 
| 131 if (refresh_params) |  | 
| 132   window.location.href = MakeURL(params); |  | 
| 133 |  | 
| 134 if (!Config.detailTabs) |  | 
| 135   Config.detailTabs = {'view-change': 'CL'}; |  | 
| 136 |  | 
| 137 /** |  | 
| 138  * Encapsulates a *-summary.dat file. |  | 
| 139  * @constructor |  | 
| 140  */ |  | 
| 141 function Rows(data) { |  | 
| 142   this.rows = data.split('\n'); |  | 
| 143   this.length = this.rows.length; |  | 
| 144 } |  | 
| 145 |  | 
| 146 /** |  | 
| 147  * Returns the row at the given index. |  | 
| 148  */ |  | 
| 149 Rows.prototype.get = function(i) { |  | 
| 150   if (!i in this.rows || this.rows[i] === undefined) return null; |  | 
| 151   if (!this.rows[i].length) return null; |  | 
| 152   var row = JSON.parse(this.rows[i]); |  | 
| 153   row.revision = isNaN(row['rev']) ? row['rev'] : parseInt(row['rev']); |  | 
| 154   row.webkitRevision = isNaN(row['webkit_rev']) ? |  | 
| 155       row['webkit_rev'] : parseInt(row['webkit_rev']); |  | 
| 156   return row; |  | 
| 157 }; |  | 
| 158 |  | 
| 159 function report_error(error) { |  | 
| 160   document.getElementById("output").innerHTML = "<p>" + error + "</p>"; |  | 
| 161 } |  | 
| 162 |  | 
| 163 function received_graph_list(data, error) { |  | 
| 164   if (error) { |  | 
| 165     report_error(error); |  | 
| 166     return; |  | 
| 167   } |  | 
| 168   graph_list = JSON.parse(data); |  | 
| 169 |  | 
| 170   if (!('graph' in params) || params.graph == '') { |  | 
| 171     if (graph_list.length > 0) |  | 
| 172       params.graph = graph_list[0].name |  | 
| 173   } |  | 
| 174 |  | 
| 175   // Add a selection tab for each graph, and find the units for the selected |  | 
| 176   // one while we're at it. |  | 
| 177   tabs = []; |  | 
| 178   for (var index = 0; index < graph_list.length; ++index) { |  | 
| 179     var graph = graph_list[index]; |  | 
| 180     tabs.push(graph.name); |  | 
| 181     if (graph.name == params.graph) |  | 
| 182       units = graph.units; |  | 
| 183   } |  | 
| 184   initPlotSwitcher(tabs); |  | 
| 185 |  | 
| 186   // Fetch the data for the selected graph. |  | 
| 187   fetch_summary(); |  | 
| 188 |  | 
| 189 } |  | 
| 190 |  | 
| 191 function go_to(graph) { |  | 
| 192   params.graph = graph; |  | 
| 193   if (params.graph == '') |  | 
| 194     delete params.graph; |  | 
| 195   window.location.href = MakeURL(params); |  | 
| 196 } |  | 
| 197 |  | 
| 198 function get_url() { |  | 
| 199   var new_url = encodeURI(window.location.href); |  | 
| 200   new_url = new_url.replace(/'/g, '%27'); |  | 
| 201   new_url = new_url.replace(/\&lookout=1/, ''); |  | 
| 202   if (new_url.indexOf('http://') == 0 || new_url.indexOf('https://') == 0) |  | 
| 203     return new_url; |  | 
| 204   return ''; |  | 
| 205 } |  | 
| 206 |  | 
| 207 function on_clicked_plot(prev_entry, current_entry) { |  | 
| 208   if ('lookout' in params) { |  | 
| 209     window.open(get_url()); |  | 
| 210     return; |  | 
| 211   } |  | 
| 212 |  | 
| 213   // Define sources for detail tabs |  | 
| 214   if ('view-change' in Config.detailTabs) { |  | 
| 215     // If the changeLinkPrefix has {PREV_CL}/{CL} markers, replace them. |  | 
| 216     // Otherwise, append to the URL. |  | 
| 217     var url = Config.changeLinkPrefix; |  | 
| 218     if (url.indexOf('{PREV_CL}') >= 0 || url.indexOf('{CL}') >= 0) { |  | 
| 219       url = url.replace('{PREV_CL}', prev_entry.chromium); |  | 
| 220       url = url.replace('{CL}', current_entry.chromium); |  | 
| 221     } else { |  | 
| 222       url += prev_entry.chromium + ':' + current_entry.chromium; |  | 
| 223     } |  | 
| 224     document.getElementById('view-change').setAttribute('src', url); |  | 
| 225   } |  | 
| 226   if ('view-pages' in Config.detailTabs) { |  | 
| 227     document.getElementById('view-pages').src = 'details.html?cl=' + |  | 
| 228       current_entry.chromium + '&graph=' + params.graph + '&trace=' + |  | 
| 229       first_trace; |  | 
| 230   } |  | 
| 231   if ('view-coverage' in Config.detailTabs) { |  | 
| 232     document.getElementById('view-coverage').src = |  | 
| 233         Config.coverageLinkPrefix + current_entry.chromium; |  | 
| 234   } |  | 
| 235   if (!isNaN(prev_entry.webkit) && !isNaN(current_entry.webkit) && |  | 
| 236       prev_entry.webkit <= current_entry.webkit) { |  | 
| 237     Config.detailTabs['view-webkit-change'] = 'Webkit'; |  | 
| 238     document.getElementById('webkit-tab').style.display = 'inline-block'; |  | 
| 239     var url = 'http://trac.webkit.org/log/?verbose=on&rev=' + |  | 
| 240         current_entry.webkit + '&stop_rev=' + prev_entry.webkit; |  | 
| 241     document.getElementById('view-webkit-change').src = url; |  | 
| 242   } else { |  | 
| 243     var webkitView = document.getElementById('view-webkit-change'); |  | 
| 244     if (webkitView.style.display == 'block') |  | 
| 245       show_first_view(); |  | 
| 246     delete Config.detailTabs['view-webkit-change']; |  | 
| 247     document.getElementById('webkit-tab').style.display = 'none'; |  | 
| 248   } |  | 
| 249 |  | 
| 250   if (!did_position_details) { |  | 
| 251     show_first_view(); |  | 
| 252     position_details(); |  | 
| 253     did_position_details = true; |  | 
| 254   } |  | 
| 255 } |  | 
| 256 |  | 
| 257 function show_first_view() { |  | 
| 258     for (var tab in Config.detailTabs) { |  | 
| 259       change_view(tab); |  | 
| 260       break; |  | 
| 261     } |  | 
| 262 } |  | 
| 263 |  | 
| 264 function received_summary(data, error) { |  | 
| 265   if (error) { |  | 
| 266     report_error(error); |  | 
| 267     return; |  | 
| 268   } |  | 
| 269   // Parse the summary data file. |  | 
| 270   var rows = new Rows(data); |  | 
| 271   var max_rows = rows.length; |  | 
| 272   if (max_rows > params.history) |  | 
| 273     max_rows = params.history; |  | 
| 274 |  | 
| 275   var allTraces = {}; |  | 
| 276 |  | 
| 277   // Find the start and end of the data slice we will focus on. |  | 
| 278   var start_row = 0; |  | 
| 279   if (params.rev > 0) { |  | 
| 280     var i = 0; |  | 
| 281     while (i < rows.length) { |  | 
| 282       var row = rows.get(i); |  | 
| 283 |  | 
| 284       // If the current row's revision is higher than the desired revision, |  | 
| 285       // continue searching. |  | 
| 286       if (row.revision > params.rev) { |  | 
| 287         i++; |  | 
| 288         continue; |  | 
| 289       } |  | 
| 290 |  | 
| 291       // We're either just under or at the desired revision. |  | 
| 292       start_row = i; |  | 
| 293 |  | 
| 294       // If the desired revision does not exist, use the row before it. |  | 
| 295       if (row.revision < params.rev && start_row > 0) |  | 
| 296         start_row -= 1; |  | 
| 297 |  | 
| 298       break; |  | 
| 299     } |  | 
| 300   } |  | 
| 301 |  | 
| 302   // Some summary files contain data not listed in rev-descending order.  For |  | 
| 303   // those cases, it is possible we will find a start row in the middle of the |  | 
| 304   // data whose neighboring data is not nearby.  See xp-release-dual-core |  | 
| 305   // moz rev 265 => no graph. |  | 
| 306   var end_row = start_row + max_rows; |  | 
| 307 |  | 
| 308   // Build and order a list of revision numbers. |  | 
| 309   var revisionNumbers = []; |  | 
| 310   var hasNumericRevisions = true; |  | 
| 311   // graphData[rev] = {trace1:[value, stddev], trace2:[value, stddev], ...} |  | 
| 312   var graphData = {}; |  | 
| 313   for (var i = start_row; i < end_row; ++i) { |  | 
| 314     var row = rows.get(i); |  | 
| 315     if (!row) |  | 
| 316       continue; |  | 
| 317     var traces = row['traces']; |  | 
| 318     for (var j = 0; j < traces.length; ++j) |  | 
| 319       traces[j] = parseFloat(traces[j]); |  | 
| 320 |  | 
| 321     if (!(row.revision in graphData)) { |  | 
| 322       graphData[row.revision] = {}; |  | 
| 323     } |  | 
| 324     graphData[row.revision][row.webkitRevision] = traces; |  | 
| 325     if (isNaN(row.revision) || isNaN(row.webkitRevision)) { |  | 
| 326       hasNumericRevisions = false; |  | 
| 327     } |  | 
| 328     revisionNumbers.push( |  | 
| 329         { chromium: row.revision, webkit: row.webkitRevision }); |  | 
| 330 |  | 
| 331     // Collect unique trace names.  If traces are explicitly specified in |  | 
| 332     // params, delete unspecified trace data. |  | 
| 333     for (var traceName in traces) { |  | 
| 334       if (typeof(params['trace']) != 'undefined' && |  | 
| 335           params['trace'][traceName] != 1) { |  | 
| 336         delete(traces[traceName]); |  | 
| 337       } |  | 
| 338       allTraces[traceName] = 1; |  | 
| 339     } |  | 
| 340   } |  | 
| 341 |  | 
| 342   // Build a list of all the trace names we've seen, in the order in which |  | 
| 343   // they appear in the data file. Although JS objects are not required by |  | 
| 344   // the spec to iterate their properties in order, in practice they do, |  | 
| 345   // because it causes compatibility problems otherwise. |  | 
| 346   var traceNames = []; |  | 
| 347   for (var traceName in allTraces) |  | 
| 348     traceNames.push(traceName); |  | 
| 349 |  | 
| 350   first_trace = traceNames[0]; |  | 
| 351 |  | 
| 352   // If the revisions are numeric (svn), sort them numerically to ensure they |  | 
| 353   // are in ascending order. Otherwise, if the revisions aren't numeric (git), |  | 
| 354   // reverse them under the assumption the rows were prepended to the file. |  | 
| 355   if (hasNumericRevisions) { |  | 
| 356     revisionNumbers.sort(function(a, b) { |  | 
| 357         var revdiff = parseInt(a.chromium, 10) - parseInt(b.chromium, 10); |  | 
| 358         if (revdiff != 0) { |  | 
| 359           return revdiff; |  | 
| 360         } |  | 
| 361         return parseInt(a.webkit, 10) - parseInt(b.webkit, 10); |  | 
| 362       }); |  | 
| 363   } else { |  | 
| 364     revisionNumbers.reverse(); |  | 
| 365   } |  | 
| 366 |  | 
| 367   // Build separate ordered lists of trace data. |  | 
| 368   var traceData = {}; |  | 
| 369   for (var revIndex = 0; revIndex < revisionNumbers.length; ++revIndex) { |  | 
| 370     var rev = revisionNumbers[revIndex].chromium; |  | 
| 371     var webkitrev = revisionNumbers[revIndex].webkit; |  | 
| 372     var revisionData = graphData[rev][webkitrev]; |  | 
| 373     for (var nameIndex = 0; nameIndex < traceNames.length; ++nameIndex) { |  | 
| 374       var traceName = traceNames[nameIndex]; |  | 
| 375       if (!traceData[traceName]) |  | 
| 376         traceData[traceName] = []; |  | 
| 377       if (!revisionData[traceName]) |  | 
| 378         traceData[traceName].push([NaN, NaN]); |  | 
| 379       else |  | 
| 380         traceData[traceName].push([parseFloat(revisionData[traceName][0]), |  | 
| 381                                    parseFloat(revisionData[traceName][1])]); |  | 
| 382     } |  | 
| 383   } |  | 
| 384   var plotData = []; |  | 
| 385   for (var traceName in traceData) |  | 
| 386     plotData.push(traceData[traceName]); |  | 
| 387   var plotter = new Plotter(revisionNumbers, plotData, traceNames, units, |  | 
| 388     document.getElementById("output")); |  | 
| 389   plotter.onclick = on_clicked_plot; |  | 
| 390   plotter.plot(); |  | 
| 391 } |  | 
| 392 |  | 
| 393 function fetch_summary() { |  | 
| 394   if ('graph' in params) |  | 
| 395     file = escape(params.graph) + "-summary.dat" |  | 
| 396   else |  | 
| 397     file = "summary.dat" |  | 
| 398   var baseUrl = params.baseUrl || ''; |  | 
| 399   Fetch(baseUrl + file, received_summary); |  | 
| 400 } |  | 
| 401 |  | 
| 402 function fetch_graph_list() { |  | 
| 403   var baseUrl = params.baseUrl || ''; |  | 
| 404   Fetch(baseUrl + "graphs.dat", received_graph_list); |  | 
| 405 } |  | 
| 406 |  | 
| 407 function initPlotSwitcher(tabs) { |  | 
| 408   var switcher = document.getElementById("switcher"); |  | 
| 409   for (var i = 0; i < tabs.length; i++) { |  | 
| 410     var is_selected = tabs[i] == params.graph; |  | 
| 411     var tab = document.createElement(is_selected ? "span" : "a"); |  | 
| 412     tab.appendChild(document.createTextNode(tabs[i] + " ")); |  | 
| 413     if (!is_selected) |  | 
| 414       tab.addEventListener("click", goToClosure(tabs[i]), false); |  | 
| 415     switcher.appendChild(tab); |  | 
| 416   } |  | 
| 417 } |  | 
| 418 |  | 
| 419 function goToClosure(graph) { |  | 
| 420   return function(){go_to(graph)}; |  | 
| 421 } |  | 
| 422 |  | 
| 423 function position_details() { |  | 
| 424   var views = document.getElementById("views"); |  | 
| 425   views.style.display = "block"; |  | 
| 426   var selectors = document.getElementById("selectors"); |  | 
| 427   selectors.style.display = "block"; |  | 
| 428 |  | 
| 429   var output = document.getElementById("output"); |  | 
| 430   var views_width = output.offsetWidth - selectors.offsetWidth; |  | 
| 431 |  | 
| 432   views.style.height = (window.innerHeight - output.offsetHeight - |  | 
| 433       output.offsetTop - 16) + "px"; |  | 
| 434   selectors.style.top = (views.offsetTop - selectors.offsetHeight + 1) + "px"; |  | 
| 435 } |  | 
| 436 |  | 
| 437 function change_view(target) { |  | 
| 438   for (var tab in Config.detailTabs) { |  | 
| 439     document.getElementById(tab).style.display = |  | 
| 440         (tab == target ? "block" : "none"); |  | 
| 441   } |  | 
| 442 } |  | 
| 443 |  | 
| 444 function init() { |  | 
| 445   // We need to fill the graph list before parsing the params or fetching the |  | 
| 446   // data, so we have a default graph in case none was specified. |  | 
| 447   fetch_graph_list(); |  | 
| 448 } |  | 
| 449 |  | 
| 450 window.addEventListener("resize", position_details, false); |  | 
| 451 window.addEventListener("load", init, false); |  | 
| 452 </script> |  | 
| 453 </head> |  | 
| 454 |  | 
| 455 |  | 
| 456 <body> |  | 
| 457 <div id="header_lookout" align="center"> |  | 
| 458   <font style='color: #0066FF; font-family: Arial, serif; |  | 
| 459                font-size: 20pt; font-weight: bold;'> |  | 
| 460     <script> |  | 
| 461       document.write("<a target=\"_blank\" href=\""); |  | 
| 462       document.write(get_url()); |  | 
| 463       document.write("\">"); |  | 
| 464       if ('header' in params && params.header != '') { |  | 
| 465         document.write(escape(params.header)); |  | 
| 466       } else { |  | 
| 467         document.write(Config.title); |  | 
| 468       } |  | 
| 469       document.write("</a>"); |  | 
| 470     </script> |  | 
| 471   </font> |  | 
| 472 </div> |  | 
| 473 |  | 
| 474 <div id="header_text"> |  | 
| 475 Builds generated by the <a href="http://build.chromium.org/">Chromium Buildbot</
     a> |  | 
| 476 are run through <b> |  | 
| 477 <script> |  | 
| 478 document.write(Config.title); |  | 
| 479 </script> |  | 
| 480 </b>and the results of that test are charted here. |  | 
| 481 </div> |  | 
| 482 |  | 
| 483 <div id="explain"> |  | 
| 484 The vertical axis is measured values, and the horizontal |  | 
| 485 axis is the revision number for the build being tested. |  | 
| 486 </div> |  | 
| 487 <p></p> |  | 
| 488 <div id="switcher"> |  | 
| 489 |  | 
| 490 </div> |  | 
| 491 <div id="output"></div> |  | 
| 492 <div id="details"> |  | 
| 493   <div id="views"> |  | 
| 494     <script> |  | 
| 495       for (var tab in Config.detailTabs) { |  | 
| 496         document.write("<iframe id=\"" + tab + "\"></iframe>"); |  | 
| 497       } |  | 
| 498     </script> |  | 
| 499     <iframe id='view-webkit-change'></iframe> |  | 
| 500   </div> |  | 
| 501   <div id="selectors"> |  | 
| 502     <script> |  | 
| 503       var firstTab = true; |  | 
| 504       for (var tab in Config.detailTabs) { |  | 
| 505         document.write("<div "); |  | 
| 506         if (firstTab) { |  | 
| 507           firstTab = false; |  | 
| 508         } else { |  | 
| 509     document.write("style=\"border-left: none\" "); |  | 
| 510         } |  | 
| 511         document.write("class=\"selector\" onclick=\"change_view('" |  | 
| 512             + tab + "')\">" + Config.detailTabs[tab] + "</div>"); |  | 
| 513       } |  | 
| 514     </script><div id="webkit-tab" class="selector" |  | 
| 515       onclick="change_view('view-webkit-change')">Webkit</div> |  | 
| 516   </div> |  | 
| 517 </div> |  | 
| 518 <pre id="log"></pre> |  | 
| 519 <script> |  | 
| 520 if ('lookout' in params) { |  | 
| 521   document.getElementById("switcher").style.display = "none"; |  | 
| 522   document.getElementById("details").style.display = "none"; |  | 
| 523   document.getElementById("header_text").style.display = "none"; |  | 
| 524   document.getElementById("explain").style.display = "none"; |  | 
| 525   if ('thumbnail' in params) { |  | 
| 526     document.getElementById("header_lookout").style.display = "none"; |  | 
| 527   } |  | 
| 528 } else { |  | 
| 529   document.getElementById("header_lookout").style.display = "none"; |  | 
| 530 } |  | 
| 531 </script> |  | 
| 532 </body> |  | 
| 533 </html> |  | 
| OLD | NEW | 
|---|