+2012-01-26 Ryosuke Niwa <rniwa@webkit.org>
+
+ Import Chromium's dom_perf test
+ https://bugs.webkit.org/show_bug.cgi?id=77175
+
+ Reviewed by Adam Barth.
+
+ Import dom_perf.
+
+ Note resources/dom/suites.js isn't used by any html file yet but it will be used by Chromium port
+ once its perf bots start pulling test files from WebKit repository instead of Google's internal repository.
+
+ * DOM: Added.
+ * DOM/Accessors.html: Added.
+ * DOM/CloneNodes.html: Added.
+ * DOM/CreateNodes.html: Added.
+ * DOM/DOMDivWalk.html: Added.
+ * DOM/DOMTable.html: Added.
+ * DOM/DOMWalk.html: Added.
+ * DOM/Events.html: Added.
+ * DOM/GetElement.html: Added.
+ * DOM/GridSort.html: Added.
+ * DOM/Template.html: Added.
+ * DOM/resources: Added.
+ * DOM/resources/dom-perf.js: Added.
+ * DOM/resources/dom-perf: Added.
+ * DOM/resources/dom-perf/accessors.js: Added.
+ * DOM/resources/dom-perf/clonenodes.js: Added.
+ * DOM/resources/dom-perf/createnodes.js: Added.
+ * DOM/resources/dom-perf/domdivwalk.js: Added.
+ * DOM/resources/dom-perf/domtable.js: Added.
+ * DOM/resources/dom-perf/domwalk.js: Added.
+ * DOM/resources/dom-perf/events.js: Added.
+ * DOM/resources/dom-perf/getelement.js: Added.
+ * DOM/resources/dom-perf/gridsort.js: Added.
+ * DOM/resources/dom-perf/suites.js: Added.
+ * DOM/resources/dom-perf/template.js: Added.
+ * resources/runner.js:
+
2012-01-25 Ryosuke Niwa <rniwa@webkit.org>
html5-full-render.html fails due to a log
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/accessors.js"></script>
+<script> runBenchmarkSuite(AccessorsTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/clonenodes.js"></script>
+<script> runBenchmarkSuite(CloneNodesTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/createnodes.js"></script>
+<script> runBenchmarkSuite(CreateNodesTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/domdivwalk.js"></script>
+<script> runBenchmarkSuite(DOMDivWalkTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/domtable.js"></script>
+<script> runBenchmarkSuite(DOMTableTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/domwalk.js"></script>
+<script> runBenchmarkSuite(DOMWalkTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/events.js"></script>
+<script> runBenchmarkSuite(EventTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/getelement.js"></script>
+<script> runBenchmarkSuite(GetElementTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/gridsort.js"></script>
+<script> runBenchmarkSuite(GridSortTest); </script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<div id="container"><span id="benchmark_content"></span></div>
+<pre id="log"></pre>
+<script type="text/javascript" src="../resources/runner.js"></script>
+<script type="text/javascript" src="resources/dom-perf.js"></script>
+<script type="text/javascript" src="resources/dom-perf/template.js"></script>
+<script> runBenchmarkSuite(TemplateTest); </script>
+</body>
+</html>
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// A framework for running the benchmark suites and computing a score based
+// on timing measurements.
+//
+// Time measurements are computed by summing individual measurements in a
+// test's run() function. Because Javascript generally has a 1ms timer
+// granularity, tests should be careful to take at least 2ms to run. Tests
+// which run in less than 1ms are invalid. Some older browsers have less
+// timer resolution, as low as 15ms. On these systems tests which take less
+// than 15ms to run are invalid.
+//
+// Scores for a benchmark suite are calculated as the geometric mean across
+// all benchmarks which comprise that suite.
+//
+// Overall score is calculated as the geometric mean across all benchmark
+// suites.
+
+// Benchmark Object.
+// A benchmark is a test which can be run as part of a BenchmarkSuite.
+//
+// Each test provides the following properties:
+// name
+// The name of the benchmark.
+// run
+// The work function which is measured
+// opt_setup (optional)
+// A function which is run before each benchmark iteration.
+// opt_cleanup (optional)
+// A function to cleanup any side-effects of a benchmark.
+// opt_shareSetup (optional)
+// A flag indicating whether the setup function should be run once or with
+// each benchmark iteration. (default is false)
+//
+
+// Add each method to Arrays, to allow easy iteration over elements
+if(!Array.prototype.forEach) {
+ // forEach's calling syntax is:
+ // [].forEach(func, scope);
+ // registered callbacks always get 3 args:
+ // [].forEach(function(item, index, fullArray){});
+ Array.prototype.forEach = function(callback, scope) {
+ callback = hitch(scope, callback);
+ for (var i = 0, len = this.length; i < len; i++)
+ callback(this[i], i, this);
+ };
+}
+
+byId = function(id, doc) {
+ if (typeof id == "string")
+ return (doc||document).getElementById(id);
+ return id;
+};
+
+// A utility object for measuring time intervals.
+function Interval() {
+ var start_ = 0;
+ var stop_ = 0;
+ this.start = function() { start_ = new Date(); };
+ this.stop = function() { stop_ = new Date(); };
+ this.microseconds = function() { return (stop_ - start_) * 1000; };
+}
+
+// A stub profiler object.
+function PseudoProfiler() {}
+PseudoProfiler.prototype.start = function() {};
+PseudoProfiler.prototype.stop = function() {};
+
+var chromiumProfilerInitOnce = false; // Chromium profiler initialization.
+
+// Cross-platform function to get the profiler object.
+// For chromium, returns a Profiler object which can be used during the run.
+// For other browsers, returns a PseudoProfiler object.
+function GetProfiler() {
+ if (window.chromium && window.chromium.Profiler) {
+ var profiler = new window.chromium.Profiler();
+ if (!chromiumProfilerInitOnce) {
+ chromiumProfilerInitOnce = true;
+ profiler.stop();
+ if (profiler.clear)
+ profiler.clear();
+ if (profiler.setThreadName)
+ profiler.setThreadName("domperf javascript");
+ }
+ return profiler;
+ }
+ return new PseudoProfiler();
+}
+
+// To make the benchmark results predictable, we replace Math.random with a
+// 100% deterministic alternative.
+Math.random = (function() {
+ var seed = 49734321;
+ return function() {
+ // Robert Jenkins' 32 bit integer hash function.
+ seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
+ seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
+ seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
+ seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
+ seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
+ seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
+ return (seed & 0xfffffff) / 0x10000000;
+ };
+})();
+
+function Benchmark(name, run, opt_setup, opt_cleanup, opt_shareSetup) {
+ this.name = name;
+ this.timeToRun = 500; // ms
+ this.run = run;
+ this.setup = opt_setup;
+ this.cleanup = opt_cleanup;
+ this.shareSetup = opt_shareSetup;
+}
+
+// BenchmarkSuite
+// A group of benchmarks that can be run together.
+function BenchmarkSuite(name, benchmarks) {
+ this.name = name;
+ this.benchmarks = benchmarks;
+ for (var i = 0; i < this.benchmarks.length; i++)
+ this.benchmarks[i].suite = this;
+ this.suiteFile = this.currentSuiteFile;
+}
+
+// This computes the amount of overhead is associated with the call to the test
+// function and getting the date.
+BenchmarkSuite.start = new Date();
+
+BenchmarkSuite.Math = new (function() {
+ // Computes the geometric mean of a set of numbers.
+ // nulls in numbers will be ignored
+ // minNumber is optional (defaults to 0.001) anything smaller than this
+ // will be changed to this value, eliminating infinite results
+ // mapFn is an optional arg that will be used as a map function on numbers
+ this.GeometricMean = function(numbers, minNumber, mapFn) {
+ if (mapFn)
+ numbers = dojo.map(numbers, mapFn);
+ var log = 0;
+ var nNumbers = 0;
+ for (var i = 0, n = numbers.length; i < n; i++) {
+ var number = numbers[i];
+ if (number) {
+ if (number < minNumber)
+ number = minNumber;
+ nNumbers++;
+ log += Math.log(number);
+ }
+ }
+ return Math.pow(Math.E, log / nNumbers);
+ };
+
+ // Compares two objects using built-in JavaScript operators.
+ this.ascend = function(a, b) {
+ if (a < b)
+ return -1;
+ else if (a > b)
+ return 1;
+ return 0;
+ };
+});
+
+// Benchmark results hold the benchmark and the measured time used to run the
+// benchmark. The benchmark score is computed later once a full benchmark suite
+// has run to completion.
+function BenchmarkResult(benchmark, times, error, benchmarkContent) {
+ this.benchmark = benchmark;
+ this.error = error;
+ this.times = times;
+
+ this.countNodes = function(parent) {
+ var nDescendants = 0;
+ for (var child = parent.firstChild; child; child = child.nextSibling)
+ nDescendants += countNodes(child);
+ return nDescendants + 1;
+ };
+
+ if (benchmarkContent) {
+ var nNodes = countNodes(benchmarkContent) - 1;
+ if (nNodes > 0) {
+ this.html = benchmarkContent.innerHTML;
+ this.nNodes = nNodes;
+ }
+ }
+ if (!error) {
+ var data = times.slice();
+ var count = data.length;
+
+ // Sort the data so that all seemingly
+ // insignificant values such as 0.000000003 will
+ // be at the beginning of the array and their
+ // contribution to the mean and variance of the
+ // data will not be lost because of the precision
+ // of the CPU.
+ data.sort(BenchmarkSuite.Math.ascend);
+
+ // Since the data is now sorted, the minimum value
+ // is at the beginning of the array, the median
+ // value is in the middle of the array, and the
+ // maximum value is at the end of the array.
+ this.min = data[0];
+ var middle = Math.floor(data.length / 2);
+ if ((data.length % 2) !== 0)
+ this.median = data[middle];
+ else
+ this.median = (data[middle - 1] + data[middle]) / 2;
+ this.max = data[data.length - 1];
+
+ // Compute the mean and variance using a
+ // numerically stable algorithm.
+ var sqsum = 0;
+ this.mean = data[0];
+ var nZeros = 0;
+ for (var i = 1; i < data.length; ++i) {
+ var x = data[i];
+ var delta = x - this.mean;
+ var sweep = i + 1.0;
+ this.mean += delta / sweep;
+ sqsum += delta * delta * (i / sweep);
+ }
+ this.sum = this.mean * count;
+ this.variance = sqsum / count;
+
+ this.sdev = Math.sqrt(this.variance);
+ this.score = 1000 / this.mean;
+ }
+
+ this.toString = function() {
+ var s =
+ " min: " + this.min +
+ " max: " + this.max +
+ " mean: " + this.mean +
+ " median: " + this.median +
+ " sdev: " + this.sdev;
+ return s;
+ };
+
+ // Convert results to numbers. Used by the geometric mean computation.
+ this.valueOf = function() { return this.time; };
+}
+
+// Runs a single benchmark and computes the average time it takes to run a
+// single iteration.
+BenchmarkSuite.prototype.RunSingle = function(benchmark, times) {
+ var elapsed = 0;
+ var start = new Date();
+ var runInterval = new Interval();
+ var setupReturn = null;
+ var runReturn = null;
+ var time;
+ var totalTime = 0;
+ var nZeros = 0;
+ var error = null;
+ var profiler = GetProfiler();
+ for (var n = 0; !error && totalTime < benchmark.timeToRun; n++) {
+ if (this.benchmarkContent)
+ this.benchmarkContentHolder.removeChild(this.benchmarkContent);
+ this.benchmarkContent = this.benchmarkContentProto.cloneNode();
+ this.benchmarkContentHolder.appendChild(this.benchmarkContent);
+ try {
+ if (benchmark.setup) {
+ if (!setupReturn || !benchmark.shareSetup)
+ setupReturn = benchmark.setup();
+ }
+
+ profiler.start();
+ runInterval.start();
+ runReturn = benchmark.run(setupReturn);
+ runInterval.stop();
+ profiler.stop();
+ time = runInterval.microseconds() / 1000;
+ if (time > 0) {
+ times.push(time);
+ elapsed += time;
+ } else
+ times.push(0);
+ if (benchmark.cleanup)
+ benchmark.cleanup(runReturn);
+ } catch (e) {
+ error = e;
+ }
+ totalTime = new Date() - start;
+ }
+
+ var result = new BenchmarkResult(benchmark, times, error, null);
+ if (this.benchmarkContent) {
+ this.benchmarkContentHolder.removeChild(this.benchmarkContent);
+ this.benchmarkContent = null;
+ }
+ return result;
+};
+
+BenchmarkSuite.prototype.generateTree = function(parent, width, depth) {
+ var id = parent.id;
+ if (depth !== 0) {
+ var middle = Math.floor(width / 2);
+ for (var i = 0; i < width; i++) {
+ if (i == middle) {
+ var divNode = document.createElement("div");
+ // TODO:[dave] this causes us to have potentially very long
+ // ids. We might want to encode these values into a smaller string
+ divNode.id = id + ':(' + i + ', 0)';
+ parent.appendChild(divNode);
+ this.generateTree(divNode, width, depth - 1);
+ } else {
+ var p = parent;
+ for (var j = 0; j < i; j++) {
+ var divNode = document.createElement("div");
+ divNode.id = id + ':(' + i + ',' + j + ')';
+ p.appendChild(divNode);
+ p = divNode;
+ }
+ var span = document.createElement("span");
+ span.appendChild(document.createTextNode(p.id));
+ p.appendChild(span);
+ }
+ }
+ }
+};
+
+// Generates a DOM tree (doesn't insert it into the document).
+// The width and depth arguments help shape the "bushiness" of the full tree.
+// The approach is to recursively generate a set of subtrees. At each root we
+// generate width trees, of increasing depth, from 1 to width. Then we recurse
+// from the middle child of this subtree. We do this up to depth times. reps
+// allows us to duplicate a set of trees, without additional recursions.
+BenchmarkSuite.prototype.generateDOMTree = function(width, depth, reps) {
+ var top = document.createElement("div");
+
+ top.id = "test";
+ for (var i = 0; i < reps; i++) {
+ var divNode = document.createElement("div");
+ divNode.id = "test" + i;
+ this.generateTree(divNode, width, depth);
+ top.appendChild(divNode);
+ }
+ return top;
+};
+
+// Generate a small sized tree.
+// 92 span leaves, max depth: 23, avg depth: 14
+BenchmarkSuite.prototype.generateSmallTree = function() {
+ return this.generateDOMTree(14, 12, 10);
+};
+
+// Generate a medium sized tree.
+// 1320 span leaves, max depth: 27, avg depth: 16
+BenchmarkSuite.prototype.generateMediumTree = function() {
+ return this.generateDOMTree(19, 13, 9);
+};
+
+// Generate a large sized tree.
+// 2600 span leaves, max depth: 55, avg depth: 30
+BenchmarkSuite.prototype.generateLargeTree = function() {
+ return this.generateDOMTree(26, 26, 4);
+};
+
+function runBenchmarkSuite(suite) {
+ startCustom(20, function () {
+ var container = document.getElementById('container');
+ var content = document.getElementById('benchmark_content');
+ suite.benchmarkContentHolder = container;
+ suite.benchmarkContentProto = content;
+ var totalMeanTime = 0;
+ for (var j = 0; j < suite.benchmarks.length; j++) {
+ var result = suite.RunSingle(suite.benchmarks[j], []);
+ if (result.error)
+ log(result.error);
+ else
+ totalMeanTime += result.mean;
+ }
+ return totalMeanTime;
+ }, function () {
+ var container = document.getElementById('container');
+ if (container.firstChild)
+ container.removeChild(container.firstChild);
+ });
+}
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Accessors - measure access get/set properties on various objects.
+
+// Accessors are generally pretty quick. We loop on each accessor
+// many times in order to crank up the time of the microbenchmark
+// so that measurements are more substantial. Values were chosen
+// to make Firefox3 performance land in the 100-300ms range.
+var kBigCount = 1500000;
+var kLittleCount = 300000;
+var kTinyCount = 30000;
+var kVeryTinyCount = 5000;
+
+var Accessors = {};
+
+Accessors.WindowGet = function() {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ window.length;
+}
+
+Accessors.WindowSet = function() {
+ var nLoops = kBigCount;
+ for (var loop = 0; loop < kLittleCount; loop++)
+ window.name = "title";
+}
+
+Accessors.RootGet = function() {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ length;
+}
+
+Accessors.RootSet = function() {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ title = "name";
+}
+
+Accessors.DocumentGet = function() {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ document.nodeType;
+}
+
+Accessors.DocumentSet = function() {
+ var nLoops = kVeryTinyCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ document.title = "name";
+}
+
+Accessors.DOMObjectSetup = function() {
+ var o1 = document.createElement("span");
+ var o2 = document.createElement("span");
+ o1.appendChild(o2);
+ this.suite.benchmarkContent.appendChild(o1);
+ return o1;
+}
+
+Accessors.DOMObjectGet = function(o1) {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ o1.nodeType;
+}
+
+Accessors.DOMObjectSet = function(o1) {
+ var nLoops = kLittleCount;
+ var title = "title";
+ for (var loop = 0; loop < nLoops; loop++)
+ o1.title = title;
+}
+
+Accessors.ObjectGet = function(o1) {
+ var nLoops = kBigCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ o1.nodeType;
+}
+
+Accessors.NodeListSetup = function() {
+ var o1 = document.createElement("span");
+ var o2 = document.createElement("span");
+ o1.appendChild(o2);
+ this.suite.benchmarkContent.appendChild(o1);
+ return o1.childNodes;
+}
+
+Accessors.NodeListGet = function(o1) {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ o1.length;
+}
+
+Accessors.CSSSetup = function() {
+ var span = document.createElement("span");
+ span.appendChild(document.createTextNode("test"));
+ span.style.fontWeight = "bold";
+ this.suite.benchmarkContent.appendChild(span);
+ return span;
+}
+
+
+Accessors.CSSGet = function(span) {
+ var nLoops = kLittleCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ span.style.fontWeight;
+}
+
+Accessors.CSSSet = function(span) {
+ var nLoops = kTinyCount;
+ for (var loop = 0; loop < nLoops; loop++)
+ span.style.fontWeight = "bold";
+}
+
+var AccessorsTest = new BenchmarkSuite('Accessors', [
+ new Benchmark("CSS Style Get", Accessors.CSSGet, Accessors.CSSSetup),
+ new Benchmark("CSS Style Set", Accessors.CSSSet, Accessors.CSSSetup),
+ new Benchmark("Document Get NodeType", Accessors.DocumentGet),
+ new Benchmark("Document Set Title", Accessors.DocumentSet),
+ new Benchmark("Nodelist Get Length", Accessors.NodeListGet, Accessors.NodeListSetup),
+ new Benchmark("Span Get NodeType", Accessors.DOMObjectGet, Accessors.DOMObjectSetup),
+ new Benchmark("Span Set Title", Accessors.DOMObjectSet, Accessors.DOMObjectSetup),
+ new Benchmark("Root Get Length", Accessors.RootGet),
+ new Benchmark("Root Set Title", Accessors.RootSet),
+ new Benchmark("Window Get Length", Accessors.WindowGet),
+ new Benchmark("Window Set Name", Accessors.WindowSet)
+]);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Tests cloning and appending small and medium DOM trees.
+function CloneNodes(size, style) {
+ var me = this;
+ this.size = size;
+ this.style = style;
+
+ this.name = me.size + ', ' + me.style;
+
+ var nClones = 0;
+
+ this.Setup = function() {
+ // getDOMTree will initialize the global tree if necessary.
+ me.domTree = me.getDOMTree();
+ if (me.style == 'append')
+ me.domTree = me.domTree.cloneNode(true);
+ };
+
+ this.Test = function() {
+ var kIterations = 10;
+ if (me.style == 'clone') {
+ for (var iterations = 0; iterations < kIterations; iterations++)
+ me.domTree.cloneNode(true);
+ } else {
+ for (var iterations = 0; iterations < kIterations; iterations++)
+ this.suite.benchmarkContent.appendChild(me.domTree);
+ }
+ };
+
+ // Returns a tree of size me.size from the pool in the prototype, creating
+ // one if needed.
+ this.getDOMTree = function() {
+ var domTree = me.Trees[me.size];
+ if (!domTree) {
+ switch (this.size) {
+ case 'small':
+ domTree = BenchmarkSuite.prototype.generateSmallTree();
+ break;
+ case 'medium':
+ domTree = BenchmarkSuite.prototype.generateMediumTree();
+ break;
+ case 'large':
+ domTree = BenchmarkSuite.prototype.generateLargeTree();
+ break;
+ }
+ me.Trees[me.size] = domTree;
+ }
+ return domTree;
+ };
+
+ this.GetBenchmark = function() {
+ return new Benchmark(this.name, this.Test, this.Setup);
+ };
+};
+
+CloneNodes.prototype = {
+ Sizes: ['small', 'medium'],
+ Styles: ['clone', 'append'],
+ Trees: {}
+};
+
+// Generate a test for each size/style combination.
+var benchmarks = [];
+CloneNodes.prototype.Sizes.forEach(function(size) {
+ CloneNodes.prototype.Styles.forEach(function(style) {
+ benchmarks.push(new CloneNodes(size, style).GetBenchmark());
+ });
+});
+
+var CloneNodesTest = new BenchmarkSuite('CloneNodes', benchmarks);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// CreateNodes
+// Test mechanisms for creating and inserting nodes into a DOM tree.
+//
+// There are many ways to add nodes into a DOM tree. This test exercises
+// use of:
+// - createElement()/appendChild()
+// - createDocumentFragment()
+// - innerHTML()
+// - spans
+
+function CreateNodes() {}
+
+CreateNodes.nIterations = 10000;
+CreateNodes.nIterationsSlow = 2000;
+CreateNodes.currentIterations = CreateNodes.nIterations;
+CreateNodes.nodeProto = null;
+
+CreateNodes.createNode = function() {
+ if (!CreateNodes.nodeProto) {
+ var html = this.getNodeHTML();
+ var span = document.createElement("span");
+ span.innerHTML = html;
+ CreateNodes.nodeProto = span.firstChild;
+ }
+ return CreateNodes.nodeProto;
+};
+
+CreateNodes.getNodeHTML = function() {
+ return "<div style=\"font-style:bold\">test</div>";
+};
+
+CreateNodes.appendNodesWithDOM = function(node) {
+ var nodes = CreateNodes.createNodesWithDOM(node);
+ for (var i = 0; i < CreateNodes.nIterations; i++)
+ this.suite.benchmarkContent.appendChild(nodes[i]);
+ CreateNodes.forceNode(this);
+};
+
+CreateNodes.justAppendNodesWithDOM = function(nodes) {
+ for (var i = 0; i < CreateNodes.nIterations; i++)
+ this.suite.benchmarkContent.appendChild(nodes[i]);
+};
+
+CreateNodes.createNodesWithHTML = function(html) {
+ var allHTML = "";
+ for (var i = 0; i < CreateNodes.currentIterations; i++)
+ allHTML += html;
+ return allHTML;
+};
+
+CreateNodes.checkNodes = function() {
+ var length = this.suite.benchmarkContent.childNodes.length;
+ var count = CreateNodes.currentIterations;
+ if (length != count)
+ throw "Should have " + count + " nodes, have: " + length;
+};
+
+// Ensures that the node has really been created and not just delayed
+CreateNodes.forceNode = function(benchmark) {
+ var child = benchmark.suite.benchmarkContent.childNodes[CreateNodes.nIterations / 2];
+};
+
+CreateNodes.appendNodesWithHTML = function(html) {
+ var allHTML = CreateNodes.createNodesWithHTML(html);
+ this.suite.benchmarkContent.innerHTML = allHTML;
+ CreateNodes.forceNode(this);
+};
+
+CreateNodes.createNodesWithDOM = function(node) {
+ var nodes = [];
+ for (var i = 0; i < CreateNodes.nIterations; i++)
+ nodes.push(node.cloneNode(true));
+ return nodes;
+};
+
+CreateNodes.createNodesWithDOMSetup = function() {
+ return createNodesWithDOM(createNode());
+};
+
+CreateNodes.createNodeSetup = function() {
+ CreateNodes.currentIterations = CreateNodes.nIterations;
+ return CreateNodes.createNode();
+};
+
+CreateNodes.createNodesWithHTMLUsingSpans = function(html) {
+ var spans = [];
+ for (var i = 0; i < CreateNodes.currentIterations; i++) {
+ var spanNode = document.createElement("span");
+ spanNode.innerHTML = html;
+ spans.push(spanNode);
+ }
+ return spans;
+};
+
+CreateNodes.appendNodesWithHTMLUsingSpans = function(html) {
+ var spans = CreateNodes.createNodesWithHTMLUsingSpans(html);
+ for (var i = 0; i < CreateNodes.currentIterations; i++)
+ this.suite.benchmarkContent.appendChild(spans[i]);
+ CreateNodes.forceNode(this);
+};
+
+CreateNodes.appendNodesWithHTMLUsingDocumentFragments = function(html) {
+ var fragments = CreateNodes.createNodesWithHTMLUsingDocumentFragments(html);
+ for (var i = 0; i < CreateNodes.nIterations; i++)
+ this.suite.benchmarkContent.appendChild(fragments[i]);
+ CreateNodes.forceNode(this);
+};
+
+CreateNodes.createNodesWithHTMLUsingDocumentFragments = function(html) {
+ var fragments = [];
+ for (var i = 0; i < CreateNodes.nIterations; i++) {
+ var fragment = document.createDocumentFragment();
+ fragment.innerHTML = html;
+ fragments.push(fragment);
+ }
+ return fragments;
+};
+
+CreateNodes.appendNodesWithDOMUsingDocumentFragment = function(node) {
+ var fragment = CreateNodes.createNodesWithDOMUsingDocumentFragment(node);
+ this.suite.benchmarkContent.appendChild(fragment);
+ CreateNodes.forceNode(this);
+};
+
+CreateNodes.appendNodesWithDOMUsingSharedDocumentFragment = function(fragment) {
+ this.suite.benchmarkContent.appendChild(fragment.cloneNode(true));
+ CreateNodes.forceNode(this);
+};
+
+CreateNodes.createNodesWithDOMUsingDocumentFragment = function(node) {
+ var nodes = CreateNodes.createNodesWithDOM(node);
+ var fragment = document.createDocumentFragment();
+ for (var i = 0; i < CreateNodes.nIterations; i++)
+ fragment.appendChild(nodes[i]);
+ return fragment;
+};
+
+CreateNodes.createSharedDocumentFragment = function() {
+ var nodes = CreateNodes.createNodesWithDOM(CreateNodes.createNode());
+ var fragment = document.createDocumentFragment();
+ for (var i = 0; i < CreateNodes.nIterations; i++)
+ fragment.appendChild(nodes[i]);
+ return fragment;
+};
+
+CreateNodes.createHTMLSetup = function() {
+ CreateNodes.currentIterations = CreateNodes.nIterationsSlow;
+ return CreateNodes.getNodeHTML();
+};
+
+CreateNodes.createIFramesHTML = function() {
+ var html = [];
+ for (var i = 0; i < 100; i++)
+ html.push("<iframe src='blank.html'></iframe>");
+ return html.join('');
+}
+
+CreateNodes.appendIFramesHTML = function(html) {
+ this.suite.benchmarkContent.innerHTML = html;
+}
+
+CreateNodes.createIFramesDOM = function() {
+ var nodes = [];
+ for (var i = 0; i < 100; i++) {
+ var iframe = document.createElement('iframe');
+ iframe.src = 'blank.html';
+ nodes.push(iframe);
+ }
+ return nodes;
+}
+
+CreateNodes.appendIFramesDOM = function(nodes) {
+ var content = this.suite.benchmarkContent;
+ for (var i = 0, l = nodes.length; i < l; i++)
+ content.appendChild(nodes[i]);
+}
+
+var CreateNodesTest = new BenchmarkSuite('CreateNodes', [
+ new Benchmark("append, DOM, DocumentFragment",
+ CreateNodes.appendNodesWithDOMUsingDocumentFragment, CreateNodes.createNodeSetup, CreateNodes.checkNodes),
+ new Benchmark("create, DOM, DocumentFragment",
+ CreateNodes.createNodesWithDOMUsingDocumentFragment, CreateNodes.createNodeSetup),
+ new Benchmark("append, DOM, SharedDocumentFragment",
+ CreateNodes.appendNodesWithDOMUsingSharedDocumentFragment, CreateNodes.createSharedDocumentFragment, CreateNodes.checkNodes),
+ new Benchmark("create, DOM",
+ CreateNodes.createNodesWithDOM, CreateNodes.createNodeSetup),
+ new Benchmark("append, DOM",
+ CreateNodes.appendNodesWithDOM, CreateNodes.createNodeSetup, CreateNodes.checkNodes),
+ new Benchmark("append, DOM, iFrame",
+ CreateNodes.appendIFramesDOM, CreateNodes.createIFramesDOM),
+ new Benchmark("append, HTML",
+ CreateNodes.appendNodesWithHTML, CreateNodes.createHTMLSetup, CreateNodes.checkNodes),
+ new Benchmark("create, HTML, Spans",
+ CreateNodes.createNodesWithHTMLUsingSpans, CreateNodes.createHTMLSetup),
+ new Benchmark("append, HTML, Spans",
+ CreateNodes.appendNodesWithHTMLUsingSpans, CreateNodes.createHTMLSetup, CreateNodes.checkNodes),
+ new Benchmark("append, HTML, iFrame",
+ CreateNodes.appendIFramesHTML, CreateNodes.createIFramesHTML)
+]);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// DOMDivWalk
+// Tests iteration of a DOM tree which is comprised of a hierarchical
+// set of div sections.
+//
+// Two mechanisms are used to iterate the DOM tree:
+// - recursive iteration via Node.children
+// - document.getElementsByTagName
+//
+// Various tree sizes are tested:
+// - small, medium, and large DOMs
+//
+// Because this test is run repeatedly on the same DOM tree, the first
+// iteration may cause creation of the JS DOM Objects, and subsequent
+// iterations will use the cached instance of that JS Object.
+//
+
+// These tests try walking over different DOM trees full of divs
+// First create a DDWalkTest, using the above values,
+// then call GetBenchmark()
+function DDWalkTest(size, appendStyle, traverseStyle) {
+ var me = this;
+ this.size = size;
+ this.appendStyle = appendStyle;
+ this.traverseStyle = traverseStyle;
+ this.nodesFound = 0;
+
+ this.name = (this.Sizes.length > 1 ? (me.size + ", ") : "") + me.appendStyle + ", " + me.traverseStyle;
+
+ this.setupDOM = function() {
+ var domTree = me.getDOMTree().cloneNode(true);
+ this.suite.benchmarkContent.appendChild(domTree);
+ me.nodesFound = 0;
+ return domTree;
+ };
+
+ this.setupHTML = function() {
+ this.suite.benchmarkContent.innerHTML = me.getDOMTree().innerHTML;
+ me.nodesFound = 0;
+ };
+
+ if (this.appendStyle == "DOM")
+ this.Setup = this.setupDOM;
+ else
+ this.Setup = this.setupHTML;
+
+ this.recurseChildNodes = function(handle) {
+ function walkSubtree(parent, depth) {
+ if (me.nodesFound == me.maxNodesFound)
+ return;
+ else {
+ if (parent.nodeName == "SPAN" && depth > 0)
+ me.nodesFound++;
+ var children = parent.childNodes;
+ var nChildren = children.length;
+ while (nChildren-- > 0) {
+ var child = children[nChildren];
+ walkSubtree(child, depth + 1);
+ }
+ }
+ }
+ walkSubtree(this.suite.benchmarkContent, 0);
+ };
+
+ this.recurseSiblings = function(handle) {
+ function walkSubtree(parent, depth) {
+ if (me.nodesFound == me.maxNodesFound)
+ return;
+ else {
+ if (parent.nodeName == "SPAN" && depth > 0)
+ me.nodesFound++;
+ for (var child = parent.firstChild; child !== null; child = child.nextSibling)
+ walkSubtree(child, depth + 1);
+ }
+ }
+ walkSubtree(this.suite.benchmarkContent, 0);
+ };
+
+ this.getElements = function(handle) {
+ var kIterations = 10;
+
+ for (var iterations = 0; iterations < kIterations; iterations++) {
+ me.nodesFound = 0;
+ var items = this.suite.benchmarkContent.getElementsByTagName("span");
+ for (var index = 0, length = items.length; index < length; ++index) {
+ var item = items[index];
+ var nodeName = item.nodeName;
+ ++me.nodesFound;
+ if (me.nodesFound == me.maxNodesFound)
+ return;
+ }
+ }
+ };
+
+ this.Test = this[this.traverseStyle];
+
+ this.Cleanup = function(handle) {
+ var expectedCount = me.ExpectedCounts[me.size];
+ if (me.nodesFound != expectedCount)
+ throw "Wrong number of nodes found: " + me.nodesFound + " expected: " + expectedCount;
+ };
+
+ this.GetBenchmark = function() {
+ return new Benchmark(this.name, this.Test, this.Setup, this.Cleanup);
+ };
+
+ this.getDOMTree = function() {
+ var domTree = me.Trees[me.size];
+ if (!domTree) {
+ switch (me.size) {
+ case "small" : domTree = BenchmarkSuite.prototype.generateSmallTree(); break;
+ case "medium" : domTree = BenchmarkSuite.prototype.generateMediumTree(); break;
+ case "large" : domTree = BenchmarkSuite.prototype.generateLargeTree(); break;
+ }
+ me.Trees[me.size] = domTree;
+ }
+ return domTree;
+ };
+}
+
+DDWalkTest.prototype = {
+ // Different sizes are possible, but we try to keep the runtime and memory
+ // consumption reasonable.
+ Sizes: ["medium"],
+ Trees: {},
+ AppendStyles: ["DOM", "HTML"],
+ TraverseStyles: ["recurseChildNodes", "recurseSiblings", "getElements"],
+ ExpectedCounts : {"small" : 90, "medium": 2106, "large" : 2000},
+
+ // Limits the number of nodes to this number
+ // If we don't do this the tests can take too long to run
+ maxNodesFound : 10000
+};
+
+
+// Generate the matrix of all benchmarks
+var benchmarks = [];
+DDWalkTest.prototype.Sizes.forEach(function(size) {
+ DDWalkTest.prototype.AppendStyles.forEach(function(appendStyle) {
+ DDWalkTest.prototype.TraverseStyles.forEach(function(traverseStyle) {
+ benchmarks.push(new DDWalkTest(size, appendStyle, traverseStyle).GetBenchmark());
+ });
+ });
+});
+
+var DOMDivWalkTest = new BenchmarkSuite("DOMDivWalk", benchmarks);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// DOMTable - a benchmark creating tables and accessing table elements
+//
+// This benchmark tests different mechanisms for creating an HTML table. By
+// either creating the DOM elements individually or by creating an inner HTML
+// as a string. The effect of forcing a layout is also measured.
+// A second part of the benchmark sums the contents of all elements. Again
+// in one set the benchmark is created using DOM functions from JavaScript,
+// causing all nodes to be prewrapped, while in a second set the table is
+// created using inner HTML which will wrap the elements at access.
+
+// Size of the created tables.
+var DOMTable_maxRows = 100;
+var DOMTable_maxCols = 40;
+
+// Helper variable to create consistent values for the table elements.
+var DOMTable_element_count = 0;
+
+// Functions needed to create a table by creating individual DOM elements.
+function DOMTable_CreateCell(row_id, col_id) {
+ var cell = document.createElement("td");
+ cell.id = "$" + row_id + "$" + col_id;
+ cell.textContent = DOMTable_element_count++;
+ return cell;
+}
+
+function DOMTable_CreateRow(row_id, cols) {
+ var row = document.createElement("tr");
+ for (var i = 0; i < cols; i++)
+ row.appendChild(DOMTable_CreateCell(row_id, i));
+ return row;
+}
+
+function DOMTable_CreateTable(rows, cols) {
+ var table = document.createElement("table");
+ for (var i = 0; i < rows; i++)
+ table.appendChild(DOMTable_CreateRow(i, cols));
+ return table;
+}
+
+// Functions needed to create a table by creating a big piece of HTML in a
+// single string.
+function DOMTable_CreateCellIH(row_id, col_id) {
+ return '<td id="$' + row_id + '$' + col_id + '">' + DOMTable_element_count++ + '</td>';
+}
+
+function DOMTable_CreateRowIH(row_id, cols) {
+ var html_string = '<tr>';
+ for (var i = 0; i < cols; i++)
+ html_string += DOMTable_CreateCellIH(row_id, i);
+ return html_string + '</tr>';
+}
+
+function DOMTable_CreateTableIH(rows, cols) {
+ var html_string = '<table>';
+ for (var i = 0; i < rows; i++)
+ html_string += DOMTable_CreateRowIH(i, cols);
+ return html_string + '</table>';
+}
+
+
+// Shared setup function for all table creation tests.
+function DOMTable_CreateSetup() {
+ DOMTable_element_count = 0;
+ return document.getElementById("benchmark_content");
+}
+
+function DOMTable_Create(root_element) {
+ // Create the table and add it to the root_element for the benchmark.
+ root_element.appendChild(DOMTable_CreateTable(DOMTable_maxRows, DOMTable_maxCols));
+ return root_element;
+}
+
+function DOMTable_CreateLayout(root_element) {
+ // Create the table and add it to the root_element for the benchmark.
+ root_element.appendChild(DOMTable_CreateTable(DOMTable_maxRows, DOMTable_maxCols));
+ // Force a layout by requesting the height of the table. The result is
+ // going to be ignored because there is not cleanup function registered.
+ return root_element.scrollHeight;
+}
+
+function DOMTable_InnerHTML(root_element) {
+ // Create the HTML string for the table and set it at the root_element for the benchmark.
+ root_element.innerHTML = DOMTable_CreateTableIH(DOMTable_maxRows, DOMTable_maxCols);
+ return root_element;
+}
+
+function DOMTable_InnerHTMLLayout(root_element) {
+ // Create the HTML string for the table and set it at the root_element for the benchmark.
+ root_element.innerHTML = DOMTable_CreateTableIH(DOMTable_maxRows, DOMTable_maxCols);
+ // Force a layout by requesting the height of the table. The result is
+ // going to be ignored because there is not cleanup function registered.
+ return root_element.scrollHeight;
+}
+
+function DOMTableSum_Setup() {
+ // Create the table to be summed using DOM operations from JavaScript. By
+ // doing it this way the elements are all pre-wrapped.
+ DOMTable_element_count = 0;
+ var root_element = document.getElementById("benchmark_content");
+ var table = DOMTable_CreateTable(DOMTable_maxRows, DOMTable_maxCols*5);
+ root_element.appendChild(table);
+ return root_element;
+}
+
+function DOMTableSum_SetupIH() {
+ // Create the table to be summed using InnerHTML. By doing it this way the
+ // elements need to be wrapped on access.
+ DOMTable_element_count = 0;
+ var root_element = document.getElementById("benchmark_content");
+ var table = DOMTable_CreateTableIH(DOMTable_maxRows, DOMTable_maxCols*5);
+ root_element.innerHTML = table;
+ return root_element;
+}
+
+function DOMTableSum_ById(ignore) {
+ // Sum all elements in the table by finding each element by its id.
+ var sum = 0;
+ var maxRows = DOMTable_maxRows;
+ var maxCols = DOMTable_maxCols*5;
+ for (var r = 0; r < maxRows; r++) {
+ for (var c = 0; c < maxCols; c++) {
+ var cell = document.getElementById("$"+r+"$"+c);
+ sum += (+cell.textContent);
+ }
+ }
+ return sum;
+}
+
+function DOMTableSum_ByTagName(root_element) {
+ // Sum all elements in the table by getting a NodeList of all "td" elements.
+ var sum = 0;
+ var nodes = root_element.getElementsByTagName("td");
+ var length = nodes.length;
+ for (var i = 0; i < length; i++) {
+ var cell = nodes[i];
+ sum += (+cell.textContent);
+ }
+ return sum;
+}
+
+var DOMTableTest = new BenchmarkSuite('DOMTable', [
+ new Benchmark("create", DOMTable_Create, DOMTable_CreateSetup),
+ new Benchmark("create and layout", DOMTable_CreateLayout, DOMTable_CreateSetup),
+ new Benchmark("create with innerHTML", DOMTable_InnerHTML, DOMTable_CreateSetup),
+ new Benchmark("create and layout with innerHTML", DOMTable_InnerHTMLLayout, DOMTable_CreateSetup),
+ new Benchmark("sum elements by id", DOMTableSum_ById, DOMTableSum_Setup),
+ new Benchmark("sum elements by id with innerHTML", DOMTableSum_ById, DOMTableSum_SetupIH),
+ new Benchmark("sum elements by tagname", DOMTableSum_ByTagName, DOMTableSum_Setup),
+ new Benchmark("sum elements by tagname with innerHTML", DOMTableSum_ByTagName, DOMTableSum_SetupIH)
+]);
+
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// DOMWalk - a benchmark for walking the DOM
+//
+// This benchmark tests different mechanisms for touching every element
+// in a section of the DOM. The elements are created from JavaScript causing
+// them to be pre-wrapped.
+//
+// Tests small iterations and large iterations to see if there is a difference.
+
+
+// We use two mechanisms to walk the DOM, and then
+// verify that both yield the same result.
+var __num_nodes = 0;
+
+// We create a table to iterate as part of this test.
+// For now it's just a big table with lots of elements.
+function DOMWalk_CreateTable(height, width) {
+ var table = document.getElementById("fake_dom");
+ if (table) {
+ // clear out existing table
+ while (table.rows.length > 0)
+ table.deleteRow(0);
+ } else {
+ var doc = document;
+ table = doc.createElement("table");
+ table.id = "fake_dom";
+ table.border = 1;
+
+ for (var row = 0; row < height; row++) {
+ var row_object = doc.createElement("tr");
+ for (var column = 0; column < width; column++) {
+ var col_object = doc.createElement("td");
+ var text = document.createTextNode(row.toString() + "." + column.toString());
+ col_object.appendChild(text);
+ row_object.appendChild(col_object);
+ }
+ table.appendChild(row_object);
+ }
+ var content = document.getElementById("benchmark_content");
+ content.appendChild(table);
+ var html = content.innerHTML;
+ var width = content.clientWidth;
+ }
+ return table;
+}
+
+function DOMWalk_SetupSmall() {
+ // Creates a table with 100 nodes.
+ DOMWalk_CreateTable(10, 10);
+}
+
+function DOMWalk_SetupLarge() {
+ // Creates a table with 4000 nodes.
+ DOMWalk_CreateTable(200, 200);
+}
+
+// Walks the DOM via getElementsByTagName.
+function DOMWalk_ByTagName(table) {
+ var items = table.getElementsByTagName("*");
+ var item;
+ var length = items.length;
+ for (var index = 0; index < length; index++)
+ item = items[index];
+
+ // Return the number of nodes seen.
+ return items.length;
+}
+
+function DOMWalk_ByTagNameDriver(loops) {
+ var table = document.getElementById("benchmark_content");
+ for (var loop = 0; loop < loops; loop++)
+ __num_nodes = DOMWalk_ByTagName(table);
+}
+
+function DOMWalk_ByTagNameSmall() {
+ // This test runs in a short time. We loop a few times in order to avoid small time measurements.
+ DOMWalk_ByTagNameDriver(1000);
+}
+
+function DOMWalk_ByTagNameLarge() {
+ DOMWalk_ByTagNameDriver(1);
+}
+
+function DOMWalk_Recursive(node) {
+ // Count Element Nodes only.
+ // IE does not define the Node constants.
+ var count = (node.nodeType == /* Node.ELEMENT_NODE */ 1) ? 1 : 0;
+
+ var child = node.firstChild;
+ while (child !== null) {
+ count += DOMWalk_Recursive(child);
+ child = child.nextSibling;
+ }
+
+ return count;
+}
+
+// Walks the DOM via a recursive walk
+function DOMWalk_RecursiveDriver(loops) {
+ var table = document.getElementById("benchmark_content");
+ for (var loop = 0; loop < loops; loop++) {
+ var count = DOMWalk_Recursive(table) - 1; // don't count the root node.
+ if (count != __num_nodes || count === 0)
+ throw "DOMWalk failed! Expected " + __num_nodes + " nodes but found " + count + ".";
+ }
+}
+
+function DOMWalk_RecursiveSmall() {
+ // This test runs in a short time. We loop a few times in order to avoid small time measurements.
+ DOMWalk_RecursiveDriver(200);
+}
+
+function DOMWalk_RecursiveLarge() {
+ // Only iterate once over the large DOM structures.
+ DOMWalk_RecursiveDriver(1);
+}
+
+
+var DOMWalkTest = new BenchmarkSuite('DOMWalk', [
+ new Benchmark("DOMWalkByTag (100 nodes)", DOMWalk_ByTagNameSmall, DOMWalk_SetupSmall),
+ new Benchmark("DOMWalkRecursive (100 nodes)", DOMWalk_RecursiveSmall, DOMWalk_SetupSmall),
+ new Benchmark("DOMWalkByTag (4000 nodes)", DOMWalk_ByTagNameLarge, DOMWalk_SetupLarge),
+ new Benchmark("DOMWalkRecursive (4000 nodes)", DOMWalk_RecursiveLarge, DOMWalk_SetupLarge)
+]);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Events test - test the hooking and dispatch of events.
+//
+// This is a fairly simple test for measuring event peformance.
+// We create a DOM structure (a set of nested divs) to test with.
+//
+// The Hooking test measures the time to register onclick handlers for
+// each node in the structure. This simulates conditions where applications
+// register event handlers on many nodes programatically.
+//
+// The Dispatch test measures the time to dispatch events to each node
+// in the structure. In this case, we register the event handler as part
+// of the HTML for the structure, and then simply simulate onclick events
+// to each node.
+//
+// Works in IE, FF, Safari, and Chrome.
+
+var Events_counter = 0;
+function EventClickHandler() {
+ Events_counter++;
+}
+
+function EventsTest(rows, cols) {
+ var me = this;
+ this.rows = rows;
+ this.cols = cols;
+ this.cell_count = 0; // Track the number of cells created in our dom tree.
+ this.proxies = [];
+ this.random_ids = [];
+
+ // Create a DOM structure and optionally register event handlers on each node.
+ // Create the structure by setting innerHTML so that the DOM nodes are not
+ // pre-wrapped for JS access.
+ this.CreateTable = function(add_event_listeners) {
+ var html_string = '<div>';
+ for (var i = 0; i < me.rows; i++)
+ html_string += me.CreateRow(i, me.cols, add_event_listeners);
+ return html_string + '</div>';
+ };
+
+ // Returns an html string for a div with a row/column based id, with an optional onclick handler.
+ this.CreateCell = function(row_id, col_id, add_event_listeners) {
+ var str = '<div id="r' + row_id + 'c' + col_id + '"';
+ if (add_event_listeners)
+ str += ' onclick="EventClickHandler();"';
+ str += '>'+ me.cell_count++ + '</div>';
+ return str;
+ };
+
+ // Returns an html string with an outer div containing |cols| inner divs,
+ // optionally having an onclick handler.
+ this.CreateRow = function(row_id, cols, add_event_listeners) {
+ var html_string = '<div id="r' + row_id + '">';
+ for (var i = 0; i < cols; i++)
+ html_string += me.CreateCell(row_id, i, add_event_listeners);
+ return html_string + '</div>';
+ };
+
+ // Prepares for testing with elements that have no pre-defined onclick
+ // handlers.
+ this.Setup = function() {
+ me.cell_count = 0;
+ Events_counter = 0;
+ var root_element = document.getElementById("benchmark_content");
+ root_element.innerHTML = me.CreateTable(false);
+ return root_element;
+ };
+
+ // Similar to Setup, but with onclick handlers already defined in the html.
+ this.SetupWithListeners = function() {
+ me.cell_count = 0;
+ Events_counter = 0;
+ var root_element = document.getElementById("benchmark_content");
+ root_element.innerHTML = me.CreateTable(true);
+ return root_element;
+ };
+
+ // Sets up for testing performance of removing event handlers.
+ this.SetupForTeardown = function() {
+ me.random_ids = [];
+ me.SetupWithListeners();
+ var tmp = [];
+ for (var row = 0; row < me.rows; row++) {
+ for (var col = 0; col < me.cols; col++)
+ tmp.push("r" + row + "c" + col);
+ }
+ while (tmp.length > 0) {
+ var index = Math.floor(Math.random() * tmp.length);
+ me.random_ids.push(tmp.splice(index, 1));
+ }
+ };
+
+ // Tests the time it takes to go through and hook all elements in our dom.
+ this.HookTest = function() {
+ var node_count = 0;
+
+ var row_id = 0;
+ while(true) {
+ var row = document.getElementById('r' + row_id);
+ if (row == undefined)
+ break;
+
+ var col_id = 0;
+ while(true) {
+ var col = document.getElementById('r' + row_id + 'c' + col_id);
+ if (col == undefined)
+ break;
+
+ if (col.addEventListener)
+ col.addEventListener("click", EventClickHandler, false);
+ else if (col.attachEvent)
+ col.attachEvent("onclick", EventClickHandler); // To support IE
+ else
+ throw "FAILED TO ATTACH EVENTS";
+ col_id++;
+ node_count++;
+ }
+
+ row_id++;
+ }
+
+ if (node_count != me.rows * me.cols)
+ throw "ERROR - did not iterate all nodes";
+ };
+
+ // Tests the time it takes to go through and hook all elements in our dom.
+ // Creates new proxy object for each registration
+ this.HookTestProxy = function() {
+ var node_count = 0;
+
+ var row_id = 0;
+ while(true) {
+ var row = document.getElementById('r' + row_id);
+ if (row == undefined)
+ break;
+
+ var col_id = 0;
+ while(true) {
+ var col = document.getElementById('r' + row_id + 'c' + col_id);
+ if (col == undefined)
+ break;
+
+ var proxy = function() {};
+ proxy.col = col;
+ me.proxies.push(proxy);
+ if (col.addEventListener)
+ col.addEventListener("click", proxy, false);
+ else if (col.attachEvent)
+ col.attachEvent("onclick", proxy); // To support IE
+ else
+ throw "FAILED TO ATTACH EVENTS";
+ col_id++;
+ node_count++;
+ }
+
+ row_id++;
+ }
+
+ if (node_count != me.rows * me.cols)
+ throw "ERROR - did not iterate all nodes";
+ };
+
+ // Tests firing the events for each element in our dom.
+ this.DispatchTest = function() {
+ var node_count = 0;
+
+ var row_id = 0;
+ while(true) {
+ var row = document.getElementById('r' + row_id);
+ if (row == undefined)
+ break;
+
+ var col_id = 0;
+ while(true) {
+ var col = document.getElementById('r' + row_id + 'c' + col_id);
+ if (col == undefined)
+ break;
+
+ if (document.createEvent) {
+ var event = document.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ col.dispatchEvent(event);
+ } else if (col.fireEvent) {
+ var event = document.createEventObject();
+ col.fireEvent("onclick", event);
+ } else
+ throw "FAILED TO FIRE EVENTS";
+
+ col_id++;
+ node_count++;
+ }
+
+ row_id++;
+ }
+
+ if (Events_counter != me.rows * me.cols)
+ throw "ERROR - did not fire events on all nodes!" + Events_counter;
+ };
+
+ // Tests removing event handlers.
+ this.TeardownTest = function() {
+ var node_count = 0;
+ for (var i = 0; i < me.random_ids.length; i++) {
+ var col = document.getElementById(me.random_ids[i]);
+ if (col.removeEventListener)
+ col.removeEventListener("click", EventClickHandler, false);
+ else if (col.detachEvent)
+ col.detachEvent("onclick", EventClickHandler);
+ else
+ throw "FAILED TO FIRE EVENTS";
+ node_count++;
+ }
+
+ if (node_count != me.rows * me.cols)
+ throw "ERROR - did not remove listeners from all nodes! " + node_count;
+ };
+
+ // Removes event handlers and their associated proxy objects.
+ this.ProxyCleanup = function() {
+ for (var i = 0, n = me.proxies.length; i < n; i++) {
+ var proxy = me.proxies[i];
+ var col = proxy.col;
+ if (col.removeEventListener)
+ col.removeEventListener("click", proxy, false);
+ else if (col.detachEvent)
+ col.detachEvent("onclick", proxy); // To support IE
+ }
+ me.proxies = [];
+ };
+}
+
+var small_test = new EventsTest(100, 10);
+var large_test = new EventsTest(100, 50);
+var extra_large_test = new EventsTest(200, 20);
+
+var EventTest = new BenchmarkSuite('Events', [
+ new Benchmark("Event Hooking (1000 nodes)", small_test.HookTest, small_test.Setup),
+ new Benchmark("Event Dispatch (1000 nodes)", small_test.DispatchTest, small_test.SetupWithListeners),
+ new Benchmark("Event Hooking (5000 nodes)", large_test.HookTest, large_test.Setup),
+ new Benchmark("Event Hooking Proxy (4000 nodes)",
+ extra_large_test.HookTestProxy, extra_large_test.Setup, extra_large_test.ProxyCleanup),
+ new Benchmark("Event Dispatch (5000 nodes)", large_test.DispatchTest, large_test.SetupWithListeners),
+ new Benchmark("Event Teardown (5000 nodes)", large_test.TeardownTest, large_test.SetupForTeardown),
+ new Benchmark("Event Teardown (4000 nodes)", extra_large_test.TeardownTest, extra_large_test.SetupForTeardown)
+]);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Tests looking for ids in different DOM trees full of div elements.
+function GetElementTest(size, appendStyle, treeStyle) {
+ var me = this;
+ this.size = size;
+ this.appendStyle = appendStyle;
+ this.ids = null;
+ this.treeStyle = treeStyle;
+ this.name = (this.Sizes.length > 1 ? (me.size + ", ") : "") + me.appendStyle + ", " + me.treeStyle;
+
+ this.setupDOM = function() {
+ var domTree = me.getDOMTree().cloneNode(true);
+ this.suite.benchmarkContent.appendChild(domTree);
+ me.nodesFound = 0;
+ me.ids = me.getIds();
+ return domTree;
+ };
+
+ this.setupHTML = function() {
+ this.suite.benchmarkContent.innerHTML = me.getDOMTree().innerHTML;
+ me.nodesFound = 0;
+ me.ids = me.getIds();
+ };
+
+ if (this.appendStyle == "DOM")
+ this.Setup = this.setupDOM;
+ else
+ this.Setup = this.setupHTML;
+
+ this.Test = function(handle) {
+ var kIterations = 1;
+ for (var iterations = 0; iterations < kIterations; iterations++) {
+ me.nodesFound = 0;
+ for (var i = 0, len = me.ids.length; i < len; i++) {
+ var div = document.getElementById(me.ids[i]);
+ var nodeName = div.nodeName;
+ me.nodesFound++;
+ }
+ }
+ };
+
+ this.Cleanup = function(handle) {
+ var expectedCount = me.ids.length;
+ if (me.nodesFound != expectedCount)
+ throw "Wrong number of nodes found: " + me.nodesFound + " expected: " + expectedCount;
+ };
+
+ this.GetBenchmark = function() {
+ return new Benchmark(this.name, this.Test, this.Setup, this.Cleanup);
+ };
+
+ this.getIdsFromTree = function(parent, maxNumberOfNodes) {
+ var allDivs = parent.getElementsByTagName("div");
+ var len = allDivs.length;
+ var skip;
+ if (maxNumberOfNodes >= allDivs.length)
+ skip = 0;
+ else
+ skip = Math.floor(allDivs.length / maxNumberOfNodes) - 1;
+ var ids = [];
+ var l = 0;
+ for (var i = 0, len = allDivs.length; i < len && l < maxNumberOfNodes; i += (skip + 1)) {
+ var div = allDivs[i];
+ ids.push(div.id);
+ l++;
+ }
+ return ids;
+ };
+
+ this.createTreeAndIds = function() {
+ var maxNumberOfNodes = 20000;
+ var domTree;
+
+ if (me.treeStyle == "dups") {
+ // We use four of the trees for the dups style,
+ // so they get too big if you use the full size for the bigger trees
+ switch (me.size) {
+ case "small":
+ domTree = BenchmarkSuite.prototype.generateSmallTree();
+ break;
+ case "medium":
+ domTree = BenchmarkSuite.prototype.generateDOMTree(15, 12, 4);
+ break;
+ case "large":
+ domTree = BenchmarkSuite.prototype.generateDOMTree(26, 26, 1);
+ break;
+ }
+ } else {
+ switch (me.size) {
+ case "small":
+ domTree = BenchmarkSuite.prototype.generateSmallTree();
+ break;
+ case "medium":
+ domTree = BenchmarkSuite.prototype.generateMediumTree();
+ break;
+ case "large":
+ domTree = BenchmarkSuite.prototype.generateLargeTree();
+ break;
+ }
+ }
+
+ var allDivs = domTree.getElementsByTagName("*");
+ var len = allDivs.length;
+ var modBy;
+ if (maxNumberOfNodes >= allDivs.length)
+ modBy = 1;
+ else
+ modBy = Math.floor(allDivs.length / maxNumberOfNodes);
+ var ids = [];
+ for (var i = 0, len = allDivs.length; i < len; i++) {
+ var mod = i % modBy;
+ var div = allDivs[i];
+ if (mod == 0 && ids.length < maxNumberOfNodes) {
+ if (div.id && div.id != "")
+ ids.push(div.id);
+ } else if (me.treeStyle == "sparse")
+ div.id = null;
+ }
+
+ if (me.treeStyle == "dups") {
+ var newRoot = document.createElement("div");
+ for (var i = 0; i < 5; i++)
+ newRoot.appendChild(domTree.cloneNode(true));
+ domTree = newRoot;
+ }
+
+ var treeAndIds = {
+ tree: domTree,
+ ids: ids
+ };
+ return treeAndIds;
+ };
+
+ this.getTreeAndIds = function() {
+ var treeAndIdsMap = me.TreeAndIds[me.size];
+ if (!treeAndIdsMap) {
+ treeAndIdsMap = {};
+ me.TreeAndIds[me.size] = treeAndIdsMap;
+ }
+ var treeAndIds = treeAndIdsMap[me.treeStyle];
+ if (!treeAndIds) {
+ treeAndIds = me.createTreeAndIds();
+ treeAndIdsMap[me.treeStyle] = treeAndIds;
+ }
+ return treeAndIds;
+ };
+
+ this.getDOMTree = function() {
+ var treeAndIds = me.getTreeAndIds();
+ return treeAndIds.tree;
+ };
+
+ this.getIds = function() {
+ var treeAndIds = me.getTreeAndIds();
+ return treeAndIds.ids;
+ };
+}
+
+GetElementTest.prototype = {
+ // Different sizes are possible, but we try to keep the runtime and memory
+ // consumption reasonable.
+ Sizes: ["medium"],
+ TreeStyles: ["sparse", "dense", "dups"],
+ TreeAndIds: {},
+ AppendStyles: ["DOM", "HTML"]
+};
+
+// Generate the matrix of all benchmarks
+var benchmarks = [];
+GetElementTest.prototype.Sizes.forEach(function(size) {
+ GetElementTest.prototype.AppendStyles.forEach(function(appendStyle) {
+ GetElementTest.prototype.TreeStyles.forEach(function(treeStyle) {
+ benchmarks.push(new GetElementTest(size, appendStyle, treeStyle).GetBenchmark());
+ });
+ });
+});
+
+var GetElementTest = new BenchmarkSuite("Get Elements", benchmarks);
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// GridSort
+//
+// This test is designed to test the performance of sorting a grid of nodes
+// such as what you might use in a spreadsheet application.
+
+// returns an array of integers from 0 to totalCells
+function generateValuesArray(totalCells) {
+ var values = [];
+ for (var i = 0; i < totalCells; i++)
+ values[i] = i;
+ return values;
+}
+
+// creates value text nodes in a table using DOM methods
+function populateTableUsingDom(tableId, width, height) {
+ var table = document.getElementById(tableId);
+ var values = generateValuesArray(width * height);
+
+ for (var i = 0; i < height; i++) {
+ var row = table.insertRow(i);
+ for (var j = 0; j < width; j++) {
+ var cell = row.insertCell(j);
+ var valueIndex = Math.floor(Math.random() * values.length);
+ var value = values.splice(valueIndex, 1);
+ cell.appendChild(document.createTextNode(value));
+ }
+ }
+}
+
+// returns HTML string for rows/columns of table data
+function getTableContentHTML(width, height) {
+ var values = generateValuesArray(width * height);
+
+ var html = []; // fragments we will join together at the end
+ var htmlIndex = 0;
+
+ for (var i = 0; i < height; i++) {
+ html.push("<tr>");
+ for (var j = 0; j < width; j++) {
+ html.push("<td>");
+ var valueIndex = Math.floor(Math.random() * values.length);
+ var value = values.splice(valueIndex, 1);
+ html.push(value);
+ html.push("</td>");
+ }
+ html.push("</tr>");
+ }
+ return html.join("");
+}
+
+// When sorting a table by a column, we create one of these for each
+// cell in the column, and it keeps pointers to all the nodes in that
+// row. This way we can sort an array of SortHelper objects, and then
+// use the sibling node pointers to move all values in a row to their
+// proper place according to the new sort order.
+function SortHelper(row, index) {
+ this.nodes = [];
+ var numCells = row.cells.length;
+ for (var i = 0; i < numCells; i++)
+ this.nodes[i] = row.cells[i].firstChild;
+ this.originalIndex = index;
+}
+
+function compare(a, b) {
+ return a - b;
+}
+
+// sorts all rows of the table on a given column
+function sortTableOnColumn(table, columnIndex) {
+ var numRows = table.rows.length;
+ var sortHelpers = [];
+ for (var i = 0; i < numRows; i++)
+ sortHelpers.push(new SortHelper(table.rows[i], i));
+
+ // sort by nodeValue with original position breaking ties
+ sortHelpers.sort(function(a, b) {
+ var cmp = compare(Number(a.nodes[columnIndex].nodeValue), Number(b.nodes[columnIndex].nodeValue));
+ if (cmp === 0)
+ return compare(a.originalIndex, b.originalIndex);
+ return cmp;
+ });
+
+ // now place all cells in their new position
+ var numSortHelpers = sortHelpers.length;
+ for (var i = 0; i < numSortHelpers; i++) {
+ var helper = sortHelpers[i];
+ if (i == helper.originalIndex)
+ continue; // no need to move this row
+ var columnCount = table.rows[i].cells.length;
+ for (var j = 0; j < columnCount; j++) {
+ var cell = table.rows[i].cells[j];
+ if (cell.firstChild) {
+ // a SortHelper will still have a reference to this node, so it
+ // won't get orphaned/garbage collected
+ cell.removeChild(cell.firstChild);
+ }
+ cell.appendChild(helper.nodes[j]);
+ }
+ }
+}
+
+function clearExistingTable() {
+ var table = document.getElementById("gridsort_table");
+ if (table) {
+ // clear out existing table
+ table.parentNode.removeChild(table);
+ }
+}
+
+function createTableUsingDom() {
+ clearExistingTable();
+ var table = document.createElement("table");
+ table.id = "gridsort_table";
+ table.border = 1;
+ document.getElementById("benchmark_content").appendChild(table);
+ populateTableUsingDom("gridsort_table", 60, 60);
+}
+
+function createTableUsingInnerHtml() {
+ clearExistingTable();
+ var tableContent = getTableContentHTML(60, 60);
+ var html = "<table id='gridsort_table' border='1'>" + tableContent + "</table>";
+ document.getElementById("benchmark_content").innerHTML = html;
+}
+
+function sortTable() {
+ var table = document.getElementById("gridsort_table");
+ // TODO - it might be interesting to sort several (or all)
+ // columns in succession, but for now that's fairly slow
+ sortTableOnColumn(table, 0);
+}
+
+var GridSortTest = new BenchmarkSuite('GridSort', [
+ new Benchmark("SortDomTable (60x60)", sortTable, createTableUsingDom, null, false),
+ new Benchmark("SortInnerHtmlTable (60x60)", sortTable, createTableUsingInnerHtml, null, false)
+]);
--- /dev/null
+var suiteFiles = [
+ "dom/accessors.js",
+ "dom/createnodes.js",
+ "dom/domtable.js",
+ "dom/domwalk.js",
+ "dom/domdivwalk.js",
+ "dom/clonenodes.js",
+ "dom/getelement.js",
+ "dom/events.js",
+ "dom/gridsort.js",
+ "dom/template.js"
+];
--- /dev/null
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Template Test
+// This test uses a simple JS templating system for injecting JSON data into
+// an HTML page. It is designed to use the kind of DOM manipulation common
+// in templating systems.
+//
+// Code from http://code.google.com/p/google-jstemplate/source/browse/trunk/jstemplate.js
+
+// This is the HTML code to use in the template test
+var content ="\
+<style type=\"text/css\"> \
+body {\
+ border-top: 10px solid #3B85E3;\
+ color: #333;\
+ font-family: Verdana,Arial,Helvetica,sans-serif;\
+}\
+body, td {\
+ font-size: 11px;\
+}\
+a:link, a:visited {\
+ color: #2C3EBA;\
+ text-decoration: none;\
+}\
+a:hover {\
+ color: red;\
+ text-decoration: underline;\
+}\
+h1 {\
+ border-left: 10px solid #FFF;\
+ font-size: 16px;\
+ font-weight: bold;\
+ margin: 0;\
+ padding: 0.2em;\
+ color: #3B85E3;\
+}\
+h2 {\
+ border-left: 10px solid #FFF;\
+ font-size: 11px;\
+ font-weight: normal;\
+ margin: 0;\
+ padding: 0 6em 0.2em 0.2em;\
+}\
+.details {\
+ margin: 0.4em 1.9em 0 1.2em;\
+ padding: 0 0.4em 0.3em 0;\
+ white-space: nowrap;\
+}\
+.details .outer {\
+ padding-right: 0;\
+ vertical-align: top;\
+}\
+.details .top {\
+ border-top: 2px solid #333;\
+ font-weight: bold;\
+ margin-top: 0.4em;\
+}\
+.details .header2 {\
+ font-weight: bold;\
+ padding-left: 0.9em;\
+}\
+.details .key {\
+ padding-left: 1.1em;\
+ vertical-align: top;\
+}\
+.details .value {\
+ text-align: right;\
+ color: #333;\
+ font-weight: bold;\
+}\
+.details .zebra {\
+ background: #EEE;\
+}\
+.lower {\
+ text-transform: lowercase;\
+}\
+</style> \
+ <h1 class=\"lower\">About Stats</h1> \
+ <table class=\"details\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"> \
+ <tbody> \
+ <tr> \
+ <td class=\"outer\"> \
+ <table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"> \
+ <tbody> \
+ <tr> \
+ <td class=\"top\" width=\"100\">Counters</td> \
+ <td class=\"top value\" colspan=2></td> \
+ </tr> \
+ <tr> \
+ <td class=\"header2 lower\" width=\"200\">name</td> \
+ <td class=\"header2 lower\">value</td> \
+ <td class=\"header2 lower\">delta</td> \
+ </tr> \
+ <tr jsselect=\"counters\" name=\"counter\"> \
+ <td class=\"key\" width=\"200\" jscontent=\"name\"></td> \
+ <td class=\"value\" jscontent=\"value\"></td> \
+ <td class=\"value\" jscontent=\"delta\"></td> \
+ </tr> \
+ </tbody> \
+ </table> \
+ </td> \
+ <td width=\"15\"/> \
+ <td class=\"outer\"> \
+ <table cellspacing=\"0\" cellpadding=\"0\" border=\"0\"> \
+ <tbody> \
+ <tr> \
+ <td class=\"top\" width=\"100\">Timers</td> \
+ <td class=\"top value\"></td> \
+ <td class=\"top value\" colspan=3></td> \
+ </tr> \
+ <tr> \
+ <td class=\"header2 lower\" width=\"200\">name</td> \
+ <td class=\"header2 lower\">count</td> \
+ <td class=\"header2 lower\">time (ms)</td> \
+ <td class=\"header2 lower\">avg time (ms)</td> \
+ </tr> \
+ <tr jsselect=\"timers\" name=\"timer\"> \
+ <td class=\"key\" width=\"200\" jscontent=\"name\"></td> \
+ <td class=\"value\" jscontent=\"value\"></td> \
+ <td class=\"value\" jscontent=\"time\"></td> \
+ <td class=\"value\"></td> \
+ </tr> \
+ </tbody> \
+ </table> \
+ </td> \
+ </tr> \
+ </tbody> \
+ </table><br/> \
+</body> \
+</html> \
+";
+
+// Generic Template Library
+
+/**
+ * @fileoverview This file contains miscellaneous basic functionality.
+ *
+ */
+
+/**
+ * Returns the document owner of the given element. In particular,
+ * returns window.document if node is null or the browser does not
+ * support ownerDocument.
+ *
+ * @param {Node} node The node whose ownerDocument is required.
+ * @returns {Document|Null} The owner document or null if unsupported.
+ */
+function Template_ownerDocument(node) {
+ return (node ? node.ownerDocument : null) || document;
+}
+
+/**
+ * Wrapper function to create CSS units (pixels) string
+ *
+ * @param {Number} numPixels Number of pixels, may be floating point.
+ * @returns {String} Corresponding CSS units string.
+ */
+function Template_px(numPixels) {
+ return round(numPixels) + "px";
+}
+
+/**
+ * Sets display to none. Doing this as a function saves a few bytes for
+ * the 'style.display' property and the 'none' literal.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function Template_displayNone(node) {
+ node.style.display = 'none';
+}
+
+/**
+ * Sets display to default.
+ *
+ * @param {Element} node The dom element to manipulate.
+ */
+function Template_displayDefault(node) {
+ node.style.display = '';
+}
+
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+/**
+ * Get an attribute from the DOM. Simple redirect, exists to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {String} name Name of parameter to extract.
+ * @return {String} Resulting attribute.
+ */
+function Template_domGetAttribute(node, name) {
+ return node.getAttribute(name);
+}
+
+/**
+ * Set an attribute in the DOM. Simple redirect to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {String} name Name of parameter to set.
+ * @param {String} value Set attribute to this value.
+ */
+function Template_domSetAttribute(node, name, value) {
+ node.setAttribute(name, value);
+}
+
+/**
+ * Remove an attribute from the DOM. Simple redirect to compress code.
+ *
+ * @param {Element} node Element to interrogate.
+ * @param {String} name Name of parameter to remove.
+ */
+function Template_domRemoveAttribute(node, name) {
+ node.removeAttribute(name);
+}
+
+/**
+ * Clone a node in the DOM.
+ *
+ * @param {Node} node Node to clone.
+ * @return {Node} Cloned node.
+ */
+function Template_domCloneNode(node) {
+ return node.cloneNode(true);
+}
+
+/**
+ * Return a safe string for the className of a node.
+ * If className is not a string, returns "".
+ *
+ * @param {Element} node DOM element to query.
+ * @return {String}
+ */
+function Template_domClassName(node) {
+ return node.className ? "" + node.className : "";
+}
+
+/**
+ * Inserts a new child before a given sibling.
+ *
+ * @param {Node} newChild Node to insert.
+ * @param {Node} oldChild Sibling node.
+ * @return {Node} Reference to new child.
+ */
+function Template_domInsertBefore(newChild, oldChild) {
+ return oldChild.parentNode.insertBefore(newChild, oldChild);
+}
+
+/**
+ * Appends a new child to the specified (parent) node.
+ *
+ * @param {Element} node Parent element.
+ * @param {Node} child Child node to append.
+ * @return {Node} Newly appended node.
+ */
+function Template_domAppendChild(node, child) {
+ return node.appendChild(child);
+}
+
+/**
+ * Remove a new child from the specified (parent) node.
+ *
+ * @param {Element} node Parent element.
+ * @param {Node} child Child node to remove.
+ * @return {Node} Removed node.
+ */
+function Template_domRemoveChild(node, child) {
+ return node.removeChild(child);
+}
+
+/**
+ * Replaces an old child node with a new child node.
+ *
+ * @param {Node} newChild New child to append.
+ * @param {Node} oldChild Old child to remove.
+ * @return {Node} Replaced node.
+ */
+function Template_domReplaceChild(newChild, oldChild) {
+ return oldChild.parentNode.replaceChild(newChild, oldChild);
+}
+
+/**
+ * Removes a node from the DOM.
+ *
+ * @param {Node} node The node to remove.
+ * @return {Node} The removed node.
+ */
+function Template_domRemoveNode(node) {
+ return Template_domRemoveChild(node.parentNode, node);
+}
+
+/**
+ * Creates a new text node in the given document.
+ *
+ * @param {Document} doc Target document.
+ * @param {String} text Text composing new text node.
+ * @return {Text} Newly constructed text node.
+ */
+function Template_domCreateTextNode(doc, text) {
+ return doc.createTextNode(text);
+}
+
+/**
+ * Redirect to document.getElementById
+ *
+ * @param {Document} doc Target document.
+ * @param {String} id Id of requested node.
+ * @return {Element|Null} Resulting element.
+ */
+function Template_domGetElementById(doc, id) {
+ return doc.getElementById(id);
+}
+
+/**
+ * @fileoverview This file contains javascript utility functions that
+ * do not depend on anything defined elsewhere.
+ *
+ */
+
+/**
+ * Returns the value of the length property of the given object. Used
+ * to reduce compiled code size.
+ *
+ * @param {Array | String} a The string or array to interrogate.
+ * @return {Number} The value of the length property.
+ */
+function Template_jsLength(a) {
+ return a.length;
+}
+
+var min = Math.min;
+var max = Math.max;
+var ceil = Math.ceil;
+var floor = Math.floor;
+var round = Math.round;
+var abs = Math.abs;
+
+/**
+ * Copies all properties from second object to the first. Modifies to.
+ *
+ * @param {Object} to The target object.
+ * @param {Object} from The source object.
+ */
+function Template_copyProperties(to, from) {
+ foreachin(from, function(p) { to[p] = from[p]; });
+}
+
+/**
+ * Iterates over the array, calling the given function for each
+ * element.
+ *
+ * @param {Array} array
+ * @param {Function} fn
+ */
+function foreach(array, fn) {
+ var I = Template_jsLength(array);
+ for (var i = 0; i < I; ++i)
+ fn(array[i], i);
+}
+
+/**
+ * Safely iterates over all properties of the given object, calling
+ * the given function for each property. If opt_all isn't true, uses
+ * hasOwnProperty() to assure the property is on the object, not on
+ * its prototype.
+ *
+ * @param {Object} object
+ * @param {Function} fn
+ * @param {Boolean} opt_all If true, also iterates over inherited properties.
+ */
+function foreachin(object, fn, opt_all) {
+ for (var i in object) {
+ if (opt_all || !object.hasOwnProperty || object.hasOwnProperty(i))
+ fn(i, object[i]);
+ }
+}
+
+/**
+ * Trim whitespace from begin and end of string.
+ *
+ * @see testStringTrim();
+ *
+ * @param {String} str Input string.
+ * @return {String} Trimmed string.
+ */
+function Template_stringTrim(str) {
+ return Template_stringTrimRight(stringTrimLeft(str));
+}
+
+/**
+ * Trim whitespace from beginning of string.
+ *
+ * @see testStringTrimLeft();
+ *
+ * @param {String} str Input string.
+ * @return {String} Trimmed string.
+ */
+function Template_stringTrimLeft(str) {
+ return str.replace(/^\s+/, "");
+}
+
+/**
+ * Trim whitespace from end of string.
+ *
+ * @see testStringTrimRight();
+ *
+ * @param {String} str Input string.
+ * @return {String} Trimmed string.
+ */
+function Template_stringTrimRight(str) {
+ return str.replace(/\s+$/, "");
+}
+
+/**
+ * Jscompiler wrapper for parseInt() with base 10.
+ *
+ * @param {String} s String repersentation of a number.
+ *
+ * @return {Number} The integer contained in s, converted on base 10.
+ */
+function Template_parseInt10(s) {
+ return parseInt(s, 10);
+}
+/**
+ * @fileoverview A simple formatter to project JavaScript data into
+ * HTML templates. The template is edited in place. I.e. in order to
+ * instantiate a template, clone it from the DOM first, and then
+ * process the cloned template. This allows for updating of templates:
+ * If the templates is processed again, changed values are merely
+ * updated.
+ *
+ * NOTE: IE DOM doesn't have importNode().
+ *
+ * NOTE: The property name "length" must not be used in input
+ * data, see comment in jstSelect_().
+ */
+
+/**
+ * Names of jstemplate attributes. These attributes are attached to
+ * normal HTML elements and bind expression context data to the HTML
+ * fragment that is used as template.
+ */
+var ATT_select = 'jsselect';
+var ATT_instance = 'jsinstance';
+var ATT_display = 'jsdisplay';
+var ATT_values = 'jsvalues';
+var ATT_eval = 'jseval';
+var ATT_transclude = 'transclude';
+var ATT_content = 'jscontent';
+
+/**
+ * Names of special variables defined by the jstemplate evaluation
+ * context. These can be used in js expression in jstemplate
+ * attributes.
+ */
+var VAR_index = '$index';
+var VAR_this = '$this';
+
+/**
+ * Context for processing a jstemplate. The context contains a context
+ * object, whose properties can be referred to in jstemplate
+ * expressions, and it holds the locally defined variables.
+ *
+ * @param {Object} opt_data The context object. Null if no context.
+ *
+ * @param {Object} opt_parent The parent context, from which local
+ * variables are inherited. Normally the context object of the parent
+ * context is the object whose property the parent object is. Null for the
+ * context of the root object.
+ *
+ * @constructor
+ */
+function JsExprContext(opt_data, opt_parent) {
+ var me = this;
+
+ /**
+ * The local context of the input data in which the jstemplate
+ * expressions are evaluated. Notice that this is usually an Object,
+ * but it can also be a scalar value (and then still the expression
+ * $this can be used to refer to it). Notice this can be a scalar
+ * value, including undefined.
+ *
+ * @type {Object}
+ */
+ me.data_ = opt_data;
+
+ /**
+ * The context for variable definitions in which the jstemplate
+ * expressions are evaluated. Other than for the local context,
+ * which replaces the parent context, variable definitions of the
+ * parent are inherited. The special variable $this points to data_.
+ *
+ * @type {Object}
+ */
+ me.vars_ = {};
+ if (opt_parent)
+ Template_copyProperties(me.vars_, opt_parent.vars_);
+ this.vars_[VAR_this] = me.data_;
+}
+
+/**
+ * Evaluates the given expression in the context of the current
+ * context object and the current local variables.
+ *
+ * @param {String} expr A javascript expression.
+ *
+ * @param {Element} template DOM node of the template.
+ *
+ * @return The value of that expression.
+ */
+JsExprContext.prototype.jseval = function(expr, template) {
+ with (this.vars_) {
+ with (this.data_) {
+ try {
+ return (function() { return eval('[' + expr + '][0]'); }).call(template);
+ } catch (e) {
+ return null;
+ }
+ }
+ }
+};
+
+/**
+ * Clones the current context for a new context object. The cloned
+ * context has the data object as its context object and the current
+ * context as its parent context. It also sets the $index variable to
+ * the given value. This value usually is the position of the data
+ * object in a list for which a template is instantiated multiply.
+ *
+ * @param {Object} data The new context object.
+ *
+ * @param {Number} index Position of the new context when multiply
+ * instantiated. (See implementation of jstSelect().)
+ *
+ * @return {JsExprContext}
+ */
+JsExprContext.prototype.clone = function(data, index) {
+ var ret = new JsExprContext(data, this);
+ ret.setVariable(VAR_index, index);
+ if (this.resolver_)
+ ret.setSubTemplateResolver(this.resolver_);
+ return ret;
+};
+
+/**
+ * Binds a local variable to the given value. If set from jstemplate
+ * jsvalue expressions, variable names must start with $, but in the
+ * API they only have to be valid javascript identifier.
+ *
+ * @param {String} name
+ *
+ * @param {Object} value
+ */
+JsExprContext.prototype.setVariable = function(name, value) {
+ this.vars_[name] = value;
+};
+
+/**
+ * Sets the function used to resolve the values of the transclude
+ * attribute into DOM nodes. By default, this is jstGetTemplate(). The
+ * value set here is inherited by clones of this context.
+ *
+ * @param {Function} resolver The function used to resolve transclude
+ * ids into a DOM node of a subtemplate. The DOM node returned by this
+ * function will be inserted into the template instance being
+ * processed. Thus, the resolver function must instantiate the
+ * subtemplate as necessary.
+ */
+JsExprContext.prototype.setSubTemplateResolver = function(resolver) {
+ this.resolver_ = resolver;
+};
+
+/**
+ * Resolves a sub template from an id. Used to process the transclude
+ * attribute. If a resolver function was set using
+ * setSubTemplateResolver(), it will be used, otherwise
+ * jstGetTemplate().
+ *
+ * @param {String} id The id of the sub template.
+ *
+ * @return {Node} The root DOM node of the sub template, for direct
+ * insertion into the currently processed template instance.
+ */
+JsExprContext.prototype.getSubTemplate = function(id) {
+ return (this.resolver_ || jstGetTemplate).call(this, id);
+};
+
+/**
+ * HTML template processor. Data values are bound to HTML templates
+ * using the attributes transclude, jsselect, jsdisplay, jscontent,
+ * jsvalues. The template is modifed in place. The values of those
+ * attributes are JavaScript expressions that are evaluated in the
+ * context of the data object fragment.
+ *
+ * @param {JsExprContext} context Context created from the input data
+ * object.
+ *
+ * @param {Element} template DOM node of the template. This will be
+ * processed in place. After processing, it will still be a valid
+ * template that, if processed again with the same data, will remain
+ * unchanged.
+ */
+function jstProcess(context, template) {
+ var processor = new JstProcessor();
+ processor.run_([ processor, processor.jstProcess_, context, template ]);
+}
+
+/**
+ * Internal class used by jstemplates to maintain context.
+ * NOTE: This is necessary to process deep templates in Safari
+ * which has a relatively shallow stack.
+ * @class
+ */
+function JstProcessor() {
+}
+
+/**
+ * Runs the state machine, beginning with function "start".
+ *
+ * @param {Array} start The first function to run, in the form
+ * [object, method, args ...]
+ */
+JstProcessor.prototype.run_ = function(start) {
+ var me = this;
+
+ me.queue_ = [ start ];
+ while (Template_jsLength(me.queue_)) {
+ var f = me.queue_.shift();
+ f[1].apply(f[0], f.slice(2));
+ }
+};
+
+/**
+ * Appends a function to be called later.
+ * Analogous to calling that function on a subsequent line, or a subsequent
+ * iteration of a loop.
+ *
+ * @param {Array} f A function in the form [object, method, args ...]
+ */
+JstProcessor.prototype.enqueue_ = function(f) {
+ this.queue_.push(f);
+};
+
+/**
+ * Implements internals of jstProcess.
+ *
+ * @param {JsExprContext} context
+ *
+ * @param {Element} template
+ */
+JstProcessor.prototype.jstProcess_ = function(context, template) {
+ var me = this;
+
+ var transclude = Template_domGetAttribute(template, ATT_transclude);
+ if (transclude) {
+ var tr = context.getSubTemplate(transclude);
+ if (tr) {
+ Template_domReplaceChild(tr, template);
+ me.enqueue_([ me, me.jstProcess_, context, tr ]);
+ } else
+ Template_domRemoveNode(template);
+ return;
+ }
+
+ var select = Template_domGetAttribute(template, ATT_select);
+ if (select) {
+ me.jstSelect_(context, template, select);
+ return;
+ }
+
+ var display = Template_domGetAttribute(template, ATT_display);
+ if (display) {
+ if (!context.jseval(display, template)) {
+ Template_displayNone(template);
+ return;
+ }
+ Template_displayDefault(template);
+ }
+
+ var values = Template_domGetAttribute(template, ATT_values);
+ if (values)
+ me.jstValues_(context, template, values);
+
+ var expressions = Template_domGetAttribute(template, ATT_eval);
+ if (expressions) {
+ foreach(expressions.split(/\s*;\s*/), function(expression) {
+ expression = Template_stringTrim(expression);
+ if (Template_jsLength(expression))
+ context.jseval(expression, template);
+ });
+ }
+
+ var content = Template_domGetAttribute(template, ATT_content);
+ if (content)
+ me.jstContent_(context, template, content);
+ else {
+ var childnodes = [];
+ for (var i = 0; i < Template_jsLength(template.childNodes); ++i) {
+ if (template.childNodes[i].nodeType == DOM_ELEMENT_NODE)
+ me.enqueue_([me, me.jstProcess_, context, template.childNodes[i]]);
+ }
+ }
+};
+
+/**
+ * Implements the jsselect attribute: evalutes the value of the
+ * jsselect attribute in the current context, with the current
+ * variable bindings (see JsExprContext.jseval()). If the value is an
+ * array, the current template node is multiplied once for every
+ * element in the array, with the array element being the context
+ * object. If the array is empty, or the value is undefined, then the
+ * current template node is dropped. If the value is not an array,
+ * then it is just made the context object.
+ *
+ * @param {JsExprContext} context The current evaluation context.
+ *
+ * @param {Element} template The currently processed node of the template.
+ *
+ * @param {String} select The javascript expression to evaluate.
+ *
+ * @param {Function} process The function to continue processing with.
+ */
+JstProcessor.prototype.jstSelect_ = function(context, template, select) {
+ var me = this;
+
+ var value = context.jseval(select, template);
+ Template_domRemoveAttribute(template, ATT_select);
+
+ var instance = Template_domGetAttribute(template, ATT_instance);
+ var instance_last = false;
+ if (instance) {
+ if (instance.charAt(0) == '*') {
+ instance = Template_parseInt10(instance.substr(1));
+ instance_last = true;
+ } else
+ instance = Template_parseInt10(instance);
+ }
+
+ var multiple = (value !== null && typeof value == 'object' && typeof value.length == 'number');
+ var multiple_empty = (multiple && value.length == 0);
+
+ if (multiple) {
+ if (multiple_empty) {
+ if (!instance) {
+ Template_domSetAttribute(template, ATT_select, select);
+ Template_domSetAttribute(template, ATT_instance, '*0');
+ Template_displayNone(template);
+ } else
+ Template_domRemoveNode(template);
+ } else {
+ Template_displayDefault(template);
+ if (instance === null || instance === "" || instance === undefined ||
+ (instance_last && instance < Template_jsLength(value) - 1)) {
+ var templatenodes = [];
+ var instances_start = instance || 0;
+ for (var i = instances_start + 1; i < Template_jsLength(value); ++i) {
+ var node = Template_domCloneNode(template);
+ templatenodes.push(node);
+ Template_domInsertBefore(node, template);
+ }
+ templatenodes.push(template);
+
+ for (var i = 0; i < Template_jsLength(templatenodes); ++i) {
+ var ii = i + instances_start;
+ var v = value[ii];
+ var t = templatenodes[i];
+
+ me.enqueue_([me, me.jstProcess_, context.clone(v, ii), t]);
+ var instanceStr = (ii == Template_jsLength(value) - 1 ? '*' : '') + ii;
+ me.enqueue_([null, postProcessMultiple_, t, select, instanceStr]);
+ }
+ } else if (instance < Template_jsLength(value)) {
+ var v = value[instance];
+
+ me.enqueue_([me, me.jstProcess_, context.clone(v, instance), template]);
+ var instanceStr = (instance == Template_jsLength(value) - 1 ? '*' : '') + instance;
+ me.enqueue_([null, postProcessMultiple_, template, select, instanceStr]);
+ } else
+ Template_domRemoveNode(template);
+ }
+ } else {
+ if (value == null) {
+ Template_domSetAttribute(template, ATT_select, select);
+ Template_displayNone(template);
+ } else {
+ me.enqueue_([me, me.jstProcess_, context.clone(value, 0), template]);
+ me.enqueue_([null, postProcessSingle_, template, select]);
+ }
+ }
+};
+
+/**
+ * Sets ATT_select and ATT_instance following recursion to jstProcess.
+ *
+ * @param {Element} template The template
+ *
+ * @param {String} select The jsselect string
+ *
+ * @param {String} instanceStr The new value for the jsinstance attribute
+ */
+function postProcessMultiple_(template, select, instanceStr) {
+ Template_domSetAttribute(template, ATT_select, select);
+ Template_domSetAttribute(template, ATT_instance, instanceStr);
+}
+
+/**
+ * Sets ATT_select and makes the element visible following recursion to
+ * jstProcess.
+ *
+ * @param {Element} template The template
+ *
+ * @param {String} select The jsselect string
+ */
+function postProcessSingle_(template, select) {
+ Template_domSetAttribute(template, ATT_select, select);
+ Template_displayDefault(template);
+}
+
+/**
+ * Implements the jsvalues attribute: evaluates each of the values and
+ * assigns them to variables in the current context (if the name
+ * starts with '$', javascript properties of the current template node
+ * (if the name starts with '.'), or DOM attributes of the current
+ * template node (otherwise). Since DOM attribute values are always
+ * strings, the value is coerced to string in the latter case,
+ * otherwise it's the uncoerced javascript value.
+ *
+ * @param {JsExprContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {String} valuesStr Value of the jsvalues attribute to be
+ * processed.
+ */
+JstProcessor.prototype.jstValues_ = function(context, template, valuesStr) {
+ var values = valuesStr.split(/\s*;\s*/);
+ for (var i = 0; i < Template_jsLength(values); ++i) {
+ var colon = values[i].indexOf(':');
+ if (colon < 0)
+ continue;
+ var label = Template_stringTrim(values[i].substr(0, colon));
+ var value = context.jseval(values[i].substr(colon + 1), template);
+
+ if (label.charAt(0) == '$')
+ context.setVariable(label, value);
+ else if (label.charAt(0) == '.')
+ template[label.substr(1)] = value;
+ else if (label) {
+ if (typeof value == 'boolean') {
+ if (value)
+ Template_domSetAttribute(template, label, label);
+ else
+ Template_domRemoveAttribute(template, label);
+ } else
+ Template_domSetAttribute(template, label, '' + value);
+ }
+ }
+};
+
+/**
+ * Implements the jscontent attribute. Evalutes the expression in
+ * jscontent in the current context and with the current variables,
+ * and assigns its string value to the content of the current template
+ * node.
+ *
+ * @param {JsExprContext} context Current evaluation context.
+ *
+ * @param {Element} template Currently processed template node.
+ *
+ * @param {String} content Value of the jscontent attribute to be
+ * processed.
+ */
+JstProcessor.prototype.jstContent_ = function(context, template, content) {
+ var value = '' + context.jseval(content, template);
+ if (template.innerHTML == value)
+ return;
+ while (template.firstChild)
+ Template_domRemoveNode(template.firstChild);
+ var t = Template_domCreateTextNode(Template_ownerDocument(template), value);
+ Template_domAppendChild(template, t);
+};
+
+/**
+ * Helps to implement the transclude attribute, and is the initial
+ * call to get hold of a template from its ID.
+ *
+ * @param {String} name The ID of the HTML element used as template.
+ *
+ * @returns {Element} The DOM node of the template. (Only element
+ * nodes can be found by ID, hence it's a Element.)
+ */
+function jstGetTemplate(name) {
+ var section = Template_domGetElementById(document, name);
+ if (section) {
+ var ret = Template_domCloneNode(section);
+ Template_domRemoveAttribute(ret, 'id');
+ return ret;
+ } else
+ return null;
+}
+
+window['jstGetTemplate'] = jstGetTemplate;
+window['jstProcess'] = jstProcess;
+window['JsExprContext'] = JsExprContext;
+
+function TemplateTest() {
+ // Find the location to insert the content
+ var tp = document.getElementById('benchmark_content');
+
+ // Inject the content
+ tp.innerHTML = content;
+
+ // Run the template
+ var cx = new JsExprContext(
+ {"counters": [
+ {"name":"Chrome:Init","time":5},
+ {"delta":0,"name":"Shutdown:window_close:time","time":111,"value":1},
+ {"delta":0,"name":" Shutdown:window_close:timeMA","value":111},
+ {"delta":0,"name":"Shutdown:window_close:time_pe","time":111,"value":1},
+ {"delta":0,"name":" Shutdown:window_close:time_p","value":111},
+ {"delta":0,"name":"Shutdown:renderers:total","time":1,"value":1},
+ {"delta":0,"name":" Shutdown:renderers:totalMAX","value":1},
+ {"delta":0,"name":"Shutdown:renderers:slow","time":0,"value":1},
+ {"delta":0,"name":" Shutdown:renderers:slowMAX","value":10},
+ {"delta":0,"name":"DNS:PrefetchQueue","time":2,"value":6},
+ {"delta":0,"name":" DNS:PrefetchQueueMAX","value":1},
+ {"delta":0,"name":"DNS:PrefetchFoundNameL","time":1048,"value":1003},
+ {"delta":0,"name":" DNS:PrefetchFoundNameLMAX","value":46},
+ {"delta":0,"name":"SB:QueueDepth","time":0,"value":1},
+ {"delta":0,"name":" SB:QueueDepthMAX","value":0},
+ {"delta":102,"name":"IPC:SendMsgCount","value":1016},
+ {"delta":98,"name":"Chrome:ProcMsgL UI","time":2777,"value":1378},
+ {"delta":2381,"name":" Chrome:ProcMsgL UIMAX","value":2409},
+ {"delta":98,"name":"Chrome:TotalMsgL UI","time":5715,"value":1378},
+ {"delta":1518,"name":" Chrome:TotalMsgL UIMAX","value":2409},
+ {"name":"Chrome:RendererInit","time":9},
+ {"delta":0,"name":"WebFrameActiveCount","value":2},
+ {"delta":0,"name":"Gears:LoadTime","time":1,"value":1},
+ {"delta":0,"name":" Gears:LoadTimeMAX","value":1},
+ {"delta":1,"name":"URLRequestCount","value":41},
+ {"delta":1,"name":"mime_sniffer:ShouldSniffMimeT","time":27,"value":27},
+ {"delta":0,"name":" mime_sniffer:ShouldSniffMime","value":1},
+ {"delta":3,"name":"ResourceLoadServer","time":1065,"value":73},
+ {"delta":0,"name":" ResourceLoadServerMAX","value":51},
+ {"delta":11,"name":"WebFramePaintTime","time":232,"value":42},
+ {"delta":9,"name":" WebFramePaintTimeMAX","value":41},
+ {"delta":11,"name":"MPArch:RWH_OnMsgPaintRect","time":136,"value":42},
+ {"delta":0,"name":" MPArch:RWH_OnMsgPaintRectMAX","value":9},
+ {"delta":0,"name":"NPObjects","value":2},
+ {"delta":6008832,"name":"V8:OsMemoryAllocated","value":28422144},
+ {"delta":7905,"name":"V8:GlobalHandles","value":16832},
+ {"delta":0,"name":"V8:PcreMallocCount","value":0},
+ {"delta":1,"name":"V8:ObjectPropertiesToDictiona","value":16},
+ {"delta":0,"name":"V8:ObjectElementsToDictionary","value":0},
+ {"delta":1128652,"name":"V8:AliveAfterLastGC","value":4467596},
+ {"delta":0,"name":"V8:ObjsSinceLastYoung","value":0},
+ {"delta":0,"name":"V8:ObjsSinceLastFull","value":0},
+ {"delta":2048,"name":"V8:SymbolTableCapacity","value":12288},
+ {"delta":1493,"name":"V8:NumberOfSymbols","value":6865},
+ {"delta":100442,"name":"V8:TotalExternalStringMemory","value":359184},
+ {"delta":0,"name":"V8:ScriptWrappers","value":0},
+ {"delta":3,"name":"V8:CallInitializeStubs","value":20},
+ {"delta":0,"name":"V8:CallPreMonomorphicStubs","value":4},
+ {"delta":0,"name":"V8:CallNormalStubs","value":0},
+ {"delta":6,"name":"V8:CallMegamorphicStubs","value":44},
+ {"delta":0,"name":"V8:ArgumentsAdaptors","value":0},
+ {"delta":647,"name":"V8:CompilationCacheHits","value":1269},
+ {"delta":9,"name":"V8:CompilationCacheMisses","value":57},
+ {"delta":0,"name":"V8:RegExpCacheHits","value":2},
+ {"delta":0,"name":"V8:RegExpCacheMisses","value":6},
+ {"delta":6260,"name":"V8:TotalEvalSize","value":12621},
+ {"delta":50221,"name":"V8:TotalLoadSize","value":217362},
+ {"delta":63734,"name":"V8:TotalParseSize","value":299135},
+ {"delta":15174,"name":"V8:TotalPreparseSkipped","value":61824},
+ {"delta":69932,"name":"V8:TotalCompileSize","value":313048},
+ {"delta":22,"name":"V8:CodeStubs","value":117},
+ {"delta":1185,"name":"V8:TotalStubsCodeSize","value":6456},
+ {"delta":45987,"name":"V8:TotalCompiledCodeSize","value":169546},
+ {"delta":0,"name":"V8:GCCompactorCausedByRequest","value":0},
+ {"delta":0,"name":"V8:GCCompactorCausedByPromote","value":0},
+ {"delta":0,"name":"V8:GCCompactorCausedByOldspac","value":0},
+ {"delta":0,"name":"V8:GCCompactorCausedByWeakHan","value":0},
+ {"delta":0,"name":"V8:GCLastResortFromJS","value":0},
+ {"delta":0,"name":"V8:GCLastResortFromHandles","value":0},
+ {"delta":0,"name":"V8:KeyedLoadGenericSmi","value":0},
+ {"delta":0,"name":"V8:KeyedLoadGenericSymbol","value":0},
+ {"delta":0,"name":"V8:KeyedLoadGenericSlow","value":0},
+ {"delta":0,"name":"V8:KeyedLoadFunctionPrototype","value":0},
+ {"delta":0,"name":"V8:KeyedLoadStringLength","value":0},
+ {"delta":0,"name":"V8:KeyedLoadArrayLength","value":0},
+ {"delta":0,"name":"V8:KeyedLoadConstantFunction","value":0},
+ {"delta":0,"name":"V8:KeyedLoadField","value":0},
+ {"delta":0,"name":"V8:KeyedLoadCallback","value":0},
+ {"delta":0,"name":"V8:KeyedLoadInterceptor","value":0},
+ {"delta":0,"name":"V8:KeyedStoreField","value":0},
+ {"delta":0,"name":"V8:ForIn","value":0},
+ {"delta":2,"name":"V8:EnumCacheHits","value":9},
+ {"delta":4,"name":"V8:EnumCacheMisses","value":23},
+ {"delta":3724,"name":"V8:RelocInfoCount","value":18374},
+ {"delta":6080,"name":"V8:RelocInfoSize","value":30287},
+ {"delta":0,"name":"History:InitTime","time":12,"value":1},
+ {"delta":0,"name":" History:InitTimeMAX","value":12},
+ {"delta":1,"name":"History:GetFavIconForURL","time":0,"value":22},
+ {"delta":0,"name":" History:GetFavIconForURLMAX","value":0},
+ {"delta":2,"name":"V8:PreParse","time":9,"value":11},
+ {"delta":9,"name":"V8:Parse","time":9,"value":57},
+ {"delta":3,"name":"V8:Compile","time":3,"value":22},
+ {"delta":49,"name":"V8:ParseLazy","time":17,"value":231},
+ {"delta":47,"name":"V8:CompileLazy","time":3,"value":221},
+ {"delta":12,"name":"V8:GCScavenger","time":13,"value":28},
+ {"delta":0,"name":"NewTabPage:SearchURLs:Total","time":0,"value":1},
+ {"delta":0,"name":" NewTabPage:SearchURLs:TotalM","value":0},
+ {"delta":6,"name":"V8:CompileEval","time":1,"value":35},
+ {"delta":0,"name":"Memory:CachedFontAndDC","time":3,"value":3},
+ {"delta":0,"name":" Memory:CachedFontAndDCMAX","value":2},
+ {"delta":0,"name":"ResourceLoaderWait","time":1296,"value":48},
+ {"delta":0,"name":" ResourceLoaderWaitMAX","value":55},
+ {"delta":0,"name":"History:GetPageThumbnail","time":15,"value":9},
+ {"delta":0,"name":" History:GetPageThumbnailMAX","value":10},
+ {"delta":9,"name":"MPArch:RWH_InputEventDelta","time":327,"value":170},
+ {"delta":0,"name":" MPArch:RWH_InputEventDeltaMA","value":154},
+ {"delta":0,"name":"Omnibox:QueryBookmarksTime","time":2,"value":44},
+ {"delta":0,"name":" Omnibox:QueryBookmarksTimeMA","value":1},
+ {"delta":0,"name":"Chrome:DelayMsgUI","value":3},
+ {"delta":0,"name":"Autocomplete:HistoryAsyncQuer","time":351,"value":86},
+ {"delta":0,"name":" Autocomplete:HistoryAsyncQue","value":10},
+ {"delta":0,"name":"History:QueryHistory","time":1018,"value":44},
+ {"delta":0,"name":" History:QueryHistoryMAX","value":233},
+ {"delta":0,"name":"DiskCache:GetFileForNewBlock","time":0,"value":34},
+ {"delta":0,"name":" DiskCache:GetFileForNewBlock","value":0},
+ {"delta":0,"name":"DiskCache:CreateBlock","time":0,"value":34},
+ {"delta":0,"name":" DiskCache:CreateBlockMAX","value":0},
+ {"delta":0,"name":"DiskCache:CreateTime","time":0,"value":10},
+ {"delta":0,"name":" DiskCache:CreateTimeMAX","value":0},
+ {"delta":0,"name":"DNS:PrefetchPositiveHitL","time":1048,"value":2},
+ {"delta":0,"name":" DNS:PrefetchPositiveHitLMAX","value":1002},
+ {"delta":0,"name":"DiskCache:GetRankings","time":0,"value":27},
+ {"delta":0,"name":" DiskCache:GetRankingsMAX","value":0},
+ {"delta":0,"name":"DiskCache:DeleteHeader","time":0,"value":3},
+ {"delta":0,"name":" DiskCache:DeleteHeaderMAX","value":0},
+ {"delta":0,"name":"DiskCache:DeleteData","time":0,"value":3},
+ {"delta":0,"name":" DiskCache:DeleteDataMAX","value":0},
+ {"delta":0,"name":"DiskCache:DeleteBlock","time":0,"value":6},
+ {"delta":0,"name":" DiskCache:DeleteBlockMAX","value":0},
+ {"delta":0,"name":"SessionRestore:last_session_f","time":0,"value":1},
+ {"delta":0,"name":" SessionRestore:last_session_","value":0},
+ {"delta":3,"name":"SessionRestore:command_size","time":2940,"value":36},
+ {"delta":0,"name":" SessionRestore:command_sizeM","value":277},
+ {"delta":0,"name":"DNS:IndependentNavigation","time":2,"value":4},
+ {"delta":0,"name":" DNS:IndependentNavigationMAX","value":1},
+ {"delta":0,"name":"DiskCache:UpdateRank","time":1,"value":25},
+ {"delta":0,"name":" DiskCache:UpdateRankMAX","value":1},
+ {"delta":0,"name":"DiskCache:WriteTime","time":1,"value":21},
+ {"delta":0,"name":" DiskCache:WriteTimeMAX","value":1},
+ {"delta":0,"name":"Net:Transaction_Latency","time":183,"value":7},
+ {"delta":0,"name":" Net:Transaction_LatencyMAX","value":37},
+ {"delta":0,"name":"Net:Transaction_Bandwidth","time":40,"value":7},
+ {"delta":0,"name":" Net:Transaction_BandwidthMAX","value":8},
+ {"delta":0,"name":"NewTabUI load","time":564,"value":1},
+ {"delta":0,"name":" NewTabUI loadMAX","value":564},
+ {"delta":0,"name":"DiskCache:OpenTime","time":0,"value":2},
+ {"delta":0,"name":" DiskCache:OpenTimeMAX","value":0},
+ {"delta":0,"name":"DiskCache:ReadTime","time":0,"value":4},
+ {"delta":0,"name":" DiskCache:ReadTimeMAX","value":0},
+ {"delta":0,"name":"MPArch:RWHH_WhiteoutDuration_","time":27,"value":1},
+ {"delta":0,"name":" MPArch:RWHH_WhiteoutDuration","value":27},
+ {"delta":1,"name":"AsyncIO:IPCChannelClose","time":0,"value":4},
+ {"delta":0,"name":" AsyncIO:IPCChannelCloseMAX","value":0},
+ {"name":"GetHistoryTimer","time":0},
+ {"delta":0,"name":"DiskCache:Entries","time":7,"value":1},
+ {"delta":0,"name":" DiskCache:EntriesMAX","value":7},
+ {"delta":0,"name":"DiskCache:Size","time":0,"value":1},
+ {"delta":0,"name":" DiskCache:SizeMAX","value":0},
+ {"delta":0,"name":"DiskCache:MaxSize","time":80,"value":1},
+ {"delta":0,"name":" DiskCache:MaxSizeMAX","value":80},
+ {"delta":0,"name":"History:AddFTSData","time":1,"value":1},
+ {"delta":0,"name":" History:AddFTSDataMAX","value":1},
+ {"delta":0,"name":"Chrome:SlowMsgUI","value":1}
+ ],
+ "timers":[
+ {"name":"Chrome:Init","time":5},
+ {"delta":0,"name":"Shutdown:window_close:time","time":111,"value":1},
+ {"delta":0,"name":"Shutdown:window_close:time_pe","time":111,"value":1},
+ {"delta":0,"name":"Shutdown:renderers:total","time":1,"value":1},
+ {"delta":0,"name":"Shutdown:renderers:slow","time":0,"value":1},
+ {"delta":0,"name":"DNS:PrefetchQueue","time":2,"value":6},
+ {"delta":0,"name":"DNS:PrefetchFoundNameL","time":1048,"value":1003},
+ {"delta":0,"name":"SB:QueueDepth","time":0,"value":1},
+ {"delta":98,"name":"Chrome:ProcMsgL UI","time":2777,"value":1378},
+ {"delta":98,"name":"Chrome:TotalMsgL UI","time":5715,"value":1378},
+ {"name":"Chrome:RendererInit","time":9},
+ {"delta":0,"name":"Gears:LoadTime","time":1,"value":1},
+ {"delta":1,"name":"mime_sniffer:ShouldSniffMimeT","time":27,"value":27},
+ {"delta":3,"name":"ResourceLoadServer","time":1065,"value":73},
+ {"delta":11,"name":"WebFramePaintTime","time":232,"value":42},
+ {"delta":11,"name":"MPArch:RWH_OnMsgPaintRect","time":136,"value":42},
+ {"delta":0,"name":"History:InitTime","time":12,"value":1},
+ {"delta":1,"name":"History:GetFavIconForURL","time":0,"value":22},
+ {"delta":2,"name":"V8:PreParse","time":9,"value":11},
+ {"delta":9,"name":"V8:Parse","time":9,"value":57},
+ {"delta":3,"name":"V8:Compile","time":3,"value":22},
+ {"delta":49,"name":"V8:ParseLazy","time":17,"value":231},
+ {"delta":47,"name":"V8:CompileLazy","time":3,"value":221},
+ {"delta":12,"name":"V8:GCScavenger","time":13,"value":28},
+ {"delta":0,"name":"NewTabPage:SearchURLs:Total","time":0,"value":1},
+ {"delta":6,"name":"V8:CompileEval","time":1,"value":35},
+ {"delta":0,"name":"Memory:CachedFontAndDC","time":3,"value":3},
+ {"delta":0,"name":"ResourceLoaderWait","time":1296,"value":48},
+ {"delta":0,"name":"History:GetPageThumbnail","time":15,"value":9},
+ {"delta":9,"name":"MPArch:RWH_InputEventDelta","time":327,"value":170},
+ {"delta":0,"name":"Omnibox:QueryBookmarksTime","time":2,"value":44},
+ {"delta":0,"name":"Autocomplete:HistoryAsyncQuer","time":351,"value":86},
+ {"delta":0,"name":"History:QueryHistory","time":1018,"value":44},
+ {"delta":0,"name":"DiskCache:GetFileForNewBlock","time":0,"value":34},
+ {"delta":0,"name":"DiskCache:CreateBlock","time":0,"value":34},
+ {"delta":0,"name":"DiskCache:CreateTime","time":0,"value":10},
+ {"delta":0,"name":"DNS:PrefetchPositiveHitL","time":1048,"value":2},
+ {"delta":0,"name":"DiskCache:GetRankings","time":0,"value":27},
+ {"delta":0,"name":"DiskCache:DeleteHeader","time":0,"value":3},
+ {"delta":0,"name":"DiskCache:DeleteData","time":0,"value":3},
+ {"delta":0,"name":"DiskCache:DeleteBlock","time":0,"value":6},
+ {"delta":0,"name":"SessionRestore:last_session_f","time":0,"value":1},
+ {"delta":3,"name":"SessionRestore:command_size","time":2940,"value":36},
+ {"delta":0,"name":"DNS:IndependentNavigation","time":2,"value":4},
+ {"delta":0,"name":"DiskCache:UpdateRank","time":1,"value":25},
+ {"delta":0,"name":"DiskCache:WriteTime","time":1,"value":21},
+ {"delta":0,"name":"Net:Transaction_Latency","time":183,"value":7},
+ {"delta":0,"name":"Net:Transaction_Bandwidth","time":40,"value":7},
+ {"delta":0,"name":"NewTabUI load","time":564,"value":1},
+ {"delta":0,"name":"DiskCache:OpenTime","time":0,"value":2},
+ {"delta":0,"name":"DiskCache:ReadTime","time":0,"value":4},
+ {"delta":0,"name":"MPArch:RWHH_WhiteoutDuration_","time":27,"value":1},
+ {"delta":1,"name":"AsyncIO:IPCChannelClose","time":0,"value":4},
+ {"name":"GetHistoryTimer","time":0},
+ {"delta":0,"name":"DiskCache:Entries","time":7,"value":1},
+ {"delta":0,"name":"DiskCache:Size","time":0,"value":1},
+ {"delta":0,"name":"DiskCache:MaxSize","time":80,"value":1},
+ {"delta":0,"name":"History:AddFTSData","time":1,"value":1}
+ ]});
+ jstProcess(cx, tp);
+}
+
+var TemplateTest = new BenchmarkSuite('Template', [new Benchmark("Template",TemplateTest)]);
}
function run() {
- var start = new Date();
- for (var i = 0; i < window.loopsPerRun; ++i)
- window.runFunction();
- var time = new Date() - start;
+ if (window.customRunFunction)
+ var time = window.customRunFunction();
+ else {
+ var start = new Date();
+ for (var i = 0; i < window.loopsPerRun; ++i)
+ window.runFunction();
+ var time = new Date() - start;
+ }
+
window.completedRuns++;
if (window.completedRuns <= 0) {
log("Ignoring warm-up run (" + time + ")");
runLoop();
}
+function startCustom(runCount, customRunFunction, doneFunction) {
+ window.runCount = runCount;
+ window.customRunFunction = customRunFunction;
+ window.loopsPerRun = 1;
+ window.doneFunction = doneFunction || function() {};
+
+ log("Running " + runCount + " times");
+ runLoop();
+}
+
if (window.layoutTestController) {
layoutTestController.waitUntilDone();
layoutTestController.dumpAsText();