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, instantiate a new ScrollTest(). |
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 will be sent in | |
13 // JSON format through window.domAutomationController. | |
14 // 3c. If there is no domAC, the results are sent to the console. | |
12 | 15 |
16 window.performance = window.performance || {}; | |
nduca
2012/08/14 00:24:34
Its probably bad that we're mutating the window ob
dtu
2012/08/16 02:31:10
Done.
| |
17 performance.now = (function() { | |
18 return performance.now || | |
19 performance.mozNow || | |
20 performance.msNow || | |
21 performance.oNow || | |
22 performance.webkitNow || | |
23 function() { return new Date().getTime(); }; | |
24 })(); | |
13 | 25 |
14 // Function that sets a callback called when the next frame is rendered. | 26 window.requestAnimationFrame = (function(){ |
nduca
2012/08/14 00:24:34
same comment about mutating
dtu
2012/08/16 02:31:10
Done.
| |
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 })(); | |
16 | 36 |
17 // Function that scrolls the page. | 37 var __isGmailTest = false; |
18 var __scroll_by; | |
19 | 38 |
20 // Element that can be scrolled. Must provide scrollTop property. | 39 function BenchmarkingScrollStats() { |
nduca
2012/08/14 00:24:34
GpuBenchmarkingScrollStats
dtu
2012/08/16 02:31:10
Done.
| |
21 var __scrollable_element; | 40 this.results = []; |
22 | |
23 // Amount of scrolling at each frame. | |
24 var __scroll_delta = 100; | |
25 | |
26 // Number of scrolls to perform. | |
27 var __num_scrolls = 2; | |
28 | |
29 // Current scroll position. | |
30 var __ypos; | |
31 | |
32 // Time of previous scroll callback execution. | |
33 var __start_time = 0; | |
34 | |
35 // True when all scrolling has completed. | |
36 var __scrolling_complete = false; | |
37 | |
38 // Array of frame times for each scroll in __num_scrolls. | |
39 var __frame_times = [[]]; | |
40 | |
41 // Set this to true when scrolling in Gmail. | |
42 var __is_gmail_test = false; | |
43 | |
44 | |
45 // Initializes the platform-independent frame callback scheduler function. | |
46 function __init_set_frame_callback() { | |
47 __set_frame_callback = window.requestAnimationFrame || | |
48 window.mozRequestAnimationFrame || | |
49 window.msRequestAnimationFrame || | |
50 window.oRequestAnimationFrame || | |
51 window.webkitRequestAnimationFrame; | |
52 } | 41 } |
53 | 42 |
43 BenchmarkingScrollStats.prototype.getRenderingStats = function() { | |
nduca
2012/08/14 00:24:34
doesn't seem like it needs to be a member function
dtu
2012/08/16 02:31:10
Is it better in JavaScript to put it outside? RafS
| |
44 var stats = chrome.gpuBenchmarking.renderingStats(); | |
45 stats.totalTimeInSeconds = window.performance.now() / 1000; | |
nduca
2012/08/14 00:24:34
timestampWhenObtained?
dtu
2012/08/16 02:31:10
As is, it is more consistent with totalPaintTimeIn
| |
46 return stats; | |
47 }; | |
54 | 48 |
55 // Initializes the most realistic scrolling method. | 49 BenchmarkingScrollStats.prototype.getStatsDiff = function(old_stats) { |
56 function __init_scroll_by() { | 50 var stats = this.getRenderingStats(); |
57 if (__is_gmail_test) { | 51 for (var key in stats) |
58 __scroll_by = function(x, y) { | 52 stats[key] -= old_stats[key]; |
59 __scrollable_element.scrollByLines(1); | 53 return stats; |
60 }; | 54 }; |
61 } else if (window.chrome && window.chrome.benchmarking && | 55 |
62 window.chrome.benchmarking.smoothScrollBy) { | 56 BenchmarkingScrollStats.prototype.startScroll = function(timestamp) { |
63 __scroll_by = window.chrome.benchmarking.smoothScrollBy; | 57 this.initialStats = this.getRenderingStats(); |
58 }; | |
59 | |
60 BenchmarkingScrollStats.prototype.endStep = function(timestamp) {} | |
61 | |
62 BenchmarkingScrollStats.prototype.endScroll = function() { | |
63 this.results.push(this.getStatsDiff(this.initialStats)); | |
nduca
2012/08/14 00:24:34
do we need to support more than one results?
dtu
2012/08/16 02:31:10
Done.
| |
64 }; | |
65 | |
66 BenchmarkingScrollStats.prototype.getResults = function() { | |
67 return this.results; | |
68 }; | |
69 | |
70 function RAFScrollStats() { | |
nduca
2012/08/14 00:24:34
this class should set up its own raf loop that rec
dtu
2012/08/16 02:31:10
Done. Huh, hadn't even occurred to me. That makes
| |
71 this.results = []; | |
72 } | |
73 | |
74 RAFScrollStats.prototype.getDroppedFrameCount = function(frameTimes) { | |
75 var droppedFrameCount = 0; | |
76 for (var i = 1; i < frameTimes.length; i++) { | |
77 var frameTime = frameTimes[i] - frameTimes[i-1]; | |
78 if (frameTime > 1000 / 55) | |
79 droppedFrameCount++; | |
80 } | |
81 return droppedFrameCount; | |
82 }; | |
83 | |
84 RAFScrollStats.prototype.startScroll = function(timestamp) { | |
85 this.frameTimes = [timestamp]; | |
86 }; | |
87 | |
88 RAFScrollStats.prototype.endStep = function(timestamp) { | |
89 this.frameTimes.push(timestamp); | |
90 }; | |
91 | |
92 RAFScrollStats.prototype.endScroll = function() { | |
93 var scrollResult = {}; | |
94 scrollResult.numAnimationFrames = this.frameTimes.length - 1; | |
95 scrollResult.droppedFrameCount = this.getDroppedFrameCount(this.frameTimes); | |
96 scrollResult.totalTimeInSeconds = | |
97 (this.frameTimes[this.frameTimes.length - 1] - this.frameTimes[0]) / 1000; | |
98 this.results.push(scrollResult); | |
99 }; | |
100 | |
101 RAFScrollStats.prototype.getResults = function() { | |
102 return this.results; | |
103 }; | |
104 | |
105 // In the terminology of this class, a "step" is when the page | |
106 // is being scrolled. processStep is run between the steps. | |
107 // i.e. startScroll -> startStep -> scroll -> endStep -> | |
108 // processStep -> startStep -> scroll -> endStep -> endScroll | |
109 function ScrollTest(callback) { | |
110 var self = this; | |
111 | |
112 this.TOTAL_ITERATIONS = 2; | |
113 this.SCROLL_DELTA = 100; | |
114 | |
115 this.callback = callback; | |
116 this.iteration = 0; | |
117 | |
118 if (window.chrome && chrome.gpuBenchmarking) | |
119 this.scrollStats = new BenchmarkingScrollStats(); | |
nduca
2012/08/14 00:24:34
seems like this should be a private var, and just
dtu
2012/08/16 02:31:10
Not sure what you're saying. A getter is not neede
| |
120 else | |
121 this.scrollStats = new RAFScrollStats; | |
nduca
2012/08/14 00:24:34
any reason for not ()?
dtu
2012/08/16 02:31:10
Done.
| |
122 | |
123 if (__isGmailTest) { | |
nduca
2012/08/14 00:24:34
feels like this could be a construction time varia
dtu
2012/08/16 02:31:10
Done.
| |
124 gmonkey.load('2.0', function(api) { | |
125 self.start(api.getScrollableElement()); | |
126 }); | |
64 } else { | 127 } else { |
65 __scroll_by = window.scrollBy; | 128 if (document.readyState == 'complete') |
129 this.start(); | |
130 else | |
131 window.addEventListener('load', function() { self.start(); }); | |
66 } | 132 } |
67 } | 133 } |
68 | 134 |
135 ScrollTest.prototype.scroll = function(x, y) { | |
136 if (__isGmailTest) | |
137 this.element.scrollByLines(1); | |
138 else | |
139 window.scrollBy(x, y); | |
140 }; | |
69 | 141 |
70 // Scrolls page down and reschedules itself until it hits the bottom. | 142 ScrollTest.prototype.start = function(element) { |
71 // Collects stats along the way. | 143 this.element = element || document.body; |
72 function __do_scroll(now_time) { | 144 window.requestAnimationFrame(this.startScroll.bind(this)); |
73 __scroll_by(0, __scroll_delta); | 145 }; |
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 | 146 |
147 ScrollTest.prototype.startScroll = function(timestamp) { | |
148 this.element.scrollTop = 0; | |
149 this.scrollStats.startScroll(timestamp); | |
150 this.startStep(); | |
151 }; | |
97 | 152 |
98 function __start_scroll(scrollable_element) { | 153 ScrollTest.prototype.startStep = function() { |
99 __scrollable_element = scrollable_element; | 154 this.scroll(0, this.SCROLL_DELTA); |
100 __init_scroll_by(); | 155 window.requestAnimationFrame(this.processStep.bind(this)); |
101 __set_frame_callback(__do_scroll); | 156 }; |
102 } | |
103 | 157 |
158 ScrollTest.prototype.endStep = function(timestamp) { | |
159 this.scrollStats.endStep(timestamp); | |
nduca
2012/08/14 00:24:34
remove this
dtu
2012/08/16 02:31:10
Done.
| |
160 }; | |
104 | 161 |
105 // Performs the scroll test. | 162 ScrollTest.prototype.endScroll = function() { |
106 function __scroll_test() { | 163 this.scrollStats.endScroll(); |
107 __init_set_frame_callback(); | 164 this.iteration++; |
108 if (__is_gmail_test) { | 165 }; |
109 gmonkey.load("2.0", function(api) { | 166 |
110 __start_scroll(api.getScrollableElement()); | 167 ScrollTest.prototype.sendResults = function() { |
111 }); | 168 var results = this.scrollStats.getResults(); |
112 } else { | 169 if (this.callback) |
113 if (window.performance.timing.loadEventStart) { | 170 this.callback(results); |
114 __start_scroll(document.body); // Page already loaded. | 171 else if (window.domAutomationController) |
nduca
2012/08/14 00:24:34
Does anything still use this send case? I wonder i
dtu
2012/08/16 02:31:10
Done. On the Python side that looks like:
__Scroll
| |
115 } else { | 172 window.domAutomationController.send(JSON.stringify(results)); |
116 window.addEventListener('load', function() { | 173 else |
117 __start_scroll(document.body); // Page hasn't loaded yet, schedule. | 174 console.log(results); |
118 }); | 175 }; |
119 } | 176 |
177 ScrollTest.prototype.processStep = function(timestamp) { | |
178 this.endStep(timestamp); | |
nduca
2012/08/14 00:24:34
seems like we should remove this. RAF loop that dr
dtu
2012/08/16 02:31:10
Done.
| |
179 | |
180 // clientHeight is "special" for the body element. | |
181 if (this.element == document.body) | |
182 var clientHeight = window.innerHeight; | |
183 else | |
184 var clientHeight = this.element.clientHeight; | |
185 | |
186 var isScrollComplete = | |
187 this.element.scrollTop + clientHeight >= this.element.scrollHeight; | |
188 if (isScrollComplete) { | |
189 this.endScroll(); | |
190 | |
191 var isTestComplete = this.iteration >= this.TOTAL_ITERATIONS; | |
192 if (isTestComplete) | |
193 this.sendResults(); | |
194 else | |
195 window.requestAnimationFrame(this.startScroll.bind(this)); | |
196 | |
197 return; | |
120 } | 198 } |
121 } | 199 |
200 this.startStep(); | |
201 }; | |
OLD | NEW |