tools/memory/bloattable.pl

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rwxr-xr-x

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 #!/usr/bin/perl -w
     2 #
     3 # This Source Code Form is subject to the terms of the Mozilla Public
     4 # License, v. 2.0. If a copy of the MPL was not distributed with this
     5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     7 # bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file>
     8 #
     9 # file1, file2, ... filen should be successive BloatView files generated from the same run.
    10 # Summarize them in an HTML table.  Output the HTML to the standard output.
    11 #
    12 # If -debug is set, create a slightly larger html file which is more suitable for debugging this script.
    13 # If -source is set, create an html file that prints the html source as the output
    14 # If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics,
    15 #    respectively, and sort by the nth column (n is zero-based, so the first column has n==0).
    16 #
    17 # See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html
    19 use 5.004;
    20 use strict;
    21 use diagnostics;
    22 use File::Basename;
    23 use Getopt::Long;
    25 # The generated HTML is almost entirely generated by a script.  Only the <HTML>, <HEAD>, and <BODY> elements are explicit
    26 # because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element.
    27 # The script itself is almost all generated by an eval of a large string.  This allows the script to reproduce itself
    28 # when making a new page using document.write's.  Re-sorting the page causes it to regenerate itself in this way.
    32 # Return the file's modification date.
    33 sub fileModDate($) {
    34 	my ($pathName) = @_;
    35 	my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
    36 		stat $pathName or die "Can't stat '$pathName'";
    37 	return $mtime;
    38 }
    41 sub fileCoreName($) {
    42 	my ($pathName) = @_;
    43 	my $fileName = basename($pathName, "");
    44 	$fileName =~ s/\..*//;
    45 	return $fileName;
    46 }
    49 # Convert a raw string into a single-quoted JavaScript string.
    50 sub singleQuoteString($) {
    51 	local ($_) = @_;
    52 	s/\\/\\\\/g;
    53 	s/'/\\'/g;
    54 	s/\n/\\n/g;
    55 	s/<\//<\\\//g;
    56 	return "'$_'";
    57 }
    60 # Convert a raw string into a double-quoted JavaScript string.
    61 sub doubleQuoteString($) {
    62 	local ($_) = @_;
    63 	s/\\/\\\\/g;
    64 	s/"/\\"/g;
    65 	s/\n/\\n/g;
    66 	s/<\//<\\\//g;
    67 	return "\"$_\"";
    68 }
    71 # Quote special HTML characters in the string.
    72 sub quoteHTML($) {
    73 	local ($_) = @_;
    74 	s/\&/&amp;/g;
    75 	s/</&lt;/g;
    76 	s/>/&gt;/g;
    77 	s/ /&nbsp;/g;
    78 	s/\n/<BR>\n/g;
    79 	return $_;
    80 }
    83 # Write the generated page to the standard output.
    84 # The script source code is read from this file past the __END__ marker
    85 # @$scriptData is the JavaScript source for the tables passed to JavaScript.  Each entry is one line of JavaScript.
    86 # @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself.
    87 # If $debug is true, generate the script directly instead of having it eval itself.
    88 # If $source is true, generate a script that displays the page's source instead of the page itself.
    89 sub generate(\@\@$$$$) {
    90 	my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_;
    92 	my @scriptSource = <DATA>;
    93 	chomp @scriptSource;
    94 	print <<'EOS';
    95 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
    96 <HTML>
    97 <HEAD>
    98 <SCRIPT type="text/javascript">
    99 EOS
   101 	foreach (@$scriptData) {print "$_\n";}
   102 	print "\n";
   104 	print "var srcArray = [\n";
   105 	my @quotedScriptSource = map {
   106 		my $line = $_;
   107 		$line =~ s/^\s+//g;
   108 		# $line =~ s/^\/\/SOURCE\s+//g if $source;
   109 		$line =~ s/^\/\/.*//g;
   110 		$line =~ s/\s+$//g;
   111 		$line eq "" ? () : $line
   112 	} @$persistentScriptData, @scriptSource;
   113 	my $lastQuotedLine = pop @quotedScriptSource;
   114 	foreach (@quotedScriptSource) {print doubleQuoteString($_), ",\n";}
   115 	print doubleQuoteString($lastQuotedLine), "];\n\n";
   117 	if ($debug) {
   118 		push @quotedScriptSource, $lastQuotedLine;
   119 		foreach (@quotedScriptSource) {
   120 			s/<\//<\\\//g;	# This fails if a regexp ends with a '<'.  Oh well....
   121 			print "$_\n";
   122 		}
   123 		print "\n";
   124 	} else {
   125 		print "eval(srcArray.join(\"\\n\"));\n\n";
   126 	}
   127 	print "showMode = $showMode;\n";
   128 	print "sortColumn = $sortColumn;\n";
   129 	if ($source) {
   130 		print <<'EOS';
   131 function writeQuotedHTML(s) {
   132 	document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
   133 }
   135 var quotingDocument = {
   136   write: function () {
   137 		for (var i = 0; i < arguments.length; i++)
   138 			writeQuotedHTML(arguments[i]);
   139 	},
   140   writeln: function () {
   141 		for (var i = 0; i < arguments.length; i++)
   142 			writeQuotedHTML(arguments[i]);
   143 		document.writeln('<BR>');
   144 	}
   145 };
   146 EOS
   147 	} else {
   148 		print "showHead(document);\n";
   149 	}
   150 	print "</SCRIPT>\n";
   151 	print "</HEAD>\n\n";
   152 	print "<BODY>\n";
   153 	if ($source) {
   154 		print "<P><TT>";
   155 		print quoteHTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
   156 		print quoteHTML "<HTML>\n";
   157 		print quoteHTML "<HEAD>\n";
   158 		print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n";
   159 		print quoteHTML "</HEAD>\n\n";
   160 		print quoteHTML "<BODY>\n";
   161 		print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n";
   162 		print quoteHTML "</BODY>\n";
   163 		print quoteHTML "</HTML>\n";
   164 		print "</TT></P>\n";
   165 	} else {
   166 		print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
   167 	}
   168 	print "</BODY>\n";
   169 	print "</HTML>\n";
   170 }
   174 # Read the bloat file into hash table $h.  The hash table is indexed by class names;
   175 # each entry is a list with the following elements:
   176 #	bytesAlloc		Total number of bytes allocated
   177 #	bytesNet		Total number of bytes allocated but not deallocated
   178 #	objectsAlloc	Total number of objects allocated
   179 #	objectsNet		Total number of objects allocated but not deallocated
   180 #	refsAlloc		Total number of references AddRef'd
   181 #	refsNet			Total number of references AddRef'd but not Released
   182 # Except for TOTAL, all hash table entries refer to mutually exclusive data.
   183 # $sizes is a hash table indexed by class names.  Each entry of that table contains the class's instance size.
   184 sub readBloatFile($\%\%) {
   185 	my ($file, $h, $sizes) = @_;
   186 	local $_;	# Needed for 'while (<FILE>)' below.
   188 	my $readSomething = 0;
   189 	open FILE, $file;
   190 	while (<FILE>) {
   191 		if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) =
   192 			   /^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) {
   193 			my $bytesAlloc;
   194 			if ($name eq "TOTAL") {
   195 				$size = "undefined";
   196 				$bytesAlloc = "undefined";
   197 			} else {
   198 				$bytesAlloc = $objectsAlloc * $size;
   199 				if ($bytesNet != $objectsNet * $size) {
   200 					print STDERR "In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n";
   201 				}
   202 			}
   203 			print STDERR "Duplicate entry $name in '$file'\n" if $$h{$name};
   204 			$$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet];
   206 			my $oldSize = $$sizes{$name};
   207 			print STDERR "Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize;
   208 			$$sizes{$name} = $size;
   209 			$readSomething = 1;
   210 		} elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
   211 			print STDERR "Unable to parse '$file' line: $_";
   212 		}
   213 	}
   214 	close FILE;
   215 	print STDERR "No data in '$file'\n" unless $readSomething;
   216 	return $h;
   217 }
   220 my %sizes;			# <class-name> => <instance-size>
   221 my %tables;			# <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table>
   223 # Generate the JavaScript source code for the row named $c.  $l can contain the initial entries of the row.
   224 sub genTableRowSource($$) {
   225 	my ($l, $c) = @_;
   226 	my $lastE;
   227 	foreach (@ARGV) {
   228 		my $e = $tables{$_}{$c};
   229 		if (defined($lastE) && !defined($e)) {
   230 			$e = [0,0,0,0,0,0];
   231 			print STDERR "Class $c is defined in an earlier file but not in '$_'\n";
   232 		}
   233 		if (defined $e) {
   234 			if (defined $lastE) {
   235 				for (my $i = 0; $i <= $#$e; $i++) {
   236 					my $n = $$e[$i];
   237 					$l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ",";
   238 				}
   239 				$l .= " ";
   240 			} else {
   241 				$l .= join(",", @$e) . ", ";
   242 			}
   243 			$lastE = $e;
   244 		} else {
   245 			$l .= "0,0,0,0,0,0, ";
   246 		}
   247 	}
   248 	$l .= join(",", @$lastE);
   249 	return "[$l]";
   250 }
   254 my $debug;
   255 my $source;
   256 my $showMode;
   257 my $sortColumn;
   258 my @modeOptions;
   260 GetOptions("debug" => \$debug, "source" => \$source, "byte=i" => \$modeOptions[0], "obj=i" => \$modeOptions[1], "ref=i" => \$modeOptions[2]);
   261 for (my $i = 0; $i != 3; $i++) {
   262 	my $modeOption = $modeOptions[$i];
   263 	if ($modeOption) {
   264 		die "Only one of -byte, -obj, or -ref may be given" if defined $showMode;
   265 		my $nFileColumns = scalar(@ARGV) + 1;
   266 		die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns;
   267 		$showMode = $i;
   268 		if ($modeOption >= 2) {
   269 			$modeOption -= 2;
   270 			$sortColumn = 2 + $showMode*2;
   271 			if ($modeOption >= $nFileColumns) {
   272 				$sortColumn++;
   273 				$modeOption -= $nFileColumns;
   274 			}
   275 			$sortColumn += $modeOption*6;
   276 		} else {
   277 			$sortColumn = $modeOption;
   278 		}
   279 	}
   280 }
   281 unless (defined $showMode) {
   282 	$showMode = 0;
   283 	$sortColumn = 0;
   284 }
   286 # Read all of the bloat files.
   287 foreach (@ARGV) {
   288 	unless ($tables{$_}) {
   289 		my $f = $_;
   290 		my %table;
   292 		readBloatFile $_, %table, %sizes;
   293 		$tables{$_} = \%table;
   294 	}
   295 }
   296 die "No input" unless %sizes;
   298 my @scriptData;	# JavaScript source for the tables passed to JavaScript.  Each entry is one line of JavaScript.
   299 my @persistentScriptData;	# Same as @scriptData, but persists the page reloads itself.
   301 # Print a list of bloat file names.
   302 push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";";
   303 push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString substr(fileCoreName($_), -10)} @ARGV) . "];";
   304 push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString $_} @ARGV) . "];";
   305 push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString localtime fileModDate $_} @ARGV) . "];";
   307 # Print the bloat tables.
   308 push @persistentScriptData, "var totals = " . genTableRowSource('"TOTAL", undefined, ', "TOTAL") . ";";
   309 push @scriptData, "var classTables = [";
   310 delete $sizes{"TOTAL"};
   311 my @classes = sort(keys %sizes);
   312 for (my $i = 0; $i <= $#classes; $i++) {
   313 	my $c = $classes[$i];
   314 	push @scriptData, genTableRowSource(doubleQuoteString($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ? "];" : ",");
   315 }
   317 generate(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn);
   318 1;
   321 # The source of the eval'd JavaScript follows.
   322 # Comments starting with // that are alone on a line are stripped by the Perl script.
   323 __END__
   325 // showMode: 0=bytes, 1=objects, 2=references
   326 var showMode;
   327 var modeName;
   328 var modeNameUpper;
   330 var sortColumn;
   332 // Sort according to the sortColumn.  Column 0 is sorted alphabetically in ascending order.
   333 // All other columns are sorted numerically in descending order, with column 0 used for a secondary sort.
   334 // Undefined is always listed last.
   335 function sortCompare(x, y) {
   336 	if (sortColumn) {
   337 		var xc = x[sortColumn];
   338 		var yc = y[sortColumn];
   339 		if (xc < yc || xc === undefined && yc !== undefined) return 1;
   340 		if (yc < xc || yc === undefined && xc !== undefined) return -1;
   341 	}
   343 	var x0 = x[0];
   344 	var y0 = y[0];
   345 	if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1;
   346 	if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1;
   347 	return 0;
   348 }
   351 // Quote special HTML characters in the string.
   352 function quoteHTML(s) {
   353 	s = s.replace(/&/g, '&amp;');
   354 	// Can't use /</g because HTML interprets '</g' as ending the script!
   355 	s = s.replace(/\x3C/g, '&lt;');
   356 	s = s.replace(/>/g, '&gt;');
   357 	s = s.replace(/ /g, '&nbsp;');
   358 	return s;
   359 }
   362 function writeFileTable(d) {
   363 	d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
   364 	d.writeln('<TR>\n<TH>Name</TH>\n<TH>File</TH>\n<TH>Date</TH>\n</TR>');
   365 	for (var i = 0; i < nFiles; i++)
   366 		d.writeln('<TR>\n<TD>'+quoteHTML(fileTags[i])+'</TD>\n<TD><TT>'+quoteHTML(fileNames[i])+'</TT></TD>\n<TD>'+quoteHTML(fileDates[i])+'</TD>\n</TR>');
   367 	d.writeln('</TABLE>');
   368 }
   371 function writeReloadLink(d, column, s, rowspan) {
   372 	d.write(rowspan == 1 ? '<TH>' : '<TH rowspan='+rowspan+'>');
   373 	if (column != sortColumn)
   374 		d.write('<A href="javascript:reloadSelf('+column+','+showMode+')">');
   375 	d.write(s);
   376 	if (column != sortColumn)
   377 		d.write('</A>');
   378 	d.writeln('</TH>');
   379 }
   381 function writeClassTableRow(d, row, base, modeName) {
   382 	if (modeName) {
   383 		d.writeln('<TR>\n<TH>'+modeName+'</TH>');
   384 	} else {
   385 		d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>');
   386 		var v = row[1];
   387 		d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>');
   388 	}
   389 	for (var i = 0; i != 2; i++) {
   390 		var c = base + i;
   391 		for (var j = 0; j <= nFiles; j++) {
   392 			v = row[c];
   393 			var style = 'num';
   394 			if (j != nFiles)
   395 				if (v > 0) {
   396 					style = 'pos';
   397 					v = '+'+v;
   398 				} else
   399 					style = 'neg';
   400 			d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>');
   401 			c += 6;
   402 		}
   403 	}
   404 	d.writeln('</TR>');
   405 }
   407 function writeClassTable(d) {
   408 	var base = 2 + showMode*2;
   410 	// Make a copy because a sort is destructive.
   411 	var table = classTables.concat();
   412 	table.sort(sortCompare);
   414 	d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
   416 	d.writeln('<TR>');
   417 	writeReloadLink(d, 0, 'Class Name', 2);
   418 	writeReloadLink(d, 1, 'Instance<BR>Size', 2);
   419 	d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated</TH>');
   420 	d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated but not freed</TH>\n</TR>');
   421 	d.writeln('<TR>');
   422 	for (var i = 0; i != 2; i++) {
   423 		var c = base + i;
   424 		for (var j = 0; j <= nFiles; j++) {
   425 			writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1);
   426 			c += 6;
   427 		}
   428 	}
   429 	d.writeln('</TR>');
   431 	writeClassTableRow(d, totals, base, 0);
   432 	for (var r = 0; r < table.length; r++)
   433 		writeClassTableRow(d, table[r], base, 0);
   435 	d.writeln('</TABLE>');
   436 }
   439 var modeNames = ["byte", "object", "reference"];
   440 var modeNamesUpper = ["Byte", "Object", "Reference"];
   441 var styleSheet = '<STYLE type="TEXT/CSS">\n'+
   442 	'BODY {background-color: #FFFFFF; color: #000000}\n'+
   443 	'.num {text-align: right}\n'+
   444 	'.pos {text-align: right; color: #CC0000}\n'+
   445 	'.neg {text-align: right; color: #009900}\n'+
   446 	'</STYLE>';
   449 function showHead(d) {
   450 	modeName = modeNames[showMode];
   451 	modeNameUpper = modeNamesUpper[showMode];
   452 	d.writeln('<TITLE>'+modeNameUpper+' Bloats</TITLE>');
   453 	d.writeln(styleSheet);
   454 }
   456 function showBody(d) {
   457 	d.writeln('<H1>'+modeNameUpper+' Bloats</H1>');
   458 	writeFileTable(d);
   459 	d.write('<FORM>');
   460 	for (var i = 0; i != 3; i++)
   461 		if (i != showMode) {
   462 			var newSortColumn = sortColumn;
   463 			if (sortColumn >= 2)
   464 				newSortColumn = sortColumn + (i-showMode)*2;
   465 			d.write('<INPUT type="button" value="Show '+modeNamesUpper[i]+'s" onClick="reloadSelf('+newSortColumn+','+i+')">');
   466 		}
   467 	d.writeln('</FORM>');
   468 	d.writeln('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>');
   469 	d.writeln('<P>Click on a column heading to sort by that column. Click on a class name to see details for that class.</P>');
   470 	writeClassTable(d);
   471 }
   474 function showRowDetail(rowName) {
   475 	var row;
   476 	var i;
   478 	if (rowName == "TOTAL")
   479 		row = totals;
   480 	else {
   481 		for (i = 0; i < classTables.length; i++)
   482 			if (rowName == classTables[i][0]) {
   483 				row = classTables[i];
   484 				break;
   485 			}
   486 	}
   487 	if (row) {
   488 		var w = window.open("", "ClassTableRowDetails");
   489 		var d = w.document;
   490 		d.open();
   491 		d.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
   492 		d.writeln('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML(rowName)+' bloat details</TITLE>');
   493 		d.writeln(styleSheet);
   494 		d.writeln('</HEAD>\n\n<BODY>');
   495 		d.writeln('<H2>'+quoteHTML(rowName)+'</H2>');
   496 		if (row[1] !== undefined)
   497 			d.writeln('<P>Each instance has '+row[1]+' bytes.</P>');
   499 		d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
   500 		d.writeln('<TR>\n<TH></TH>\n<TH colspan='+(nFiles+1)+'>Allocated</TH>');
   501 		d.writeln('<TH colspan='+(nFiles+1)+'>Allocated but not freed</TH>\n</TR>');
   502 		d.writeln('<TR>\n<TH></TH>');
   503 		for (i = 0; i != 2; i++)
   504 			for (var j = 0; j <= nFiles; j++)
   505 				d.writeln('<TH>'+(j == nFiles ? 'Total' : quoteHTML(fileTags[j]))+'</TH>');
   506 		d.writeln('</TR>');
   508 		for (i = 0; i != 3; i++)
   509 			writeClassTableRow(d, row, 2+i*2, modeNamesUpper[i]+'s');
   511 		d.writeln('</TABLE>\n</BODY>\n</HTML>');
   512 		d.close();
   513 	}
   514 	return undefined;
   515 }
   518 function stringSource(s) {
   519 	s = s.replace(/\\/g, '\\\\');
   520 	s = s.replace(/"/g, '\\"');
   521 	s = s.replace(/<\//g, '<\\/');
   522 	return '"'+s+'"';
   523 }
   525 function reloadSelf(n,m) {
   526 	// Need to cache these because globals go away on document.open().
   527 	var sa = srcArray;
   528 	var ss = stringSource;
   529 	var ct = classTables;
   530 	var i;
   532 	document.open();
   533 	// Uncomment this and comment the document.open() line above to see the reloaded page's source.
   534 	//var w = window.open("", "NewDoc");
   535 	//var d = w.document;
   536 	//var document = new Object;
   537 	//document.write = function () {
   538 	//	for (var i = 0; i < arguments.length; i++) {
   539 	//		var s = arguments[i].toString();
   540 	//		s = s.replace(/&/g, '&amp;');
   541 	//		s = s.replace(/\x3C/g, '&lt;');
   542 	//		s = s.replace(/>/g, '&gt;');
   543 	//		s = s.replace(/ /g, '&nbsp;');
   544 	//		d.write(s);
   545 	//	}
   546 	//};
   547 	//document.writeln = function () {
   548 	//	for (var i = 0; i < arguments.length; i++) {
   549 	//		var s = arguments[i].toString();
   550 	//		s = s.replace(/&/g, '&amp;');
   551 	//		s = s.replace(/\x3C/g, '&lt;');
   552 	//		s = s.replace(/>/g, '&gt;');
   553 	//		s = s.replace(/ /g, '&nbsp;');
   554 	//		d.write(s);
   555 	//	}
   556 	//	d.writeln('<BR>');
   557 	//};
   559 	document.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
   560 	document.writeln('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">');
   562 	// Manually copy non-persistent script data
   563 	if (!ct.length)
   564 		document.writeln('var classTables = [];');
   565 	else {
   566 		document.writeln('var classTables = [');
   567 		for (i = 0; i < ct.length; i++) {
   568 			var row = ct[i];
   569 			document.write('[' + ss(row[0]));
   570 			for (var j = 1; j < row.length; j++)
   571 				document.write(',' + row[j]);
   572 			document.writeln(']' + (i == ct.length-1 ? '];' : ','));
   573 		}
   574 	}
   576 	document.writeln('var srcArray = [');
   577 	for (i = 0; i < sa.length; i++) {
   578 		document.write(ss(sa[i]));
   579 		if (i != sa.length-1)
   580 			document.writeln(',');
   581 	}
   582 	document.writeln('];');
   583 	document.writeln('eval(srcArray.join("\\n"));');
   584 	document.writeln('showMode = '+m+';');
   585 	document.writeln('sortColumn = '+n+';');
   586 	document.writeln('showHead(document);');
   587 	document.writeln('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>');
   588 	document.close();
   589 	return undefined;
   590 }

mercurial