/[jscoverage]/trunk/jscoverage.js
ViewVC logotype

Contents of /trunk/jscoverage.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 580 - (show annotations)
Sat Sep 11 02:09:40 2010 UTC (8 years, 1 month ago) by siliconforks
File MIME type: application/javascript
File size: 32836 byte(s)
Use JSON.parse in browsers that support it.

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

  ViewVC Help
Powered by ViewVC 1.1.24