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