298 lines
9.1 KiB
JavaScript
298 lines
9.1 KiB
JavaScript
// 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);
|
|
}
|
|
|
|
function BenchmarkTestStep(testName, testFunction) {
|
|
this.name = testName;
|
|
this.run = testFunction;
|
|
}
|
|
|
|
function BenchmarkRunner(suites, client) {
|
|
this._suites = suites;
|
|
this._prepareReturnValue = null;
|
|
this._client = client;
|
|
}
|
|
|
|
BenchmarkRunner.prototype.waitForElement = function (selector) {
|
|
var promise = new SimplePromise;
|
|
var contentDocument = this._frame.contentDocument;
|
|
|
|
function resolveIfReady() {
|
|
var element = contentDocument.querySelector(selector);
|
|
if (element)
|
|
return promise.resolve(element);
|
|
setTimeout(resolveIfReady, 50);
|
|
}
|
|
|
|
resolveIfReady();
|
|
return promise;
|
|
}
|
|
|
|
BenchmarkRunner.prototype._removeFrame = function () {
|
|
if (this._frame) {
|
|
this._frame.parentNode.removeChild(this._frame);
|
|
this._frame = null;
|
|
}
|
|
}
|
|
|
|
BenchmarkRunner.prototype._appendFrame = function (src) {
|
|
var frame = document.createElement('iframe');
|
|
frame.style.width = '800px';
|
|
frame.style.height = '600px';
|
|
frame.style.border = '0px none';
|
|
frame.style.position = 'absolute';
|
|
frame.setAttribute('scrolling', 'no');
|
|
|
|
var marginLeft = parseInt(getComputedStyle(document.body).marginLeft);
|
|
var marginTop = parseInt(getComputedStyle(document.body).marginTop);
|
|
if (window.innerWidth > 800 + marginLeft && window.innerHeight > 600 + marginTop) {
|
|
frame.style.left = marginLeft + 'px';
|
|
frame.style.top = marginTop + 'px';
|
|
} else {
|
|
frame.style.left = '0px';
|
|
frame.style.top = '0px';
|
|
}
|
|
|
|
if (this._client && this._client.willAddTestFrame)
|
|
this._client.willAddTestFrame(frame);
|
|
|
|
document.body.insertBefore(frame, document.body.firstChild);
|
|
this._frame = frame;
|
|
return frame;
|
|
}
|
|
|
|
BenchmarkRunner.prototype._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;
|
|
}
|
|
|
|
BenchmarkRunner.prototype._writeMark = function(name) {
|
|
if (window.performance && window.performance.mark)
|
|
window.performance.mark(name);
|
|
}
|
|
|
|
// This function ought be as simple as possible. Don't even use SimplePromise.
|
|
BenchmarkRunner.prototype._runTest = function(suite, test, prepareReturnValue, callback)
|
|
{
|
|
var self = this;
|
|
var now = window.performance && window.performance.now ? function () { return window.performance.now(); } : Date.now;
|
|
|
|
var contentWindow = self._frame.contentWindow;
|
|
var contentDocument = self._frame.contentDocument;
|
|
|
|
self._writeMark(suite.name + '.' + test.name + '-start');
|
|
|
|
var startTime = now();
|
|
test.run(prepareReturnValue, contentWindow, contentDocument);
|
|
// Force style resolution + layout to ensure we're measuring it.
|
|
window._unusedHeightValue = self._frame.contentDocument.body.getBoundingClientRect().height;
|
|
var endTime = now();
|
|
|
|
self._writeMark(suite.name + '.' + test.name + '-sync-end');
|
|
|
|
var syncTime = endTime - startTime;
|
|
setTimeout(function () {
|
|
var asyncTime = 1;
|
|
self._writeMark(suite.name + '.' + test.name + '-async-end');
|
|
callback(syncTime, asyncTime);
|
|
}, 0);
|
|
}
|
|
|
|
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 && this._suites[this._suiteIndex].disabled);
|
|
|
|
return this;
|
|
}
|
|
|
|
BenchmarkState.prototype.isFirstTest = function () {
|
|
return !this._testIndex;
|
|
}
|
|
|
|
BenchmarkState.prototype.prepareCurrentSuite = function (runner, frame) {
|
|
var suite = this.currentSuite();
|
|
var promise = new SimplePromise;
|
|
frame.onload = function () {
|
|
suite.prepare(runner, frame.contentWindow, frame.contentDocument).then(function (result) { promise.resolve(result); });
|
|
}
|
|
frame.src = 'resources/' + suite.url;
|
|
return promise;
|
|
}
|
|
|
|
BenchmarkRunner.prototype.step = function (state) {
|
|
if (!state) {
|
|
state = new BenchmarkState(this._suites);
|
|
this._measuredValues = {tests: {}, total: 0, mean: NaN, geomean: NaN, score: NaN};
|
|
}
|
|
|
|
var suite = state.currentSuite();
|
|
if (!suite) {
|
|
this._finalize();
|
|
var promise = new SimplePromise;
|
|
promise.resolve();
|
|
return promise;
|
|
}
|
|
|
|
if (state.isFirstTest()) {
|
|
this._removeFrame();
|
|
var self = this;
|
|
return state.prepareCurrentSuite(this, this._appendFrame()).then(function (prepareReturnValue) {
|
|
self._prepareReturnValue = prepareReturnValue;
|
|
return self._runTestAndRecordResults(state);
|
|
});
|
|
}
|
|
|
|
return this._runTestAndRecordResults(state);
|
|
}
|
|
|
|
BenchmarkRunner.prototype.runAllSteps = function (startingState) {
|
|
var nextCallee = this.runAllSteps.bind(this);
|
|
this.step(startingState).then(function (nextState) {
|
|
if (nextState)
|
|
nextCallee(nextState);
|
|
});
|
|
}
|
|
|
|
BenchmarkRunner.prototype.runMultipleIterations = function (iterationCount) {
|
|
var self = this;
|
|
var currentIteration = 0;
|
|
|
|
this._runNextIteration = function () {
|
|
currentIteration++;
|
|
if (currentIteration < iterationCount)
|
|
self.runAllSteps();
|
|
else if (this._client && this._client.didFinishLastIteration)
|
|
this._client.didFinishLastIteration();
|
|
}
|
|
|
|
if (this._client && this._client.willStartFirstIteration)
|
|
this._client.willStartFirstIteration(iterationCount);
|
|
|
|
self.runAllSteps();
|
|
}
|
|
|
|
BenchmarkRunner.prototype._runTestAndRecordResults = function (state) {
|
|
var promise = new SimplePromise;
|
|
var suite = state.currentSuite();
|
|
var test = state.currentTest();
|
|
|
|
if (this._client && this._client.willRunTest)
|
|
this._client.willRunTest(suite, test);
|
|
|
|
var self = this;
|
|
setTimeout(function () {
|
|
self._runTest(suite, test, self._prepareReturnValue, function (syncTime, asyncTime) {
|
|
var suiteResults = self._measuredValues.tests[suite.name] || {tests:{}, total: 0};
|
|
var total = syncTime + asyncTime;
|
|
self._measuredValues.tests[suite.name] = suiteResults;
|
|
suiteResults.tests[test.name] = {tests: {'Sync': syncTime, 'Async': asyncTime}, total: total};
|
|
suiteResults.total += total;
|
|
|
|
if (self._client && self._client.didRunTest)
|
|
self._client.didRunTest(suite, test);
|
|
|
|
state.next();
|
|
promise.resolve(state);
|
|
});
|
|
}, 0);
|
|
return promise;
|
|
}
|
|
|
|
BenchmarkRunner.prototype._finalize = function () {
|
|
this._removeFrame();
|
|
|
|
if (this._client && this._client.didRunSuites) {
|
|
var product = 1;
|
|
var values = [];
|
|
for (var suiteName in this._measuredValues.tests) {
|
|
var suiteTotal = this._measuredValues.tests[suiteName].total;
|
|
product *= suiteTotal;
|
|
values.push(suiteTotal);
|
|
}
|
|
|
|
values.sort(function (a, b) { return a - b }); // Avoid the loss of significance for the sum.
|
|
var total = values.reduce(function (a, b) { return a + b });
|
|
var geomean = Math.pow(product, 1 / values.length);
|
|
|
|
var correctionFactor = 1.5; // This factor makes the test score look reasonably fit within 0 to 140.
|
|
this._measuredValues.total = total;
|
|
this._measuredValues.mean = total / values.length;
|
|
this._measuredValues.geomean = geomean;
|
|
this._measuredValues.score = 60 * 1000 / geomean / correctionFactor;
|
|
this._client.didRunSuites(this._measuredValues);
|
|
}
|
|
|
|
if (this._runNextIteration)
|
|
this._runNextIteration();
|
|
}
|