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