321 lines
11 KiB
JavaScript
321 lines
11 KiB
JavaScript
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/**
|
|
* @fileoverview Classes and functions used during recording and playback.
|
|
*/
|
|
|
|
var Benchmark = Benchmark || {};
|
|
|
|
Benchmark.functionList = [
|
|
['setTimeout', 'setTimeout'],
|
|
['clearTimeout', 'clearTimeout'],
|
|
['setInterval', 'setInterval'],
|
|
['clearInterval', 'clearInterval'],
|
|
['XMLHttpRequest', 'XMLHttpRequest'],
|
|
['addEventListenerToWindow', 'addEventListener'],
|
|
['addEventListenerToNode', 'addEventListener', ['Node', 'prototype']],
|
|
['removeEventListenerFromNode', 'removeEventListener', ['Node', 'prototype']],
|
|
['addEventListenerToXHR', 'addEventListener',
|
|
['XMLHttpRequest', 'prototype']],
|
|
['random', 'random', ['Math']],
|
|
['Date', 'Date'],
|
|
['documentWriteln', 'writeln', ['document']],
|
|
['documentWrite', 'write', ['document']]
|
|
];
|
|
|
|
Benchmark.timeoutMapping = [];
|
|
|
|
Benchmark.ignoredListeners = ['mousemove', 'mouseover', 'mouseout'];
|
|
|
|
Benchmark.originals = {};
|
|
|
|
Benchmark.overrides = {
|
|
setTimeout: function(callback, timeout) {
|
|
var event = {type: 'timeout', timeout: timeout};
|
|
var eventId = Benchmark.agent.createAsyncEvent(event);
|
|
var timerId = Benchmark.originals.setTimeout.call(this, function() {
|
|
Benchmark.agent.fireAsyncEvent(eventId, callback);
|
|
}, Benchmark.playback ? 0 : timeout);
|
|
Benchmark.timeoutMapping[timerId] = eventId;
|
|
return timerId;
|
|
},
|
|
|
|
clearTimeout: function(timerId) {
|
|
var eventId = Benchmark.timeoutMapping[timerId];
|
|
if (eventId == undefined) return;
|
|
Benchmark.agent.cancelAsyncEvent(eventId);
|
|
Benchmark.originals.clearTimeout.call(this, timerId);
|
|
},
|
|
|
|
setInterval: function(callback, timeout) {
|
|
console.warn('setInterval');
|
|
},
|
|
|
|
clearInterval: function(timerId) {
|
|
console.warn('clearInterval');
|
|
},
|
|
|
|
XMLHttpRequest: function() {
|
|
return new Benchmark.XMLHttpRequestWrapper();
|
|
},
|
|
|
|
addEventListener: function(type, listener, useCapture, target, targetType,
|
|
originalFunction) {
|
|
var event = {type: 'addEventListener', target: targetType, eventType: type};
|
|
var eventId = Benchmark.agent.createAsyncEvent(event);
|
|
listener.eventId = eventId;
|
|
listener.wrapper = function(e) {
|
|
Benchmark.agent.fireAsyncEvent(eventId, function() {
|
|
listener.call(target, e);
|
|
});
|
|
};
|
|
originalFunction.call(target, type, listener.wrapper, useCapture);
|
|
},
|
|
|
|
addEventListenerToWindow: function(type, listener, useCapture) {
|
|
if (Benchmark.ignoredListeners.indexOf(type) != -1) return;
|
|
Benchmark.overrides.addEventListener(
|
|
type, listener, useCapture, this, 'window',
|
|
Benchmark.originals.addEventListenerToWindow);
|
|
},
|
|
|
|
addEventListenerToNode: function(type, listener, useCapture) {
|
|
if (Benchmark.ignoredListeners.indexOf(type) != -1) return;
|
|
Benchmark.overrides.addEventListener(
|
|
type, listener, useCapture, this, 'node',
|
|
Benchmark.originals.addEventListenerToNode);
|
|
},
|
|
|
|
addEventListenerToXHR: function(type, listener, useCapture) {
|
|
Benchmark.overrides.addEventListener(
|
|
type, listener, useCapture, this, 'xhr',
|
|
Benchmark.originals.addEventListenerToXHR);
|
|
},
|
|
|
|
removeEventListener: function(type, listener, useCapture, target,
|
|
originalFunction) {
|
|
Benchmark.agent.cancelAsyncEvent(listener.eventId);
|
|
originalFunction.call(target, listener.wrapper, useCapture);
|
|
},
|
|
|
|
removeEventListenerFromWindow: function(type, listener, useCapture) {
|
|
removeEventListener(type, listener, useCapture, this,
|
|
Benchmark.originals.removeEventListenerFromWindow);
|
|
},
|
|
|
|
removeEventListenerFromNode: function(type, listener, useCapture) {
|
|
removeEventListener(type, listener, useCapture, this,
|
|
Benchmark.originals.removeEventListenerFromNode);
|
|
},
|
|
|
|
removeEventListenerFromXHR: function(type, listener, useCapture) {
|
|
removeEventListener(type, listener, useCapture, this,
|
|
Benchmark.originals.removeEventListenerFromXHR);
|
|
},
|
|
|
|
random: function() {
|
|
return Benchmark.agent.random();
|
|
},
|
|
|
|
Date: function() {
|
|
var a = arguments;
|
|
var D = Benchmark.originals.Date, d;
|
|
switch(a.length) {
|
|
case 0: d = new D(Benchmark.agent.dateNow()); break;
|
|
case 1: d = new D(a[0]); break;
|
|
case 2: d = new D(a[0], a[1]); break;
|
|
case 3: d = new D(a[0], a[1], a[2]); break;
|
|
default: Benchmark.die('window.Date', arguments);
|
|
}
|
|
d.getTimezoneOffset = function() { return -240; };
|
|
return d;
|
|
},
|
|
|
|
dateNow: function() {
|
|
return Benchmark.agent.dateNow();
|
|
},
|
|
|
|
documentWriteln: function() {
|
|
console.warn('writeln');
|
|
},
|
|
|
|
documentWrite: function() {
|
|
console.warn('write');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Replaces window functions specified by Benchmark.functionList with overrides
|
|
* and optionally saves original functions to Benchmark.originals.
|
|
* @param {Object} wnd Window object.
|
|
* @param {boolean} storeOriginals When true, original functions are saved to
|
|
* Benchmark.originals.
|
|
*/
|
|
Benchmark.installOverrides = function(wnd, storeOriginals) {
|
|
// Substitute window functions with overrides.
|
|
for (var i = 0; i < Benchmark.functionList.length; ++i) {
|
|
var info = Benchmark.functionList[i], object = wnd;
|
|
var propertyName = info[1], pathToProperty = info[2];
|
|
if (pathToProperty)
|
|
for (var j = 0; j < pathToProperty.length; ++j)
|
|
object = object[pathToProperty[j]];
|
|
if (storeOriginals)
|
|
Benchmark.originals[info[0]] = object[propertyName];
|
|
object[propertyName] = Benchmark.overrides[info[0]];
|
|
}
|
|
wnd.__defineSetter__('onload', function() {
|
|
console.warn('window.onload setter')}
|
|
);
|
|
|
|
// Substitute window functions of static frames when DOM content is loaded.
|
|
Benchmark.originals.addEventListenerToWindow.call(wnd, 'DOMContentLoaded',
|
|
function() {
|
|
var frames = document.getElementsByTagName('iframe');
|
|
for (var i = 0, frame; frame = frames[i]; ++i) {
|
|
Benchmark.installOverrides(frame.contentWindow);
|
|
}
|
|
}, true);
|
|
|
|
// Substitute window functions of dynamically added frames.
|
|
Benchmark.originals.addEventListenerToWindow.call(
|
|
wnd, 'DOMNodeInsertedIntoDocument', function(e) {
|
|
if (e.target.tagName && e.target.tagName.toLowerCase() != 'iframe')
|
|
return;
|
|
if (e.target.contentWindow)
|
|
Benchmark.installOverrides(e.target.contentWindow);
|
|
}, true);
|
|
};
|
|
|
|
// Install overrides on top window.
|
|
Benchmark.installOverrides(window, true);
|
|
|
|
/**
|
|
* window.XMLHttpRequest wrapper. Notifies Benchmark.agent when request is
|
|
* opened, aborted, and when it's ready state changes to DONE.
|
|
* @constructor
|
|
*/
|
|
Benchmark.XMLHttpRequestWrapper = function() {
|
|
this.request = new Benchmark.originals.XMLHttpRequest();
|
|
this.wrapperReadyState = 0;
|
|
};
|
|
|
|
// Create XMLHttpRequestWrapper functions and property accessors using original
|
|
// ones.
|
|
(function() {
|
|
var request = new Benchmark.originals.XMLHttpRequest();
|
|
for (var property in request) {
|
|
if (property === 'channel') continue; // Quick fix for FF.
|
|
if (typeof(request[property]) == 'function') {
|
|
(function(property) {
|
|
var f = Benchmark.originals.XMLHttpRequest.prototype[property];
|
|
Benchmark.XMLHttpRequestWrapper.prototype[property] = function() {
|
|
f.apply(this.request, arguments);
|
|
};
|
|
})(property);
|
|
} else {
|
|
(function(property) {
|
|
Benchmark.XMLHttpRequestWrapper.prototype.__defineGetter__(property,
|
|
function() { return this.request[property]; });
|
|
Benchmark.XMLHttpRequestWrapper.prototype.__defineSetter__(property,
|
|
function(value) {
|
|
this.request[property] = value;
|
|
});
|
|
|
|
})(property);
|
|
}
|
|
}
|
|
})();
|
|
|
|
// Define onreadystatechange getter.
|
|
Benchmark.XMLHttpRequestWrapper.prototype.__defineGetter__('onreadystatechange',
|
|
function() { return this.clientOnReadyStateChange; });
|
|
|
|
// Define onreadystatechange setter.
|
|
Benchmark.XMLHttpRequestWrapper.prototype.__defineSetter__('onreadystatechange',
|
|
function(value) { this.clientOnReadyStateChange = value; });
|
|
|
|
Benchmark.XMLHttpRequestWrapper.prototype.__defineGetter__('readyState',
|
|
function() { return this.wrapperReadyState; });
|
|
|
|
Benchmark.XMLHttpRequestWrapper.prototype.__defineSetter__('readyState',
|
|
function() {});
|
|
|
|
|
|
/**
|
|
* Wrapper for XMLHttpRequest.open.
|
|
*/
|
|
Benchmark.XMLHttpRequestWrapper.prototype.open = function() {
|
|
var url = Benchmark.extractURL(arguments[1]);
|
|
var event = {type: 'request', method: arguments[0], url: url};
|
|
this.eventId = Benchmark.agent.createAsyncEvent(event);
|
|
|
|
var request = this.request;
|
|
var requestWrapper = this;
|
|
Benchmark.originals.XMLHttpRequest.prototype.open.apply(request, arguments);
|
|
request.onreadystatechange = function() {
|
|
if (this.readyState != 4 || requestWrapper.cancelled) return;
|
|
var callback = requestWrapper.clientOnReadyStateChange || function() {};
|
|
Benchmark.agent.fireAsyncEvent(requestWrapper.eventId, function() {
|
|
requestWrapper.wrapperReadyState = 4;
|
|
callback.call(request);
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Wrapper for XMLHttpRequest.abort.
|
|
*/
|
|
Benchmark.XMLHttpRequestWrapper.prototype.abort = function() {
|
|
this.cancelled = true;
|
|
Benchmark.originals.XMLHttpRequest.prototype.abort.apply(
|
|
this.request, arguments);
|
|
Benchmark.agent.cancelAsyncEvent(this.eventId);
|
|
};
|
|
|
|
/**
|
|
* Driver url for reporting results.
|
|
* @const {string}
|
|
*/
|
|
Benchmark.DRIVER_URL = '/benchmark/';
|
|
|
|
/**
|
|
* Posts request as json to Benchmark.DRIVER_URL.
|
|
* @param {Object} request Request to post.
|
|
*/
|
|
Benchmark.post = function(request, async) {
|
|
if (async === undefined) async = true;
|
|
var xmlHttpRequest = new Benchmark.originals.XMLHttpRequest();
|
|
xmlHttpRequest.open("POST", Benchmark.DRIVER_URL, async);
|
|
xmlHttpRequest.setRequestHeader("Content-type", "application/json");
|
|
xmlHttpRequest.send(JSON.stringify(request));
|
|
};
|
|
|
|
/**
|
|
* Extracts url string.
|
|
* @param {(string|Object)} url Object or string representing url.
|
|
* @return {string} Extracted url.
|
|
*/
|
|
Benchmark.extractURL = function(url) {
|
|
if (typeof(url) == 'string') return url;
|
|
return url.nI || url.G || '';
|
|
};
|
|
|
|
|
|
/**
|
|
* Logs error message to console and throws an exception.
|
|
* @param {string} message Error message
|
|
*/
|
|
Benchmark.die = function(message) {
|
|
// Debugging stuff.
|
|
var position = top.Benchmark.playback ? top.Benchmark.agent.timelinePosition :
|
|
top.Benchmark.agent.timeline.length;
|
|
message = message + ' at position ' + position;
|
|
console.error(message);
|
|
Benchmark.post({error: message});
|
|
console.log(Benchmark.originals.setTimeout.call(window, function() {}, 9999));
|
|
try { (0)() } catch(ex) { console.error(ex.stack); }
|
|
throw message;
|
|
};
|