blob: 1b9ae480317be79b966aec209cecc51aa847ee21 [file] [log] [blame]
// FIXME: Use the real promise if available.
// FIXME: Make sure this interface is compatible with the real Promise.
function SimplePromise() {
this._chainedPromise = null;
this._callback = null;
}
SimplePromise.prototype.then = function (callback) {
if (this._callback)
throw "SimplePromise doesn't support multiple calls to then";
this._callback = callback;
this._chainedPromise = new SimplePromise;
if (this._resolved)
this.resolve(this._resolvedValue);
return this._chainedPromise;
}
SimplePromise.prototype.resolve = function (value) {
if (!this._callback) {
this._resolved = true;
this._resolvedValue = value;
return;
}
var result = this._callback(value);
if (result instanceof SimplePromise) {
var chainedPromise = this._chainedPromise;
result.then(function (result) { chainedPromise.resolve(result); });
} else
this._chainedPromise.resolve(result);
}
var BenchmarkRunner = {_suites: [], _prepareReturnValue: null, _measuredValues: {}};
BenchmarkRunner.suite = function (suite) {
var self = BenchmarkRunner;
self._suites.push(suite);
}
BenchmarkRunner.waitForElement = function (selector) {
var self = BenchmarkRunner;
var promise = new SimplePromise;
var contentDocument = self._frame.contentDocument;
function resolveIfReady() {
var element = contentDocument.querySelector(selector);
if (element)
return promise.resolve(element);
setTimeout(resolveIfReady, 50);
}
resolveIfReady();
return promise;
}
BenchmarkRunner._removeFrame = function () {
var self = BenchmarkRunner;
if (self._frame) {
self._frame.parentNode.removeChild(self._frame);
self._frame = null;
}
}
BenchmarkRunner._appendFrame = function (src) {
var self = BenchmarkRunner;
var frame = document.createElement('iframe');
document.body.appendChild(frame);
self._frame = frame;
return frame;
}
BenchmarkRunner._waitAndWarmUp = function () {
var startTime = Date.now();
function Fibonacci(n) {
if (Date.now() - startTime > 100)
return;
if (n <= 0)
return 0;
else if (n == 1)
return 1;
return Fibonacci(n - 2) + Fibonacci(n - 1);
}
var promise = new SimplePromise;
setTimeout(function () {
Fibonacci(100);
promise.resolve();
}, 200);
return promise;
}
// This function ought be as simple as possible. Don't even use SimplePromise.
BenchmarkRunner._runTest = function(suite, testFunction, prepareReturnValue, callback)
{
var self = BenchmarkRunner;
var now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
var contentWindow = self._frame.contentWindow;
var contentDocument = self._frame.contentDocument;
var startTime = now();
testFunction(prepareReturnValue, contentWindow, contentDocument);
var endTime = now();
var syncTime = endTime - startTime;
var startTime = now();
setTimeout(function () {
var endTime = now();
callback(syncTime, endTime - startTime);
}, 0);
}
BenchmarkRunner._testName = function (suite, testName, metric) {
if (!testName)
return suite.name;
return suite.name + '/' + testName + (metric ? '/' + metric : '');
}
BenchmarkRunner._testItemId = function (suite, testName) {
return suite.name + '-' + testName;
}
BenchmarkRunner.listSuites = function () {
var self = BenchmarkRunner;
var control = document.createElement('nav');
var suites = self._suites;
var ol = document.createElement('ol');
var checkboxes = [];
for (var suiteIndex = 0; suiteIndex < suites.length; suiteIndex++) {
var suite = suites[suiteIndex];
var li = document.createElement('li');
var checkbox = document.createElement('input');
checkbox.id = suite.name;
checkbox.type = 'checkbox';
checkbox.checked = true;
checkboxes.push(checkbox);
li.appendChild(checkbox);
var label = document.createElement('label');
label.appendChild(document.createTextNode(self._testName(suite)));
li.appendChild(label);
label.htmlFor = checkbox.id;
var testList = document.createElement('ol');
for (var testIndex = 0; testIndex < suite.tests.length; testIndex++) {
var testItem = document.createElement('li');
var test = suite.tests[testIndex];
var anchor = document.createElement('a');
anchor.id = self._testItemId(suite, test[0]);
anchor.appendChild(document.createTextNode(self._testName(suite, test[0])));
testItem.appendChild(anchor);
testList.appendChild(testItem);
}
li.appendChild(testList);
ol.appendChild(li);
}
control.appendChild(ol);
var currentState = null;
// Don't call step while step is already executing.
var button = document.createElement('button');
button.textContent = 'Step';
button.onclick = function () {
self.step(currentState).then(function (state) { currentState = state; });
}
control.appendChild(button);
function callNextStep(state) {
self.step(state).then(function (newState) {
currentState = newState;
if (newState)
callNextStep(newState);
});
}
var button = document.createElement('button');
button.textContent = 'Run';
button.onclick = function () { callNextStep(currentState); }
control.appendChild(button);
document.body.appendChild(control);
}
function BenchmarkState(suites) {
this._suites = suites;
this._suiteIndex = -1;
this._testIndex = 0;
this.next();
}
BenchmarkState.prototype.currentSuite = function() {
return this._suites[this._suiteIndex];
}
BenchmarkState.prototype.currentTest = function () {
var suite = this.currentSuite();
return suite ? suite.tests[this._testIndex] : null;
}
BenchmarkState.prototype.next = function () {
this._testIndex++;
var suite = this._suites[this._suiteIndex];
if (suite && this._testIndex < suite.tests.length)
return this;
this._testIndex = 0;
do {
this._suiteIndex++;
} while (this._suiteIndex < this._suites.length && !document.getElementById(this._suites[this._suiteIndex].name).checked);
return this;
}
BenchmarkState.prototype.isFirstTest = function () {
return !this._testIndex;
}
BenchmarkState.prototype.prepareCurrentSuite = function (frame) {
var self = this;
var suite = this.currentSuite();
var promise = new SimplePromise;
frame.onload = function () {
suite.prepare(frame.contentWindow, frame.contentDocument).then(function (result) { promise.resolve(result); });
}
frame.src = suite.url;
return promise;
}
BenchmarkRunner.step = function (state) {
var self = BenchmarkRunner;
if (!state)
state = new BenchmarkState(self._suites);
var suite = state.currentSuite();
if (!suite) {
self._finalize();
var promise = new SimplePromise;
promise.resolve();
return promise;
}
if (state.isFirstTest()) {
self._masuredValuesForCurrentSuite = {};
return state.prepareCurrentSuite(self._appendFrame()).then(function (prepareReturnValue) {
self._prepareReturnValue = prepareReturnValue;
return self._runTestAndRecordResults(state);
});
}
return self._runTestAndRecordResults(state);
}
BenchmarkRunner._runTestAndRecordResults = function (state) {
var self = BenchmarkRunner;
var promise = new SimplePromise;
var suite = state.currentSuite();
var test = state.currentTest();
var testName = test[0];
var testItem = document.getElementById(self._testItemId(suite, testName));
testItem.classList.add('running');
setTimeout(function () {
self._runTest(suite, test[1], self._prepareReturnValue, function (syncTime, asyncTime) {
self._masuredValuesForCurrentSuite[self._testName(suite, testName, 'Sync')] = syncTime;
self._masuredValuesForCurrentSuite[self._testName(suite, testName, 'Async')] = asyncTime;
testItem.classList.remove('running');
testItem.classList.add('ran');
state.next();
if (state.currentSuite() != suite) {
var total = 0;
for (var title in self._masuredValuesForCurrentSuite) {
var value = self._masuredValuesForCurrentSuite[title];
total += value;
self._measuredValues[title] = value;
}
self._measuredValues[self._testName(suite)] = total;
self._removeFrame();
}
promise.resolve(state);
});
}, 0);
return promise;
}
BenchmarkRunner._finalize = function () {
var self = BenchmarkRunner;
var results = '';
var total = 0; // FIXME: Compute the total properly.
for (var title in self._measuredValues) {
results += title + ' : ' + self._measuredValues[title] + ' ms\n';
total += self._measuredValues[title];
}
results += 'Total : ' + (total / 2) + ' ms\n';
self._measuredValues = {};
self._removeFrame();
if (!results)
return;
var pre = document.createElement('pre');
document.body.appendChild(pre);
pre.textContent = results;
}
window.addEventListener('load', function () { BenchmarkRunner.listSuites(); });
(function () {
var style = document.createElement('style');
style.appendChild(document.createTextNode('iframe { width: 1000px; height: 500px; border: 2px solid black; }'
+ 'ol { list-style: none; margin: 0; padding: 0; }'
+ 'ol ol { margin-left: 2em; list-position: outside; }'
+ '.running { text-decoration: underline; }'
+ '.ran {color: grey}'
+ 'nav { position: absolute; right: 10px; }'));
document.head.appendChild(style);
})();