Index: tools/playback_benchmark/common.js |
=================================================================== |
--- tools/playback_benchmark/common.js (revision 0) |
+++ tools/playback_benchmark/common.js (revision 0) |
@@ -0,0 +1,318 @@ |
+// Copyright 2010 Google Inc. All Rights Reserved. |
+ |
+/** |
+ * @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; |
+}; |