PageRenderTime 172ms CodeModel.GetById 40ms app.highlight 79ms RepoModel.GetById 41ms app.codeStats 1ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/jscoverage.js

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
JavaScript | 1024 lines | 762 code | 106 blank | 156 comment | 149 complexity | e51548c881ee926ba4eea1bde27963ae MD5 | raw file
   1/*
   2    jscoverage.js - code coverage for JavaScript
   3    Copyright (C) 2007, 2008 siliconforks.com
   4
   5    This program is free software; you can redistribute it and/or modify
   6    it under the terms of the GNU General Public License as published by
   7    the Free Software Foundation; either version 2 of the License, or
   8    (at your option) any later version.
   9
  10    This program is distributed in the hope that it will be useful,
  11    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13    GNU General Public License for more details.
  14
  15    You should have received a copy of the GNU General Public License along
  16    with this program; if not, write to the Free Software Foundation, Inc.,
  17    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18*/
  19
  20/**
  21Initializes the _$jscoverage object in a window.  This should be the first
  22function called in the page.
  23@param  w  this should always be the global window object
  24*/
  25function jscoverage_init(w) {
  26  try {
  27    // in Safari, "import" is a syntax error
  28    Components.utils['import']('resource://gre/modules/jscoverage.jsm');
  29    jscoverage_isInvertedMode = true;
  30    return;
  31  }
  32  catch (e) {}
  33
  34  if (w.opener && w.opener.top._$jscoverage) {
  35    // we are in inverted mode
  36    jscoverage_isInvertedMode = true;
  37    if (! w._$jscoverage) {
  38      w._$jscoverage = w.opener.top._$jscoverage;
  39    }
  40  }
  41  else {
  42    // we are not in inverted mode
  43    jscoverage_isInvertedMode = false;
  44    if (! w._$jscoverage) {
  45      w._$jscoverage = {};
  46    }
  47  }
  48}
  49
  50var jscoverage_currentFile = null;
  51var jscoverage_currentLine = null;
  52
  53var jscoverage_inLengthyOperation = false;
  54
  55/*
  56Possible states:
  57			isInvertedMode	isServer	isReport	tabs
  58normal			false		false		false		Browser
  59inverted		true		false		false		
  60server, normal		false		true		false		Browser, Store
  61server, inverted	true		true		false		Store
  62report			false		false		true		
  63*/
  64var jscoverage_isInvertedMode = false;
  65var jscoverage_isServer = false;
  66var jscoverage_isReport = false;
  67
  68jscoverage_init(window);
  69
  70function jscoverage_createRequest() {
  71  // Note that the IE7 XMLHttpRequest does not support file URL's.
  72  // http://xhab.blogspot.com/2006/11/ie7-support-for-xmlhttprequest.html
  73  // http://blogs.msdn.com/ie/archive/2006/12/06/file-uris-in-windows.aspx
  74//#JSCOVERAGE_IF
  75  if (window.ActiveXObject) {
  76    return new ActiveXObject("Microsoft.XMLHTTP");
  77  }
  78  else {
  79    return new XMLHttpRequest();
  80  }
  81}
  82
  83// http://www.quirksmode.org/js/findpos.html
  84function jscoverage_findPos(obj) {
  85  var result = 0;
  86  do {
  87    result += obj.offsetTop;
  88    obj = obj.offsetParent;
  89  }
  90  while (obj);
  91  return result;
  92}
  93
  94// http://www.quirksmode.org/viewport/compatibility.html
  95function jscoverage_getViewportHeight() {
  96//#JSCOVERAGE_IF /MSIE/.test(navigator.userAgent)
  97  if (self.innerHeight) {
  98    // all except Explorer
  99    return self.innerHeight;
 100  }
 101  else if (document.documentElement && document.documentElement.clientHeight) {
 102    // Explorer 6 Strict Mode
 103    return document.documentElement.clientHeight;
 104  }
 105  else if (document.body) {
 106    // other Explorers
 107    return document.body.clientHeight;
 108  }
 109  else {
 110    throw "Couldn't calculate viewport height";
 111  }
 112//#JSCOVERAGE_ENDIF
 113}
 114
 115/**
 116Indicates visually that a lengthy operation has begun.  The progress bar is
 117displayed, and the cursor is changed to busy (on browsers which support this).
 118*/
 119function jscoverage_beginLengthyOperation() {
 120  jscoverage_inLengthyOperation = true;
 121
 122  var progressBar = document.getElementById('progressBar');
 123  progressBar.style.visibility = 'visible';
 124  ProgressBar.setPercentage(progressBar, 0);
 125  var progressLabel = document.getElementById('progressLabel');
 126  progressLabel.style.visibility = 'visible';
 127
 128  /* blacklist buggy browsers */
 129//#JSCOVERAGE_IF
 130  if (! /Opera|WebKit/.test(navigator.userAgent)) {
 131    /*
 132    Change the cursor style of each element.  Note that changing the class of the
 133    element (to one with a busy cursor) is buggy in IE.
 134    */
 135    var tabs = document.getElementById('tabs').getElementsByTagName('div');
 136    var i;
 137    for (i = 0; i < tabs.length; i++) {
 138      tabs.item(i).style.cursor = 'wait';
 139    }
 140  }
 141}
 142
 143/**
 144Removes the progress bar and busy cursor.
 145*/
 146function jscoverage_endLengthyOperation() {
 147  var progressBar = document.getElementById('progressBar');
 148  ProgressBar.setPercentage(progressBar, 100);
 149  setTimeout(function() {
 150    jscoverage_inLengthyOperation = false;
 151    progressBar.style.visibility = 'hidden';
 152    var progressLabel = document.getElementById('progressLabel');
 153    progressLabel.style.visibility = 'hidden';
 154    progressLabel.innerHTML = '';
 155
 156    var tabs = document.getElementById('tabs').getElementsByTagName('div');
 157    var i;
 158    for (i = 0; i < tabs.length; i++) {
 159      tabs.item(i).style.cursor = '';
 160    }
 161  }, 50);
 162}
 163
 164function jscoverage_setSize() {
 165//#JSCOVERAGE_IF /MSIE/.test(navigator.userAgent)
 166  var viewportHeight = jscoverage_getViewportHeight();
 167
 168  /*
 169  border-top-width:     1px
 170  padding-top:         10px
 171  padding-bottom:      10px
 172  border-bottom-width:  1px
 173  margin-bottom:       10px
 174                       ----
 175                       32px
 176  */
 177  var tabPages = document.getElementById('tabPages');
 178  var tabPageHeight = (viewportHeight - jscoverage_findPos(tabPages) - 32) + 'px';
 179  var nodeList = tabPages.childNodes;
 180  var length = nodeList.length;
 181  for (var i = 0; i < length; i++) {
 182    var node = nodeList.item(i);
 183    if (node.nodeType !== 1) {
 184      continue;
 185    }
 186    node.style.height = tabPageHeight;
 187  }
 188
 189  var iframeDiv = document.getElementById('iframeDiv');
 190  // may not exist if we have removed the first tab
 191  if (iframeDiv) {
 192    iframeDiv.style.height = (viewportHeight - jscoverage_findPos(iframeDiv) - 21) + 'px';
 193  }
 194
 195  var summaryDiv = document.getElementById('summaryDiv');
 196  summaryDiv.style.height = (viewportHeight - jscoverage_findPos(summaryDiv) - 21) + 'px';
 197
 198  var sourceDiv = document.getElementById('sourceDiv');
 199  sourceDiv.style.height = (viewportHeight - jscoverage_findPos(sourceDiv) - 21) + 'px';
 200
 201  var storeDiv = document.getElementById('storeDiv');
 202  if (storeDiv) {
 203    storeDiv.style.height = (viewportHeight - jscoverage_findPos(storeDiv) - 21) + 'px';
 204  }
 205//#JSCOVERAGE_ENDIF
 206}
 207
 208/**
 209Returns the boolean value of a string.  Values 'false', 'f', 'no', 'n', 'off',
 210and '0' (upper or lower case) are false.
 211@param  s  the string
 212@return  a boolean value
 213*/
 214function jscoverage_getBooleanValue(s) {
 215  s = s.toLowerCase();
 216  if (s === 'false' || s === 'f' || s === 'no' || s === 'n' || s === 'off' || s === '0') {
 217    return false;
 218  }
 219  return true;
 220}
 221
 222function jscoverage_removeTab(id) {
 223  var tab = document.getElementById(id + 'Tab');
 224  tab.parentNode.removeChild(tab);
 225  var tabPage = document.getElementById(id + 'TabPage');
 226  tabPage.parentNode.removeChild(tabPage);
 227}
 228
 229/**
 230Initializes the contents of the tabs.  This sets the initial values of the
 231input field and iframe in the "Browser" tab and the checkbox in the "Summary"
 232tab.
 233@param  queryString  this should always be location.search
 234*/
 235function jscoverage_initTabContents(queryString) {
 236  var showMissingColumn = false;
 237  var parameters, parameter, i, index, url, name, value;
 238  if (queryString.length > 0) {
 239    // chop off the question mark
 240    queryString = queryString.substring(1);
 241    parameters = queryString.split(/&|;/);
 242    for (i = 0; i < parameters.length; i++) {
 243      parameter = parameters[i];
 244      index = parameter.indexOf('=');
 245      if (index === -1) {
 246        // still works with old syntax
 247        url = parameter;
 248      }
 249      else {
 250        name = parameter.substr(0, index);
 251        value = parameter.substr(index + 1);
 252        if (name === 'missing' || name === 'm') {
 253          showMissingColumn = jscoverage_getBooleanValue(value);
 254        }
 255        else if (name === 'url' || name === 'u') {
 256          url = value;
 257        }
 258      }
 259    }
 260  }
 261
 262  var checkbox = document.getElementById('checkbox');
 263  checkbox.checked = showMissingColumn;
 264  if (showMissingColumn) {
 265    jscoverage_appendMissingColumn();
 266  }
 267
 268  // this will automatically propagate to the input field
 269  if (url) {
 270    frames[0].location = url;
 271  }
 272
 273  // if the browser tab is absent, we have to initialize the summary tab
 274  if (! document.getElementById('browserTab')) {
 275    jscoverage_recalculateSummaryTab();
 276  }
 277}
 278
 279function jscoverage_body_load() {
 280  var progressBar = document.getElementById('progressBar');
 281  ProgressBar.init(progressBar);
 282
 283  function reportError(e) {
 284    jscoverage_endLengthyOperation();
 285    var summaryThrobber = document.getElementById('summaryThrobber');
 286    summaryThrobber.style.visibility = 'hidden';
 287    var div = document.getElementById('summaryErrorDiv');
 288    div.innerHTML = 'Error: ' + e;
 289  }
 290
 291  if (jscoverage_isReport) {
 292    jscoverage_beginLengthyOperation();
 293    var summaryThrobber = document.getElementById('summaryThrobber');
 294    summaryThrobber.style.visibility = 'visible';
 295    var request = jscoverage_createRequest();
 296    try {
 297      request.open('GET', 'jscoverage.json', true);
 298      request.onreadystatechange = function (event) {
 299        if (request.readyState === 4) {
 300          try {
 301            if (request.status !== 0 && request.status !== 200) {
 302              throw request.status;
 303            }
 304            var response = request.responseText;
 305            if (response === '') {
 306              throw 404;
 307            }
 308            var json = eval('(' + response + ')');
 309            var file;
 310            for (file in json) {
 311              var fileCoverage = json[file];
 312              _$jscoverage[file] = fileCoverage.coverage;
 313              _$jscoverage[file].source = fileCoverage.source;
 314            }
 315            jscoverage_recalculateSummaryTab();
 316            summaryThrobber.style.visibility = 'hidden';
 317          }
 318          catch (e) {
 319            reportError(e);
 320          }
 321        }
 322      };
 323      request.send(null);
 324    }
 325    catch (e) {
 326      reportError(e);
 327    }
 328
 329    jscoverage_removeTab('browser');
 330    jscoverage_removeTab('store');
 331  }
 332  else {
 333    if (jscoverage_isInvertedMode) {
 334      jscoverage_removeTab('browser');
 335    }
 336
 337    if (! jscoverage_isServer) {
 338      jscoverage_removeTab('store');
 339    }
 340  }
 341
 342  jscoverage_initTabControl();
 343
 344  jscoverage_initTabContents(location.search);
 345}
 346
 347function jscoverage_body_resize() {
 348  if (/MSIE/.test(navigator.userAgent)) {
 349    jscoverage_setSize();
 350  }
 351}
 352
 353// -----------------------------------------------------------------------------
 354// tab 1
 355
 356function jscoverage_updateBrowser() {
 357  var input = document.getElementById("location");
 358  frames[0].location = input.value;
 359}
 360
 361function jscoverage_input_keypress(e) {
 362  if (e.keyCode === 13) {
 363    jscoverage_updateBrowser();
 364  }
 365}
 366
 367function jscoverage_button_click() {
 368  jscoverage_updateBrowser();
 369}
 370
 371function jscoverage_browser_load() {
 372  /* update the input box */
 373  var input = document.getElementById("location");
 374
 375  /* sometimes IE seems to fire this after the tab has been removed */
 376  if (input) {
 377    input.value = frames[0].location;
 378  }
 379}
 380
 381// -----------------------------------------------------------------------------
 382// tab 2
 383
 384function jscoverage_createLink(file, line) {
 385  var link = document.createElement("a");
 386
 387  var url;
 388  var call;
 389  var text;
 390  if (line) {
 391    url = file + ".jscoverage.html?" + line;
 392    call = "jscoverage_get('" + file + "', " + line + ");";
 393    text = line.toString();
 394  }
 395  else {
 396    url = file + ".jscoverage.html";
 397    call = "jscoverage_get('" + file + "');";
 398    text = file;
 399  }
 400
 401  link.setAttribute('href', 'javascript:' + call);
 402  link.appendChild(document.createTextNode(text));
 403
 404  return link;
 405}
 406
 407function jscoverage_recalculateSummaryTab(cc) {
 408  var checkbox = document.getElementById('checkbox');
 409  var showMissingColumn = checkbox.checked;
 410
 411  if (! cc) {
 412    cc = window._$jscoverage;
 413  }
 414  if (! cc) {
 415//#JSCOVERAGE_IF 0
 416    throw "No coverage information found.";
 417//#JSCOVERAGE_ENDIF
 418  }
 419
 420  var tbody = document.getElementById("summaryTbody");
 421  while (tbody.hasChildNodes()) {
 422    tbody.removeChild(tbody.firstChild);
 423  }
 424
 425  var totals = { files:0, statements:0, executed:0, coverage:0, skipped:0 };
 426
 427  var file;
 428  var files = [];
 429  for (file in cc) {
 430    files.push(file);
 431  }
 432  files.sort();
 433
 434  var rowCounter = 0;
 435  for (var f = 0; f < files.length; f++) {
 436    file = files[f];
 437    var lineNumber;
 438    var num_statements = 0;
 439    var num_executed = 0;
 440    var missing = [];
 441    var fileCC = cc[file];
 442    var length = fileCC.length;
 443    var currentConditionalEnd = 0;
 444    var conditionals = null;
 445    if (fileCC.conditionals) {
 446      conditionals = fileCC.conditionals;
 447    }
 448    for (lineNumber = 0; lineNumber < length; lineNumber++) {
 449      var n = fileCC[lineNumber];
 450
 451      if (lineNumber === currentConditionalEnd) {
 452        currentConditionalEnd = 0;
 453      }
 454      else if (currentConditionalEnd === 0 && conditionals && conditionals[lineNumber]) {
 455        currentConditionalEnd = conditionals[lineNumber];
 456      }
 457
 458      if (currentConditionalEnd !== 0) {
 459        continue;
 460      }
 461
 462      if (n === undefined || n === null) {
 463        continue;
 464      }
 465
 466      if (n === 0) {
 467        missing.push(lineNumber);
 468      }
 469      else {
 470        num_executed++;
 471      }
 472      num_statements++;
 473    }
 474
 475    var percentage = ( num_statements === 0 ? 0 : parseInt(100 * num_executed / num_statements) );
 476
 477    var row = document.createElement("tr");
 478    row.className = ( rowCounter++ % 2 == 0 ? "odd" : "even" );
 479
 480    var cell = document.createElement("td");
 481    cell.className = 'leftColumn';
 482    var link = jscoverage_createLink(file);
 483    cell.appendChild(link);
 484
 485    row.appendChild(cell);
 486
 487    cell = document.createElement("td");
 488    cell.className = 'numeric';
 489    cell.appendChild(document.createTextNode(num_statements));
 490    row.appendChild(cell);
 491
 492    cell = document.createElement("td");
 493    cell.className = 'numeric';
 494    cell.appendChild(document.createTextNode(num_executed));
 495    row.appendChild(cell);
 496
 497    // new coverage td containing a bar graph
 498    cell = document.createElement("td");
 499    cell.className = 'coverage';
 500    var pctGraph = document.createElement("div"),
 501        covered = document.createElement("div"),
 502        pct = document.createElement("span");
 503    pctGraph.className = "pctGraph";
 504    if( num_statements === 0 ) {
 505        covered.className = "skipped";
 506        pct.appendChild(document.createTextNode("N/A"));
 507    } else {
 508        covered.className = "covered";
 509        covered.style.width = percentage + "px";
 510        pct.appendChild(document.createTextNode(percentage + '%'));
 511    }
 512    pct.className = "pct";
 513    pctGraph.appendChild(covered);
 514    cell.appendChild(pctGraph);
 515    cell.appendChild(pct);
 516    row.appendChild(cell);
 517
 518    if (showMissingColumn) {
 519      cell = document.createElement("td");
 520      for (var i = 0; i < missing.length; i++) {
 521        if (i !== 0) {
 522          cell.appendChild(document.createTextNode(", "));
 523        }
 524        link = jscoverage_createLink(file, missing[i]);
 525        cell.appendChild(link);
 526      }
 527      row.appendChild(cell);
 528    }
 529
 530    tbody.appendChild(row);
 531
 532    totals['files'] ++;
 533    totals['statements'] += num_statements;
 534    totals['executed'] += num_executed;
 535    totals['coverage'] += percentage;
 536    if( num_statements === 0 ) {
 537        totals['skipped']++;
 538    }
 539
 540    // write totals data into summaryTotals row
 541    var tr = document.getElementById("summaryTotals");
 542    if (tr) {
 543        var tds = tr.getElementsByTagName("td");
 544        tds[0].getElementsByTagName("span")[1].firstChild.nodeValue = totals['files'];
 545        tds[1].firstChild.nodeValue = totals['statements'];
 546        tds[2].firstChild.nodeValue = totals['executed'];
 547
 548        var coverage = parseInt(totals['coverage'] / ( totals['files'] - totals['skipped'] ) );
 549        if( isNaN( coverage ) ) {
 550            coverage = 0;
 551        }
 552        tds[3].getElementsByTagName("span")[0].firstChild.nodeValue = coverage + '%';
 553        tds[3].getElementsByTagName("div")[1].style.width = coverage + 'px';
 554    }
 555
 556  }
 557  jscoverage_endLengthyOperation();
 558}
 559
 560function jscoverage_appendMissingColumn() {
 561  var headerRow = document.getElementById('headerRow');
 562  var missingHeader = document.createElement('th');
 563  missingHeader.id = 'missingHeader';
 564  missingHeader.innerHTML = '<abbr title="List of statements missed during execution">Missing</abbr>';
 565  headerRow.appendChild(missingHeader);
 566  var summaryTotals = document.getElementById('summaryTotals');
 567  var empty = document.createElement('td');
 568  empty.id = 'missingCell';
 569  summaryTotals.appendChild(empty);
 570}
 571
 572function jscoverage_removeMissingColumn() {
 573  var missingNode;
 574  missingNode = document.getElementById('missingHeader');
 575  missingNode.parentNode.removeChild(missingNode);
 576  missingNode = document.getElementById('missingCell');
 577  missingNode.parentNode.removeChild(missingNode);
 578}
 579
 580function jscoverage_checkbox_click() {
 581  if (jscoverage_inLengthyOperation) {
 582    return false;
 583  }
 584  jscoverage_beginLengthyOperation();
 585  var checkbox = document.getElementById('checkbox');
 586  var showMissingColumn = checkbox.checked;
 587  setTimeout(function() {
 588    if (showMissingColumn) {
 589      jscoverage_appendMissingColumn();
 590    }
 591    else {
 592      jscoverage_removeMissingColumn();
 593    }
 594    jscoverage_recalculateSummaryTab();
 595  }, 50);
 596  return true;
 597}
 598
 599// -----------------------------------------------------------------------------
 600// tab 3
 601
 602function jscoverage_makeTable() {
 603  var coverage = _$jscoverage[jscoverage_currentFile];
 604  var lines = coverage.source;
 605
 606  // this can happen if there is an error in the original JavaScript file
 607  if (! lines) {
 608    lines = [];
 609  }
 610
 611  var rows = ['<table id="sourceTable">'];
 612  var i = 0;
 613  var progressBar = document.getElementById('progressBar');
 614  var tableHTML;
 615  var currentConditionalEnd = 0;
 616
 617  function joinTableRows() {
 618    tableHTML = rows.join('');
 619    ProgressBar.setPercentage(progressBar, 60);
 620    /*
 621    This may be a long delay, so set a timeout of 100 ms to make sure the
 622    display is updated.
 623    */
 624    setTimeout(appendTable, 100);
 625  }
 626
 627  function appendTable() {
 628    var sourceDiv = document.getElementById('sourceDiv');
 629    sourceDiv.innerHTML = tableHTML;
 630    ProgressBar.setPercentage(progressBar, 80);
 631    setTimeout(jscoverage_scrollToLine, 0);
 632  }
 633
 634  while (i < lines.length) {
 635    var lineNumber = i + 1;
 636
 637    if (lineNumber === currentConditionalEnd) {
 638      currentConditionalEnd = 0;
 639    }
 640    else if (currentConditionalEnd === 0 && coverage.conditionals && coverage.conditionals[lineNumber]) {
 641      currentConditionalEnd = coverage.conditionals[lineNumber];
 642    }
 643
 644    var row = '<tr>';
 645    row += '<td class="numeric">' + lineNumber + '</td>';
 646    var timesExecuted = coverage[lineNumber];
 647    if (timesExecuted !== undefined && timesExecuted !== null) {
 648      if (currentConditionalEnd !== 0) {
 649        row += '<td class="y numeric">';
 650      }
 651      else if (timesExecuted === 0) {
 652        row += '<td class="r numeric" id="line-' + lineNumber + '">';
 653      }
 654      else {
 655        row += '<td class="g numeric">';
 656      }
 657      row += timesExecuted;
 658      row += '</td>';
 659    }
 660    else {
 661      row += '<td></td>';
 662    }
 663    row += '<td><pre>' + lines[i] + '</pre></td>';
 664    row += '</tr>';
 665    row += '\n';
 666    rows[lineNumber] = row;
 667    i++;
 668  }
 669  rows[i + 1] = '</table>';
 670  ProgressBar.setPercentage(progressBar, 40);
 671  setTimeout(joinTableRows, 0);
 672}
 673
 674function jscoverage_scrollToLine() {
 675  jscoverage_selectTab('sourceTab');
 676  if (! window.jscoverage_currentLine) {
 677    jscoverage_endLengthyOperation();
 678    return;
 679  }
 680  var div = document.getElementById('sourceDiv');
 681  if (jscoverage_currentLine === 1) {
 682    div.scrollTop = 0;
 683  }
 684  else {
 685    var cell = document.getElementById('line-' + jscoverage_currentLine);
 686
 687    // this might not be there if there is an error in the original JavaScript
 688    if (cell) {
 689      var divOffset = jscoverage_findPos(div);
 690      var cellOffset = jscoverage_findPos(cell);
 691      div.scrollTop = cellOffset - divOffset;
 692    }
 693  }
 694  jscoverage_currentLine = 0;
 695  jscoverage_endLengthyOperation();
 696}
 697
 698/**
 699Loads the given file (and optional line) in the source tab.
 700*/
 701function jscoverage_get(file, line) {
 702  if (jscoverage_inLengthyOperation) {
 703    return;
 704  }
 705  jscoverage_beginLengthyOperation();
 706  setTimeout(function() {
 707    var sourceDiv = document.getElementById('sourceDiv');
 708    sourceDiv.innerHTML = '';
 709    jscoverage_selectTab('sourceTab');
 710    if (file === jscoverage_currentFile) {
 711      jscoverage_currentLine = line;
 712      jscoverage_recalculateSourceTab();
 713    }
 714    else {
 715      if (jscoverage_currentFile === null) {
 716        var tab = document.getElementById('sourceTab');
 717        tab.className = '';
 718        tab.onclick = jscoverage_tab_click;
 719      }
 720      jscoverage_currentFile = file;
 721      jscoverage_currentLine = line || 1;  // when changing the source, always scroll to top
 722      var fileDiv = document.getElementById('fileDiv');
 723      fileDiv.innerHTML = jscoverage_currentFile;
 724      jscoverage_recalculateSourceTab();
 725      return;
 726    }
 727  }, 50);
 728}
 729
 730/**
 731Calculates coverage statistics for the current source file.
 732*/
 733function jscoverage_recalculateSourceTab() {
 734  if (! jscoverage_currentFile) {
 735    jscoverage_endLengthyOperation();
 736    return;
 737  }
 738  var progressLabel = document.getElementById('progressLabel');
 739  progressLabel.innerHTML = 'Calculating coverage ...';
 740  var progressBar = document.getElementById('progressBar');
 741  ProgressBar.setPercentage(progressBar, 20);
 742  setTimeout(jscoverage_makeTable, 0);
 743}
 744
 745// -----------------------------------------------------------------------------
 746// tabs
 747
 748/**
 749Initializes the tab control.  This function must be called when the document is
 750loaded.
 751*/
 752function jscoverage_initTabControl() {
 753  var tabs = document.getElementById('tabs');
 754  var i;
 755  var child;
 756  var tabNum = 0;
 757  for (i = 0; i < tabs.childNodes.length; i++) {
 758    child = tabs.childNodes.item(i);
 759    if (child.nodeType === 1) {
 760      if (child.className !== 'disabled') {
 761        child.onclick = jscoverage_tab_click;
 762      }
 763      tabNum++;
 764    }
 765  }
 766  jscoverage_selectTab(0);
 767}
 768
 769/**
 770Selects a tab.
 771@param  tab  the integer index of the tab (0, 1, 2, or 3)
 772             OR
 773             the ID of the tab element
 774             OR
 775             the tab element itself
 776*/
 777function jscoverage_selectTab(tab) {
 778  if (typeof tab !== 'number') {
 779    tab = jscoverage_tabIndexOf(tab);
 780  }
 781  var tabs = document.getElementById('tabs');
 782  var tabPages = document.getElementById('tabPages');
 783  var nodeList;
 784  var tabNum;
 785  var i;
 786  var node;
 787
 788  nodeList = tabs.childNodes;
 789  tabNum = 0;
 790  for (i = 0; i < nodeList.length; i++) {
 791    node = nodeList.item(i);
 792    if (node.nodeType !== 1) {
 793      continue;
 794    }
 795
 796    if (node.className !== 'disabled') {
 797      if (tabNum === tab) {
 798        node.className = 'selected';
 799      }
 800      else {
 801        node.className = '';
 802      }
 803    }
 804    tabNum++;
 805  }
 806
 807  nodeList = tabPages.childNodes;
 808  tabNum = 0;
 809  for (i = 0; i < nodeList.length; i++) {
 810    node = nodeList.item(i);
 811    if (node.nodeType !== 1) {
 812      continue;
 813    }
 814
 815    if (tabNum === tab) {
 816      node.className = 'selected TabPage';
 817    }
 818    else {
 819      node.className = 'TabPage';
 820    }
 821    tabNum++;
 822  }
 823}
 824
 825/**
 826Returns an integer (0, 1, 2, or 3) representing the index of a given tab.
 827@param  tab  the ID of the tab element
 828             OR
 829             the tab element itself
 830*/
 831function jscoverage_tabIndexOf(tab) {
 832  if (typeof tab === 'string') {
 833    tab = document.getElementById(tab);
 834  }
 835  var tabs = document.getElementById('tabs');
 836  var i;
 837  var child;
 838  var tabNum = 0;
 839  for (i = 0; i < tabs.childNodes.length; i++) {
 840    child = tabs.childNodes.item(i);
 841    if (child.nodeType === 1) {
 842      if (child === tab) {
 843        return tabNum;
 844      }
 845      tabNum++;
 846    }
 847  }
 848//#JSCOVERAGE_IF 0
 849  throw "Tab not found";
 850//#JSCOVERAGE_ENDIF
 851}
 852
 853function jscoverage_tab_click(e) {
 854  if (jscoverage_inLengthyOperation) {
 855    return;
 856  }
 857  var target;
 858//#JSCOVERAGE_IF
 859  if (e) {
 860    target = e.target;
 861  }
 862  else if (window.event) {
 863    // IE
 864    target = window.event.srcElement;
 865  }
 866  if (target.className === 'selected') {
 867    return;
 868  }
 869  jscoverage_beginLengthyOperation();
 870  setTimeout(function() {
 871    if (target.id === 'summaryTab') {
 872      var tbody = document.getElementById("summaryTbody");
 873      while (tbody.hasChildNodes()) {
 874        tbody.removeChild(tbody.firstChild);
 875      }
 876    }
 877    else if (target.id === 'sourceTab') {
 878      var sourceDiv = document.getElementById('sourceDiv');
 879      sourceDiv.innerHTML = '';
 880    }
 881    jscoverage_selectTab(target);
 882    if (target.id === 'summaryTab') {
 883      jscoverage_recalculateSummaryTab();
 884    }
 885    else if (target.id === 'sourceTab') {
 886      jscoverage_recalculateSourceTab();
 887    }
 888    else {
 889      jscoverage_endLengthyOperation();
 890    }
 891  }, 50);
 892}
 893
 894// -----------------------------------------------------------------------------
 895// progress bar
 896
 897var ProgressBar = {
 898  init: function(element) {
 899    element._percentage = 0;
 900
 901    /* doing this via JavaScript crashes Safari */
 902/*
 903    var pctGraph = document.createElement('div');
 904    pctGraph.className = 'pctGraph';
 905    element.appendChild(pctGraph);
 906    var covered = document.createElement('div');
 907    covered.className = 'covered';
 908    pctGraph.appendChild(covered);
 909    var pct = document.createElement('span');
 910    pct.className = 'pct';
 911    element.appendChild(pct);
 912*/
 913
 914    ProgressBar._update(element);
 915  },
 916  setPercentage: function(element, percentage) {
 917    element._percentage = percentage;
 918    ProgressBar._update(element);
 919  },
 920  _update: function(element) {
 921    var pctGraph = element.getElementsByTagName('div').item(0);
 922    var covered = pctGraph.getElementsByTagName('div').item(0);
 923    var pct = element.getElementsByTagName('span').item(0);
 924    pct.innerHTML = element._percentage.toString() + '%';
 925    covered.style.width = element._percentage + 'px';
 926  }
 927};
 928
 929// -----------------------------------------------------------------------------
 930// reports
 931
 932function jscoverage_pad(s) {
 933  return '0000'.substr(s.length) + s;
 934}
 935
 936function jscoverage_quote(s) {
 937  return '"' + s.replace(/[\u0000-\u001f"\\\u007f-\uffff]/g, function (c) {
 938    switch (c) {
 939    case '\b':
 940      return '\\b';
 941    case '\f':
 942      return '\\f';
 943    case '\n':
 944      return '\\n';
 945    case '\r':
 946      return '\\r';
 947    case '\t':
 948      return '\\t';
 949    // IE doesn't support this
 950    /*
 951    case '\v':
 952      return '\\v';
 953    */
 954    case '"':
 955      return '\\"';
 956    case '\\':
 957      return '\\\\';
 958    default:
 959      return '\\u' + jscoverage_pad(c.charCodeAt(0).toString(16));
 960    }
 961  }) + '"';
 962}
 963
 964function jscoverage_serializeCoverageToJSON() {
 965  var json = [];
 966  for (var file in _$jscoverage) {
 967    var coverage = _$jscoverage[file];
 968    var array = [];
 969    var length = coverage.length;
 970    for (var line = 0; line < length; line++) {
 971      var value = coverage[line];
 972      if (value === undefined || value === null) {
 973        value = 'null';
 974      }
 975      array.push(value);
 976    }
 977    json.push(jscoverage_quote(file) + ':[' + array.join(',') + ']');
 978  }
 979  return '{' + json.join(',') + '}';
 980}
 981
 982function jscoverage_storeButton_click() {
 983  if (jscoverage_inLengthyOperation) {
 984    return;
 985  }
 986
 987  jscoverage_beginLengthyOperation();
 988  var img = document.getElementById('storeImg');
 989  img.style.visibility = 'visible';
 990
 991  var request = jscoverage_createRequest();
 992  request.open('POST', '/jscoverage-store', true);
 993  request.onreadystatechange = function (event) {
 994    if (request.readyState === 4) {
 995      var message;
 996      try {
 997        if (request.status !== 200 && request.status !== 201 && request.status !== 204) {
 998          throw request.status;
 999        }
1000        message = request.responseText;
1001      }
1002      catch (e) {
1003        if (e.toString().search(/^\d{3}$/) === 0) {
1004          message = e + ': ' + request.responseText;
1005        }
1006        else {
1007          message = 'Could not connect to server: ' + e;
1008        }
1009      }
1010
1011      jscoverage_endLengthyOperation();
1012      var img = document.getElementById('storeImg');
1013      img.style.visibility = 'hidden';
1014
1015      var div = document.getElementById('storeDiv');
1016      div.appendChild(document.createTextNode(new Date() + ': ' + message));
1017      div.appendChild(document.createElement('br'));
1018    }
1019  };
1020  request.setRequestHeader('Content-Type', 'application/json');
1021  var json = jscoverage_serializeCoverageToJSON();
1022  request.setRequestHeader('Content-Length', json.length.toString());
1023  request.send(json);
1024}