Home | History | Annotate | Download | only in playback_benchmark
      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