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 new __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.processStep_ = function(timestamp) { |
| 151 // clientHeight is "special" for the body element. |
| 152 if (this.element_ == document.body) |
| 153 var clientHeight = window.innerHeight; |
| 154 else |
| 155 var clientHeight = this.element_.clientHeight; |
| 156 var isScrollComplete = |
| 157 this.element_.scrollTop + clientHeight >= this.element_.scrollHeight; |
| 158 |
| 159 if (!isScrollComplete) { |
| 160 this.startStep_(); |
| 161 return; |
| 162 } |
| 163 |
| 164 this.endScroll_(); |
| 165 |
| 166 var isTestComplete = this.iteration_ >= this.TOTAL_ITERATIONS_; |
| 167 if (!isTestComplete) { |
| 168 requestAnimationFrame(this.startScroll_.bind(this)); |
| 169 return; |
| 170 } |
| 171 |
| 172 // Send results. |
| 173 if (this.callback_) |
| 174 this.callback_(this.results_); |
| 175 else |
| 176 console.log(this.results_); |
| 177 }; |
| 178 |
| 179 window.__ScrollTest = ScrollTest; |
| 180 })(); |
OLD | NEW |