1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview Classes and functions used during recording and playback. 7 */ 8 9 var Benchmark = Benchmark || {}; 10 11 Benchmark.functionList = [ 12 ['setTimeout', 'setTimeout'], 13 ['clearTimeout', 'clearTimeout'], 14 ['setInterval', 'setInterval'], 15 ['clearInterval', 'clearInterval'], 16 ['XMLHttpRequest', 'XMLHttpRequest'], 17 ['addEventListenerToWindow', 'addEventListener'], 18 ['addEventListenerToNode', 'addEventListener', ['Node', 'prototype']], 19 ['removeEventListenerFromNode', 'removeEventListener', ['Node', 'prototype']], 20 ['addEventListenerToXHR', 'addEventListener', 21 ['XMLHttpRequest', 'prototype']], 22 ['random', 'random', ['Math']], 23 ['Date', 'Date'], 24 ['documentWriteln', 'writeln', ['document']], 25 ['documentWrite', 'write', ['document']] 26 ]; 27 28 Benchmark.timeoutMapping = []; 29 30 Benchmark.ignoredListeners = ['mousemove', 'mouseover', 'mouseout']; 31 32 Benchmark.originals = {}; 33 34 Benchmark.overrides = { 35 setTimeout: function(callback, timeout) { 36 var event = {type: 'timeout', timeout: timeout}; 37 var eventId = Benchmark.agent.createAsyncEvent(event); 38 var timerId = Benchmark.originals.setTimeout.call(this, function() { 39 Benchmark.agent.fireAsyncEvent(eventId, callback); 40 }, Benchmark.playback ? 0 : timeout); 41 Benchmark.timeoutMapping[timerId] = eventId; 42 return timerId; 43 }, 44 45 clearTimeout: function(timerId) { 46 var eventId = Benchmark.timeoutMapping[timerId]; 47 if (eventId == undefined) return; 48 Benchmark.agent.cancelAsyncEvent(eventId); 49 Benchmark.originals.clearTimeout.call(this, timerId); 50 }, 51 52 setInterval: function(callback, timeout) { 53 console.warn('setInterval'); 54 }, 55 56 clearInterval: function(timerId) { 57 console.warn('clearInterval'); 58 }, 59 60 XMLHttpRequest: function() { 61 return new Benchmark.XMLHttpRequestWrapper(); 62 }, 63 64 addEventListener: function(type, listener, useCapture, target, targetType, 65 originalFunction) { 66 var event = {type: 'addEventListener', target: targetType, eventType: type}; 67 var eventId = Benchmark.agent.createAsyncEvent(event); 68 listener.eventId = eventId; 69 listener.wrapper = function(e) { 70 Benchmark.agent.fireAsyncEvent(eventId, function() { 71 listener.call(target, e); 72 }); 73 }; 74 originalFunction.call(target, type, listener.wrapper, useCapture); 75 }, 76 77 addEventListenerToWindow: function(type, listener, useCapture) { 78 if (Benchmark.ignoredListeners.indexOf(type) != -1) return; 79 Benchmark.overrides.addEventListener( 80 type, listener, useCapture, this, 'window', 81 Benchmark.originals.addEventListenerToWindow); 82 }, 83 84 addEventListenerToNode: function(type, listener, useCapture) { 85 if (Benchmark.ignoredListeners.indexOf(type) != -1) return; 86 Benchmark.overrides.addEventListener( 87 type, listener, useCapture, this, 'node', 88 Benchmark.originals.addEventListenerToNode); 89 }, 90 91 addEventListenerToXHR: function(type, listener, useCapture) { 92 Benchmark.overrides.addEventListener( 93 type, listener, useCapture, this, 'xhr', 94 Benchmark.originals.addEventListenerToXHR); 95 }, 96 97 removeEventListener: function(type, listener, useCapture, target, 98 originalFunction) { 99 Benchmark.agent.cancelAsyncEvent(listener.eventId); 100 originalFunction.call(target, listener.wrapper, useCapture); 101 }, 102 103 removeEventListenerFromWindow: function(type, listener, useCapture) { 104 removeEventListener(type, listener, useCapture, this, 105 Benchmark.originals.removeEventListenerFromWindow); 106 }, 107 108 removeEventListenerFromNode: function(type, listener, useCapture) { 109 removeEventListener(type, listener, useCapture, this, 110 Benchmark.originals.removeEventListenerFromNode); 111 }, 112 113 removeEventListenerFromXHR: function(type, listener, useCapture) { 114 removeEventListener(type, listener, useCapture, this, 115 Benchmark.originals.removeEventListenerFromXHR); 116 }, 117 118 random: function() { 119 return Benchmark.agent.random(); 120 }, 121 122 Date: function() { 123 var a = arguments; 124 var D = Benchmark.originals.Date, d; 125 switch(a.length) { 126 case 0: d = new D(Benchmark.agent.dateNow()); break; 127 case 1: d = new D(a[0]); break; 128 case 2: d = new D(a[0], a[1]); break; 129 case 3: d = new D(a[0], a[1], a[2]); break; 130 default: Benchmark.die('window.Date', arguments); 131 } 132 d.getTimezoneOffset = function() { return -240; }; 133 return d; 134 }, 135 136 dateNow: function() { 137 return Benchmark.agent.dateNow(); 138 }, 139 140 documentWriteln: function() { 141 console.warn('writeln'); 142 }, 143 144 documentWrite: function() { 145 console.warn('write'); 146 } 147 }; 148 149 /** 150 * Replaces window functions specified by Benchmark.functionList with overrides 151 * and optionally saves original functions to Benchmark.originals. 152 * @param {Object} wnd Window object. 153 * @param {boolean} storeOriginals When true, original functions are saved to 154 * Benchmark.originals. 155 */ 156 Benchmark.installOverrides = function(wnd, storeOriginals) { 157 // Substitute window functions with overrides. 158 for (var i = 0; i < Benchmark.functionList.length; ++i) { 159 var info = Benchmark.functionList[i], object = wnd; 160 var propertyName = info[1], pathToProperty = info[2]; 161 if (pathToProperty) 162 for (var j = 0; j < pathToProperty.length; ++j) 163 object = object[pathToProperty[j]]; 164 if (storeOriginals) 165 Benchmark.originals[info[0]] = object[propertyName]; 166 object[propertyName] = Benchmark.overrides[info[0]]; 167 } 168 wnd.__defineSetter__('onload', function() { 169 console.warn('window.onload setter')} 170 ); 171 172 // Substitute window functions of static frames when DOM content is loaded. 173 Benchmark.originals.addEventListenerToWindow.call(wnd, 'DOMContentLoaded', 174 function() { 175 var frames = document.getElementsByTagName('iframe'); 176 for (var i = 0, frame; frame = frames[i]; ++i) { 177 Benchmark.installOverrides(frame.contentWindow); 178 } 179 }, true); 180 181 // Substitute window functions of dynamically added frames. 182 Benchmark.originals.addEventListenerToWindow.call( 183 wnd, 'DOMNodeInsertedIntoDocument', function(e) { 184 if (e.target.tagName && e.target.tagName.toLowerCase() != 'iframe') 185 return; 186 if (e.target.contentWindow) 187 Benchmark.installOverrides(e.target.contentWindow); 188 }, true); 189 }; 190 191 // Install overrides on top window. 192 Benchmark.installOverrides(window, true); 193 194 /** 195 * window.XMLHttpRequest wrapper. Notifies Benchmark.agent when request is 196 * opened, aborted, and when it's ready state changes to DONE. 197 * @constructor 198 */ 199 Benchmark.XMLHttpRequestWrapper = function() { 200 this.request = new Benchmark.originals.XMLHttpRequest(); 201 this.wrapperReadyState = 0; 202 }; 203 204 // Create XMLHttpRequestWrapper functions and property accessors using original 205 // ones. 206 (function() { 207 var request = new Benchmark.originals.XMLHttpRequest(); 208 for (var property in request) { 209 if (property === 'channel') continue; // Quick fix for FF. 210 if (typeof(request[property]) == 'function') { 211 (function(property) { 212 var f = Benchmark.originals.XMLHttpRequest.prototype[property]; 213 Benchmark.XMLHttpRequestWrapper.prototype[property] = function() { 214 f.apply(this.request, arguments); 215 }; 216 })(property); 217 } else { 218 (function(property) { 219 Benchmark.XMLHttpRequestWrapper.prototype.__defineGetter__(property, 220 function() { return this.request[property]; }); 221 Benchmark.XMLHttpRequestWrapper.prototype.__defineSetter__(property, 222 function(value) { 223 this.request[property] = value; 224 }); 225 226 })(property); 227 } 228 } 229 })(); 230 231 // Define onreadystatechange getter. 232 Benchmark.XMLHttpRequestWrapper.prototype.__defineGetter__('onreadystatechange', 233 function() { return this.clientOnReadyStateChange; }); 234 235 // Define onreadystatechange setter. 236 Benchmark.XMLHttpRequestWrapper.prototype.__defineSetter__('onreadystatechange', 237 function(value) { this.clientOnReadyStateChange = value; }); 238 239 Benchmark.XMLHttpRequestWrapper.prototype.__defineGetter__('readyState', 240 function() { return this.wrapperReadyState; }); 241 242 Benchmark.XMLHttpRequestWrapper.prototype.__defineSetter__('readyState', 243 function() {}); 244 245 246 /** 247 * Wrapper for XMLHttpRequest.open. 248 */ 249 Benchmark.XMLHttpRequestWrapper.prototype.open = function() { 250 var url = Benchmark.extractURL(arguments[1]); 251 var event = {type: 'request', method: arguments[0], url: url}; 252 this.eventId = Benchmark.agent.createAsyncEvent(event); 253 254 var request = this.request; 255 var requestWrapper = this; 256 Benchmark.originals.XMLHttpRequest.prototype.open.apply(request, arguments); 257 request.onreadystatechange = function() { 258 if (this.readyState != 4 || requestWrapper.cancelled) return; 259 var callback = requestWrapper.clientOnReadyStateChange || function() {}; 260 Benchmark.agent.fireAsyncEvent(requestWrapper.eventId, function() { 261 requestWrapper.wrapperReadyState = 4; 262 callback.call(request); 263 }); 264 } 265 }; 266 267 /** 268 * Wrapper for XMLHttpRequest.abort. 269 */ 270 Benchmark.XMLHttpRequestWrapper.prototype.abort = function() { 271 this.cancelled = true; 272 Benchmark.originals.XMLHttpRequest.prototype.abort.apply( 273 this.request, arguments); 274 Benchmark.agent.cancelAsyncEvent(this.eventId); 275 }; 276 277 /** 278 * Driver url for reporting results. 279 * @const {string} 280 */ 281 Benchmark.DRIVER_URL = '/benchmark/'; 282 283 /** 284 * Posts request as json to Benchmark.DRIVER_URL. 285 * @param {Object} request Request to post. 286 */ 287 Benchmark.post = function(request, async) { 288 if (async === undefined) async = true; 289 var xmlHttpRequest = new Benchmark.originals.XMLHttpRequest(); 290 xmlHttpRequest.open("POST", Benchmark.DRIVER_URL, async); 291 xmlHttpRequest.setRequestHeader("Content-type", "application/json"); 292 xmlHttpRequest.send(JSON.stringify(request)); 293 }; 294 295 /** 296 * Extracts url string. 297 * @param {(string|Object)} url Object or string representing url. 298 * @return {string} Extracted url. 299 */ 300 Benchmark.extractURL = function(url) { 301 if (typeof(url) == 'string') return url; 302 return url.nI || url.G || ''; 303 }; 304 305 306 /** 307 * Logs error message to console and throws an exception. 308 * @param {string} message Error message 309 */ 310 Benchmark.die = function(message) { 311 // Debugging stuff. 312 var position = top.Benchmark.playback ? top.Benchmark.agent.timelinePosition : 313 top.Benchmark.agent.timeline.length; 314 message = message + ' at position ' + position; 315 console.error(message); 316 Benchmark.post({error: message}); 317 console.log(Benchmark.originals.setTimeout.call(window, function() {}, 9999)); 318 try { (0)() } catch(ex) { console.error(ex.stack); } 319 throw message; 320 }; 321