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.
6 * @fileoverview Playback agent.
9 var Benchmark
= Benchmark
|| {};
12 * Playback agent class.
13 * @param {Object} data Test data.
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
);
33 this.startTime
= Benchmark
.originals
.Date
.now();
37 * Returns current timeline event.
38 * @return {Object} Event.
40 Benchmark
.Agent
.prototype.getCurrentEvent = function() {
41 return this.timeline
[this.timelinePosition
];
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.
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;
60 this.timelinePosition
+= 1;
62 if (this.timelinePosition
== this.steps
[this.stepsPosition
][1]) {
63 var score
= Benchmark
.originals
.Date
.now() - this.startTime
;
64 Benchmark
.reportScore(score
);
70 * Checks if two events can be considered equal. Throws exception if events
72 * @param {Object} event Event that actually happened.
73 * @param {Object} recordedEvent Event taken from timeline.
75 Benchmark
.Agent
.prototype.ensureEqual = function(event
, recordedEvent
) {
77 if (event
.type
== recordedEvent
.type
&&
78 event
.type
in Benchmark
.eventPropertiesMap
) {
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
]])
86 Benchmark
.die('unexpected event: ' + JSON
.stringify(event
) +
87 ' instead of ' + JSON
.stringify(recordedEvent
));
92 * Gets next event from timeline and returns its identifier.
93 * @param {Object} event Object with event information.
94 * @return {number} Event identifier.
96 Benchmark
.Agent
.prototype.createAsyncEvent = function(event
) {
97 return this.getNextEvent(event
).id
;
101 * Stores callback to be invoked according to timeline order.
102 * @param {number} eventId 'Parent' event identifier.
103 * @param {function} callback Callback.
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
;
113 * Ensures that things are happening according to recorded timeline.
114 * @param {number} eventId Identifier of cancelled event.
116 Benchmark
.Agent
.prototype.cancelAsyncEvent = function(eventId
) {
117 this.getNextEvent({type
: 'cancel', reference
: eventId
});
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).
129 Benchmark
.Agent
.prototype.readyToExecuteScript = function(scriptId
, doc
,
131 var event
= this.getCurrentEvent();
132 if (event
.type
== 'willExecuteScript' && event
.scriptId
== scriptId
) {
133 this.timelinePosition
+= 1;
137 var elements
= doc
.getElementsByTagName('script');
138 for (var i
= 0, el
; (el
= elements
[i
]) && !element
; ++i
) {
140 if (el
.src
) continue;
141 var text
= el
.textContent
;
142 if (scriptId
== text
.substring(2, text
.indexOf("*/")))
143 element
= elements
[i
];
145 if (!el
.src
) continue;
146 if (el
.src
.indexOf(src
) != -1 || src
.indexOf(el
.src
) != -1) {
152 Benchmark
.die('script element not found', scriptId
, src
);
154 for (var el2
= element
; el2
; el2
= el2
.parentElement
) {
156 console
.log('found', el2
);
159 this.delayedScriptElements
[scriptId
] = element
;
164 * Ensures that things are happening according to recorded timeline.
165 * @param {Object} event Object with event information.
167 Benchmark
.Agent
.prototype.didExecuteScript = function(scriptId
) {
168 this.getNextEvent({type
: 'didExecuteScript', scriptId
: scriptId
});
173 * Invokes async events' callbacks according to timeline order.
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;
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
);
194 } else if (this.callStackDepth
> 0) {
197 Benchmark
.die('unexpected event in fireSome:' + JSON
.stringify(event
));
203 * Returns recorded random.
204 * @return {number} Recorded random.
206 Benchmark
.Agent
.prototype.random = function() {
207 this.getNextEvent({type
: 'random'});
208 return this.randoms
[this.randomsPosition
++];
212 * Returns recorded ticks.
213 * @return {number} Recorded ticks.
215 Benchmark
.Agent
.prototype.dateNow = function(event
) {
216 this.getNextEvent({type
: 'ticks'});
217 return this.ticks
[this.ticksPosition
++];
221 * Event type -> property list mapping used for matching events.
224 Benchmark
.eventPropertiesMap
= {
225 'timeout': ['timeout'],
227 'addEventListener': ['eventType'],
228 'script load': ['src'],
229 'willExecuteScript': ['scriptId'],
230 'didExecuteScript': ['scriptId'],
231 'willFire': ['reference'],
232 'didFire': ['reference'],
233 'cancel': ['reference'],
239 * Agent used by native window functions wrappers.
241 Benchmark
.agent
= new Benchmark
.Agent(Benchmark
.data
);
247 Benchmark
.playback
= true;
249 Benchmark
.reportScore = function(score
) {
250 Benchmark
.score
= score
;
253 Benchmark
.originals
.addEventListenerToWindow
.call(
254 window
, 'message', function(event
) {
255 if (Benchmark
.score
) {
256 event
.source
.postMessage(Benchmark
.score
, event
.origin
);