| // 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; |
| }; |