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;