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.
5 // Inject this script on any page to measure framerate as the page is scrolled
9 // 1. Define a callback that takes the results array as a parameter.
10 // 2. To start the test, call new __ScrollTest(callback).
11 // 3a. When the test is complete, the callback will be called.
12 // 3b. If no callback is specified, the results are sent to the console.
15 var getTimeMs
= (function() {
16 if (window
.performance
)
17 return (performance
.now
||
21 performance
.webkitNow
).bind(window
.performance
);
23 return function() { return new Date().getTime(); };
26 var requestAnimationFrame
= (function() {
27 return window
.requestAnimationFrame
||
28 window
.webkitRequestAnimationFrame
||
29 window
.mozRequestAnimationFrame
||
30 window
.oRequestAnimationFrame
||
31 window
.msRequestAnimationFrame
||
33 window
.setTimeout(callback
, 1000 / 60);
38 * Scrolls a given element down a certain amount to emulate user scrolling.
39 * Uses smooth scrolling capabilities provided by the platform, if available.
42 function SmoothScrollDownGesture(opt_element
, opt_isGmailTest
) {
43 this.element_
= opt_element
|| document
.body
;
44 this.isGmailTest_
= opt_isGmailTest
;
47 SmoothScrollDownGesture
.prototype.start = function(callback
) {
48 this.callback_
= callback
;
50 chrome
.gpuBenchmarking
&&
51 chrome
.gpuBenchmarking
.beginSmoothScrollDown
) {
52 chrome
.gpuBenchmarking
.beginSmoothScrollDown(true, function() {
58 if (this.isGmailTest_
) {
59 this.element_
.scrollByLines(1);
60 requestAnimationFrame(callback
);
64 var SCROLL_DELTA
= 100;
65 this.element_
.scrollTop
+= SCROLL_DELTA
;
66 requestAnimationFrame(callback
);
70 * Tracks rendering performance using the gpuBenchmarking.renderingStats API.
73 function GpuBenchmarkingRenderingStats() {
76 GpuBenchmarkingRenderingStats
.prototype.start = function() {
77 this.initialStats_
= this.getRenderingStats_();
79 GpuBenchmarkingRenderingStats
.prototype.stop = function() {
80 this.finalStats_
= this.getRenderingStats_();
83 GpuBenchmarkingRenderingStats
.prototype.getResult = function() {
84 if (!this.initialStats_
)
85 throw new Error("Start not called.");
87 if (!this.finalStats_
)
88 throw new Error("Stop was not called.");
90 var stats
= this.finalStats_
;
91 for (var key
in stats
)
92 stats
[key
] -= this.initialStats_
[key
];
96 GpuBenchmarkingRenderingStats
.prototype.getRenderingStats_ = function() {
97 var stats
= chrome
.gpuBenchmarking
.renderingStats();
98 stats
.totalTimeInSeconds
= getTimeMs() / 1000;
103 * Tracks rendering performance using requestAnimationFrame.
106 function RafRenderingStats() {
107 this.recording_
= false;
108 this.frameTimes_
= [];
111 RafRenderingStats
.prototype.start = function() {
113 throw new Error("Already started.");
114 this.recording_
= true;
115 requestAnimationFrame(this.recordFrameTime_
.bind(this));
118 RafRenderingStats
.prototype.stop = function() {
119 this.recording_
= false;
122 RafRenderingStats
.prototype.getResult = function() {
124 result
.numAnimationFrames
= this.frameTimes_
.length
- 1;
125 result
.droppedFrameCount
= this.getDroppedFrameCount_(this.frameTimes_
);
126 result
.totalTimeInSeconds
= (this.frameTimes_
[this.frameTimes_
.length
- 1] -
127 this.frameTimes_
[0]) / 1000;
131 RafRenderingStats
.prototype.recordFrameTime_ = function(timestamp
) {
132 if (!this.recording_
)
135 this.frameTimes_
.push(timestamp
);
136 requestAnimationFrame(this.recordFrameTime_
.bind(this));
139 RafRenderingStats
.prototype.getDroppedFrameCount_ = function(frameTimes
) {
140 var droppedFrameCount
= 0;
141 for (var i
= 1; i
< frameTimes
.length
; i
++) {
142 var frameTime
= frameTimes
[i
] - frameTimes
[i
-1];
143 if (frameTime
> 1000 / 55)
146 return droppedFrameCount
;
149 // This class scrolls a page from the top to the bottom a given number of
152 // Each full transit of the page is called a "pass."
154 // The page is scrolled down by a set of scroll gestures. These gestures
155 // correspond to a reading gesture on that platform.
157 // i.e. for TOTAL_ITERATIONS_ = 2, we do
158 // start_ -> startPass_ -> ...scrolling... -> onGestureComplete_ ->
159 // -> startPass_ -> .. scrolling... -> onGestureComplete_ -> callback_
161 // TODO(nduca): This test starts in its constructor. That is strange. We
162 // should change it to start explicitly.
163 function ScrollTest(opt_callback
, opt_isGmailTest
) {
166 this.TOTAL_ITERATIONS_
= 2;
168 this.callback_
= opt_callback
;
169 this.isGmailTest_
= opt_isGmailTest
;
174 if (this.isGmailTest_
) {
175 gmonkey
.load('2.0', function(api
) {
176 self
.start_(api
.getScrollableElement());
179 if (document
.readyState
== 'complete')
182 window
.addEventListener('load', function() { self
.start_(); });
186 ScrollTest
.prototype.start_ = function(opt_element
) {
187 // Assign this.element_ here instead of constructor, because the constructor
188 // ensures this method will be called after the document is loaded.
189 this.element_
= opt_element
|| document
.body
;
190 requestAnimationFrame(this.startPass_
.bind(this));
193 ScrollTest
.prototype.startPass_ = function() {
194 this.element_
.scrollTop
= 0;
195 if (window
.chrome
&& chrome
.gpuBenchmarking
)
196 this.renderingStats_
= new GpuBenchmarkingRenderingStats();
198 this.renderingStats_
= new RafRenderingStats();
199 this.renderingStats_
.start();
201 this.gesture_
= new SmoothScrollDownGesture(this.element_
,
203 this.gesture_
.start(this.onGestureComplete_
.bind(this));
206 ScrollTest
.prototype.onGestureComplete_ = function(timestamp
) {
207 // clientHeight is "special" for the body element.
209 if (this.element_
== document
.body
)
210 clientHeight
= window
.innerHeight
;
212 clientHeight
= this.element_
.clientHeight
;
215 this.element_
.scrollTop
+ clientHeight
>= this.element_
.scrollHeight
;
217 if (!isPassComplete
) {
218 this.gesture_
.start(this.onGestureComplete_
.bind(this));
224 var isTestComplete
= this.iteration_
>= this.TOTAL_ITERATIONS_
;
225 if (!isTestComplete
) {
232 this.callback_(this.results_
);
234 console
.log(this.results_
);
237 ScrollTest
.prototype.endPass_ = function() {
238 this.renderingStats_
.stop();
239 this.results_
.push(this.renderingStats_
.getResult());
244 window
.__ScrollTest
= ScrollTest
;