OLD | NEW |
(Empty) | |
| 1 // Copyright 2010 Google Inc. All Rights Reserved. |
| 2 |
| 3 /** |
| 4 * @fileoverview Playback agent. |
| 5 */ |
| 6 |
| 7 var Benchmark = Benchmark || {}; |
| 8 |
| 9 /** |
| 10 * Playback agent class. |
| 11 * @param {Object} data Test data. |
| 12 * @constructor |
| 13 */ |
| 14 Benchmark.Agent = function(data) { |
| 15 this.timeline = data.timeline; |
| 16 this.timelinePosition = 0; |
| 17 this.steps = data.steps; |
| 18 this.stepsPosition = 0; |
| 19 this.randoms = data.randoms; |
| 20 this.randomsPosition = 0; |
| 21 this.ticks = data.ticks; |
| 22 this.ticksPosition = 0; |
| 23 this.delayedScriptElements = {}; |
| 24 this.callStackDepth = 0; |
| 25 document.cookie = data.cookie; |
| 26 if (window.innerWidth != data.width || window.innerHeight != data.height) { |
| 27 Benchmark.die('Wrong window size: ' + |
| 28 window.innerWidth + 'x' + window.innerHeight + |
| 29 ' instead of ' + data.width + 'x' + data.height); |
| 30 } |
| 31 this.startTime = Benchmark.originals.Date.now(); |
| 32 }; |
| 33 |
| 34 /** |
| 35 * Returns current timeline event. |
| 36 * @return {Object} Event. |
| 37 */ |
| 38 Benchmark.Agent.prototype.getCurrentEvent = function() { |
| 39 return this.timeline[this.timelinePosition]; |
| 40 }; |
| 41 |
| 42 /** |
| 43 * Returns next recorded event in timeline. If event is the last event in |
| 44 * timeline, posts test results to driver. |
| 45 * @param {Object} event Event that actually happened, should correspond to |
| 46 * the recorded one (used for debug only). |
| 47 * @return {Object} Recorded event from timeline. |
| 48 */ |
| 49 Benchmark.Agent.prototype.getNextEvent = function(event) { |
| 50 var recordedEvent = this.getCurrentEvent(); |
| 51 this.ensureEqual(event, recordedEvent); |
| 52 if (event.type == 'random' || event.type == 'ticks') { |
| 53 recordedEvent.count -= 1; |
| 54 if (recordedEvent.count == 0) { |
| 55 this.timelinePosition += 1; |
| 56 } |
| 57 } else { |
| 58 this.timelinePosition += 1; |
| 59 } |
| 60 if (this.timelinePosition == this.steps[this.stepsPosition][1]) { |
| 61 var score = Benchmark.originals.Date.now() - this.startTime; |
| 62 Benchmark.reportScore(score); |
| 63 } |
| 64 return recordedEvent; |
| 65 }; |
| 66 |
| 67 /** |
| 68 * Checks if two events can be considered equal. Throws exception if events |
| 69 * differ. |
| 70 * @param {Object} event Event that actually happened. |
| 71 * @param {Object} recordedEvent Event taken from timeline. |
| 72 */ |
| 73 Benchmark.Agent.prototype.ensureEqual = function(event, recordedEvent) { |
| 74 var equal = false; |
| 75 if (event.type == recordedEvent.type && |
| 76 event.type in Benchmark.eventPropertiesMap) { |
| 77 equal = true; |
| 78 var properties = Benchmark.eventPropertiesMap[event.type]; |
| 79 for (var i = 0; i < properties.length && equal; ++i) |
| 80 if (event[properties[i]] != recordedEvent[properties[i]]) |
| 81 equal = false; |
| 82 } |
| 83 if (!equal) { |
| 84 Benchmark.die('unexpected event: ' + JSON.stringify(event) + |
| 85 ' instead of ' + JSON.stringify(recordedEvent)); |
| 86 } |
| 87 }; |
| 88 |
| 89 /** |
| 90 * Gets next event from timeline and returns it's identifier. |
| 91 * @param {Object} event Object with event information. |
| 92 * @return {number} Event identifier. |
| 93 */ |
| 94 Benchmark.Agent.prototype.createAsyncEvent = function(event) { |
| 95 return this.getNextEvent(event).id; |
| 96 }; |
| 97 |
| 98 /** |
| 99 * Stores callback to be invoked according to timeline order. |
| 100 * @param {number} eventId 'Parent' event identifier. |
| 101 * @param {function} callback Callback. |
| 102 */ |
| 103 Benchmark.Agent.prototype.fireAsyncEvent = function(eventId, callback) { |
| 104 var event = this.timeline[eventId]; |
| 105 if (!event.callbackReference) return; |
| 106 this.timeline[event.callbackReference].callback = callback; |
| 107 this.fireSome(); |
| 108 }; |
| 109 |
| 110 /** |
| 111 * Ensures that things are happening according to recorded timeline. |
| 112 * @param {number} eventId Identifier of cancelled event. |
| 113 */ |
| 114 Benchmark.Agent.prototype.cancelAsyncEvent = function(eventId) { |
| 115 this.getNextEvent({type: 'cancel', reference: eventId}); |
| 116 }; |
| 117 |
| 118 /** |
| 119 * Checks if script isn't going to be executed too early and delays script |
| 120 * execution if necessary. |
| 121 * @param {number} scriptId Unique script identifier. |
| 122 * @param {HTMLElement} doc Document element. |
| 123 * @param {boolean} inlined Indicates whether script is a text block in the page |
| 124 * or resides in a separate file. |
| 125 * @param {string} src Script url (if script is not inlined). |
| 126 */ |
| 127 Benchmark.Agent.prototype.readyToExecuteScript = function(scriptId, doc, |
| 128 inlined, src) { |
| 129 var event = this.getCurrentEvent(); |
| 130 if (event.type == 'willExecuteScript' && event.scriptId == scriptId) { |
| 131 this.timelinePosition += 1; |
| 132 return true; |
| 133 } |
| 134 var element; |
| 135 var elements = doc.getElementsByTagName('script'); |
| 136 for (var i = 0, el; (el = elements[i]) && !element; ++i) { |
| 137 if (inlined) { |
| 138 if (el.src) continue; |
| 139 var text = el.textContent; |
| 140 if (scriptId == text.substring(2, text.indexOf("*/"))) |
| 141 element = elements[i]; |
| 142 } else { |
| 143 if (!el.src) continue; |
| 144 if (el.src.indexOf(src) != -1 || src.indexOf(el.src) != -1) { |
| 145 element = el; |
| 146 } |
| 147 } |
| 148 } |
| 149 if (!element) { |
| 150 Benchmark.die('script element not found', scriptId, src); |
| 151 } |
| 152 for (var el2 = element; el2; el2 = el2.parentElement) { |
| 153 if (el2.onload) { |
| 154 console.log('found', el2); |
| 155 } |
| 156 } |
| 157 this.delayedScriptElements[scriptId] = element; |
| 158 return false; |
| 159 }; |
| 160 |
| 161 /** |
| 162 * Ensures that things are happening according to recorded timeline. |
| 163 * @param {Object} event Object with event information. |
| 164 */ |
| 165 Benchmark.Agent.prototype.didExecuteScript = function(scriptId ) { |
| 166 this.getNextEvent({type: 'didExecuteScript', scriptId: scriptId}); |
| 167 this.fireSome(); |
| 168 }; |
| 169 |
| 170 /** |
| 171 * Invokes async events' callbacks according to timeline order. |
| 172 */ |
| 173 Benchmark.Agent.prototype.fireSome = function() { |
| 174 while (this.timelinePosition < this.timeline.length) { |
| 175 var event = this.getCurrentEvent(); |
| 176 if (event.type == 'willFire') { |
| 177 if(!event.callback) break; |
| 178 this.timelinePosition += 1; |
| 179 this.callStackDepth += 1; |
| 180 event.callback(); |
| 181 this.callStackDepth -= 1; |
| 182 this.getNextEvent({type: 'didFire', reference: event.reference}); |
| 183 } else if (event.type == 'willExecuteScript') { |
| 184 if (event.scriptId in this.delayedScriptElements) { |
| 185 var element = this.delayedScriptElements[event.scriptId]; |
| 186 var parent = element.parentElement; |
| 187 var cloneElement = element.cloneNode(); |
| 188 delete this.delayedScriptElements[event.scriptId]; |
| 189 parent.replaceChild(cloneElement, element); |
| 190 } |
| 191 break; |
| 192 } else if (this.callStackDepth > 0) { |
| 193 break; |
| 194 } else { |
| 195 Benchmark.die('unexpected event in fireSome:' + JSON.stringify(event)); |
| 196 } |
| 197 } |
| 198 }; |
| 199 |
| 200 /** |
| 201 * Returns recorded random. |
| 202 * @return {number} Recorded random. |
| 203 */ |
| 204 Benchmark.Agent.prototype.random = function() { |
| 205 this.getNextEvent({type: 'random'}); |
| 206 return this.randoms[this.randomsPosition++]; |
| 207 }; |
| 208 |
| 209 /** |
| 210 * Returns recorded ticks. |
| 211 * @return {number} Recorded ticks. |
| 212 */ |
| 213 Benchmark.Agent.prototype.dateNow = function(event) { |
| 214 this.getNextEvent({type: 'ticks'}); |
| 215 return this.ticks[this.ticksPosition++]; |
| 216 }; |
| 217 |
| 218 /** |
| 219 * Event type -> property list mapping used for matching events. |
| 220 * @const |
| 221 */ |
| 222 Benchmark.eventPropertiesMap = { |
| 223 'timeout': ['timeout'], |
| 224 'request': ['url'], |
| 225 'addEventListener': ['eventType'], |
| 226 'script load': ['src'], |
| 227 'willExecuteScript': ['scriptId'], |
| 228 'didExecuteScript': ['scriptId'], |
| 229 'willFire': ['reference'], |
| 230 'didFire': ['reference'], |
| 231 'cancel': ['reference'], |
| 232 'random': [], |
| 233 'ticks': [] |
| 234 }; |
| 235 |
| 236 /** |
| 237 * Agent used by native window functions wrappers. |
| 238 */ |
| 239 Benchmark.agent = new Benchmark.Agent(Benchmark.data); |
| 240 |
| 241 /** |
| 242 * Playback flag. |
| 243 * @const |
| 244 */ |
| 245 Benchmark.playback = true; |
| 246 |
| 247 Benchmark.reportScore = function(score) { |
| 248 Benchmark.score = score; |
| 249 }; |
| 250 |
| 251 Benchmark.originals.addEventListenerToWindow.call( |
| 252 window, 'message', function(event) { |
| 253 if (Benchmark.score) { |
| 254 event.source.postMessage(Benchmark.score, event.origin); |
| 255 } |
| 256 }, false); |
OLD | NEW |