OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // Inject this script on any page to measure framerate as the page is scrolled | 5 // Inject this script on any page to measure framerate as the page is scrolled |
6 // twice from top to bottom. | 6 // from top to bottom. |
7 // | 7 // |
8 // USAGE: | 8 // Usage: |
9 // 1. To start the scrolling, invoke __scroll_test(). | 9 // 1. Define a callback that takes the results array as a parameter. |
10 // 2. Wait for __scrolling_complete to be true | 10 // 2. To start the test, call window.__scrollTest(callback). |
11 // 3. Read __frame_times array. | 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. | |
12 | 13 |
14 (function() { | |
15 var getTimeMs = (function() { | |
16 if (window.performance) | |
17 return (performance.now || | |
18 performance.mozNow || | |
19 performance.msNow || | |
20 performance.oNow || | |
21 performance.webkitNow).bind(window.performance); | |
22 else | |
23 return function() { return new Date().getTime(); }; | |
24 })(); | |
13 | 25 |
14 // Function that sets a callback called when the next frame is rendered. | 26 var requestAnimationFrame = (function() { |
15 var __set_frame_callback; | 27 return window.requestAnimationFrame || |
28 window.webkitRequestAnimationFrame || | |
29 window.mozRequestAnimationFrame || | |
30 window.oRequestAnimationFrame || | |
31 window.msRequestAnimationFrame || | |
32 function(callback) { | |
33 window.setTimeout(callback, 1000 / 60); | |
34 }; | |
35 })().bind(window); | |
16 | 36 |
17 // Function that scrolls the page. | 37 function GpuBenchmarkingScrollStats() { |
18 var __scroll_by; | 38 this.initialStats_ = this.getRenderingStats_(); |
39 } | |
19 | 40 |
20 // Element that can be scrolled. Must provide scrollTop property. | 41 GpuBenchmarkingScrollStats.prototype.getResult = function() { |
21 var __scrollable_element; | 42 var stats = this.getRenderingStats_(); |
43 for (var key in stats) | |
44 stats[key] -= this.initialStats_[key]; | |
45 return stats; | |
46 }; | |
22 | 47 |
23 // Amount of scrolling at each frame. | 48 GpuBenchmarkingScrollStats.prototype.getRenderingStats_ = function() { |
24 var __scroll_delta = 100; | 49 var stats = chrome.gpuBenchmarking.renderingStats(); |
50 stats.totalTimeInSeconds = getTimeMs() / 1000; | |
51 return stats; | |
52 }; | |
25 | 53 |
26 // Number of scrolls to perform. | 54 function RafScrollStats(timestamp) { |
27 var __num_scrolls = 2; | 55 this.frameTimes_ = [timestamp]; |
56 this.recording_ = true; | |
57 requestAnimationFrame(this.processStep_.bind(this)); | |
58 } | |
28 | 59 |
29 // Current scroll position. | 60 RafScrollStats.prototype.getResult = function() { |
30 var __ypos; | 61 this.recording_ = false; |
31 | 62 |
32 // Time of previous scroll callback execution. | 63 // Fill in the result object. |
33 var __start_time = 0; | 64 var result = {}; |
65 result.numAnimationFrames = this.frameTimes_.length - 1; | |
66 result.droppedFrameCount = this.getDroppedFrameCount_(this.frameTimes_); | |
67 result.totalTimeInSeconds = (this.frameTimes_[this.frameTimes_.length - 1] - | |
68 this.frameTimes_[0]) / 1000; | |
69 return result; | |
70 }; | |
34 | 71 |
35 // True when all scrolling has completed. | 72 RafScrollStats.prototype.processStep_ = function(timestamp) { |
36 var __scrolling_complete = false; | 73 if (!this.recording_) |
74 return; | |
37 | 75 |
38 // Array of frame times for each scroll in __num_scrolls. | 76 this.frameTimes_.push(timestamp); |
39 var __frame_times = [[]]; | 77 requestAnimationFrame(this.processStep_.bind(this)); |
78 }; | |
40 | 79 |
41 // Set this to true when scrolling in Gmail. | 80 RafScrollStats.prototype.getDroppedFrameCount_ = function(frameTimes) { |
42 var __is_gmail_test = false; | 81 var droppedFrameCount = 0; |
82 for (var i = 1; i < frameTimes.length; i++) { | |
83 var frameTime = frameTimes[i] - frameTimes[i-1]; | |
84 if (frameTime > 1000 / 55) | |
85 droppedFrameCount++; | |
86 } | |
87 return droppedFrameCount; | |
88 }; | |
43 | 89 |
90 // In this class, a "step" is when the page is being scrolled. | |
91 // i.e. startScroll -> startStep -> scroll -> processStep -> | |
92 // -> startStep -> scroll -> processStep -> endScroll | |
93 function ScrollTest(callback, opt_isGmailTest) { | |
94 var self = this; | |
44 | 95 |
45 // Initializes the platform-independent frame callback scheduler function. | 96 this.TOTAL_ITERATIONS_ = 2; |
46 function __init_set_frame_callback() { | 97 this.SCROLL_DELTA_ = 100; |
47 __set_frame_callback = window.requestAnimationFrame || | |
48 window.mozRequestAnimationFrame || | |
49 window.msRequestAnimationFrame || | |
50 window.oRequestAnimationFrame || | |
51 window.webkitRequestAnimationFrame; | |
52 } | |
53 | 98 |
99 this.callback_ = callback; | |
100 this.isGmailTest_ = opt_isGmailTest; | |
101 this.iteration_ = 0; | |
54 | 102 |
55 // Initializes the most realistic scrolling method. | 103 this.results_ = [] |
56 function __init_scroll_by() { | |
57 if (__is_gmail_test) { | |
58 __scroll_by = function(x, y) { | |
59 __scrollable_element.scrollByLines(1); | |
60 }; | |
61 } else if (window.chrome && window.chrome.benchmarking && | |
62 window.chrome.benchmarking.smoothScrollBy) { | |
63 __scroll_by = window.chrome.benchmarking.smoothScrollBy; | |
64 } else { | |
65 __scroll_by = window.scrollBy; | |
66 } | |
67 } | |
68 | 104 |
69 | 105 if (this.isGmailTest_) { |
70 // Scrolls page down and reschedules itself until it hits the bottom. | 106 gmonkey.load('2.0', function(api) { |
71 // Collects stats along the way. | 107 self.start_(api.getScrollableElement()); |
72 function __do_scroll(now_time) { | 108 }); |
73 __scroll_by(0, __scroll_delta); | |
74 __set_frame_callback(function(now_time) { | |
75 if (__start_time) { | |
76 if (__scrollable_element.scrollTop > __ypos) { | |
77 // Scroll in progress, push a frame. | |
78 __frame_times[__frame_times.length-1].push(now_time - __start_time); | |
79 } else { | |
80 // Scroll complete, either scroll again or finish. | |
81 if (__frame_times.length < __num_scrolls) { | |
82 __scrollable_element.scrollTop = 0; | |
83 __frame_times.push([]); | |
84 } else { | |
85 console.log('frame_times', '' + __frame_times); | |
86 __scrolling_complete = true; | |
87 return; | |
88 } | |
89 } | |
90 } | |
91 __ypos = __scrollable_element.scrollTop; | |
92 __start_time = now_time; | |
93 __do_scroll(); | |
94 }); | |
95 } | |
96 | |
97 | |
98 function __start_scroll(scrollable_element) { | |
99 __scrollable_element = scrollable_element; | |
100 __init_scroll_by(); | |
101 __set_frame_callback(__do_scroll); | |
102 } | |
103 | |
104 | |
105 // Performs the scroll test. | |
106 function __scroll_test() { | |
107 __init_set_frame_callback(); | |
108 if (__is_gmail_test) { | |
109 gmonkey.load("2.0", function(api) { | |
110 __start_scroll(api.getScrollableElement()); | |
111 }); | |
112 } else { | |
113 if (window.performance.timing.loadEventStart) { | |
114 __start_scroll(document.body); // Page already loaded. | |
115 } else { | 109 } else { |
116 window.addEventListener('load', function() { | 110 if (document.readyState == 'complete') |
117 __start_scroll(document.body); // Page hasn't loaded yet, schedule. | 111 this.start_(); |
118 }); | 112 else |
113 window.addEventListener('load', function() { self.start_(); }); | |
119 } | 114 } |
120 } | 115 } |
121 } | 116 |
117 ScrollTest.prototype.scroll_ = function(x, y) { | |
118 if (this.isGmailTest_) | |
119 this.element_.scrollByLines(1); | |
120 else | |
121 window.scrollBy(x, y); | |
122 }; | |
123 | |
124 ScrollTest.prototype.start_ = function(opt_element) { | |
125 // Assign this.element_ here instead of constructor, because the constructor | |
126 // ensures this method will be called after the document is loaded. | |
127 this.element_ = opt_element || document.body; | |
128 requestAnimationFrame(this.startScroll_.bind(this)); | |
129 }; | |
130 | |
131 ScrollTest.prototype.startScroll_ = function(timestamp) { | |
132 this.element_.scrollTop = 0; | |
133 if (window.chrome && chrome.gpuBenchmarking) | |
134 this.scrollStats_ = new GpuBenchmarkingScrollStats(); | |
135 else | |
136 this.scrollStats_ = new RafScrollStats(timestamp); | |
137 this.startStep_(); | |
138 }; | |
139 | |
140 ScrollTest.prototype.startStep_ = function() { | |
141 this.scroll_(0, this.SCROLL_DELTA_); | |
142 requestAnimationFrame(this.processStep_.bind(this)); | |
143 }; | |
144 | |
145 ScrollTest.prototype.endScroll_ = function() { | |
146 this.results_.push(this.scrollStats_.getResult()); | |
147 this.iteration_++; | |
148 }; | |
149 | |
150 ScrollTest.prototype.sendResults_ = function() { | |
nduca
2012/08/16 02:42:59
you can probably just inline this once you clean u
dtu
2012/08/16 19:12:39
Done.
| |
151 if (this.callback_) | |
152 this.callback_(this.results_); | |
153 else | |
154 console.log(this.results_); | |
155 }; | |
156 | |
157 ScrollTest.prototype.processStep_ = function(timestamp) { | |
158 // clientHeight is "special" for the body element. | |
159 if (this.element_ == document.body) | |
160 var clientHeight = window.innerHeight; | |
161 else | |
162 var clientHeight = this.element_.clientHeight; | |
163 | |
164 var isScrollComplete = | |
165 this.element_.scrollTop + clientHeight >= this.element_.scrollHeight; | |
166 if (isScrollComplete) { | |
nduca
2012/08/16 02:42:59
you can restructure this to be if (!scrollcomplete
dtu
2012/08/16 19:12:39
Done.
| |
167 this.endScroll_(); | |
168 | |
169 var isTestComplete = this.iteration_ >= this.TOTAL_ITERATIONS_; | |
170 if (isTestComplete) | |
171 this.sendResults_(); | |
172 else | |
173 requestAnimationFrame(this.startScroll_.bind(this)); | |
174 | |
175 return; | |
176 } | |
177 | |
178 this.startStep_(); | |
179 }; | |
180 | |
181 window.__scrollTest = function(callback, opt_isGmailTest) { | |
nduca
2012/08/16 02:42:59
How about exposing the constructor directly as win
dtu
2012/08/16 19:12:39
Done.
| |
182 new ScrollTest(callback, opt_isGmailTest); | |
183 } | |
184 })(); | |
OLD | NEW |