| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 // Inject this script on any page to measure framerate as the page is scrolled | |
| 6 // from top to bottom. | |
| 7 // | |
| 8 // Usage: | |
| 9 // 1. Define a callback that takes a RenderingStats object 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 is sent to the console. | |
| 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 })(); | |
| 25 | |
| 26 var requestAnimationFrame = (function() { | |
| 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); | |
| 36 | |
| 37 /** | |
| 38 * Scrolls a given element down a certain amount to emulate user scrolling. | |
| 39 * Uses smooth scrolling capabilities provided by the platform, if available. | |
| 40 * @constructor | |
| 41 */ | |
| 42 function SmoothScrollDownGesture(opt_element) { | |
| 43 this.element_ = opt_element || document.body; | |
| 44 }; | |
| 45 | |
| 46 function min(a, b) { | |
| 47 if (a > b) { | |
| 48 return b; | |
| 49 } | |
| 50 return a; | |
| 51 }; | |
| 52 | |
| 53 function getBoundingVisibleRect(el) { | |
| 54 r = el.getBoundingClientRect(); | |
| 55 cur = el; | |
| 56 while (cur && cur.parentElement) { | |
| 57 r.top += cur.parentElement.offsetTop; | |
| 58 r.left += cur.parentElement.offsetLeft; | |
| 59 r.height = min(r.height, cur.parentElement.offsetHeight); | |
| 60 r.width = min(r.width, cur.parentElement.offsetWidth); | |
| 61 cur = cur.parentElement; | |
| 62 } | |
| 63 return r; | |
| 64 }; | |
| 65 | |
| 66 function rectsDoIntersect(r1, r2) { | |
| 67 return (r1.right >= r2.left && | |
| 68 r1.left <= r2.right && | |
| 69 r1.bottom >= r2.top && | |
| 70 r1.top <= r2.bottom); | |
| 71 }; | |
| 72 | |
| 73 SmoothScrollDownGesture.prototype.start = function(callback) { | |
| 74 this.callback_ = callback; | |
| 75 if (chrome && | |
| 76 chrome.gpuBenchmarking && | |
| 77 chrome.gpuBenchmarking.beginSmoothScrollDown) { | |
| 78 rect = getBoundingVisibleRect(this.element_); | |
| 79 if (chrome.gpuBenchmarking.beginSmoothScrollSupportsPositioning || | |
| 80 rectsDoIntersect(rect, {left: 0, | |
| 81 top: 0, | |
| 82 right: 1, | |
| 83 bottom: 1, | |
| 84 height: 1, | |
| 85 width: 1})) { | |
| 86 chrome.gpuBenchmarking.beginSmoothScrollDown(true, function() { | |
| 87 callback(); | |
| 88 }, rect.left + rect.width / 2, rect.top + rect.height / 2); | |
| 89 return; | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 var SCROLL_DELTA = 100; | |
| 94 this.element_.scrollTop += SCROLL_DELTA; | |
| 95 requestAnimationFrame(callback); | |
| 96 }; | |
| 97 | |
| 98 /** | |
| 99 * Tracks rendering performance using the gpuBenchmarking.renderingStats API. | |
| 100 * @constructor | |
| 101 */ | |
| 102 function GpuBenchmarkingRenderingStats() { | |
| 103 } | |
| 104 | |
| 105 GpuBenchmarkingRenderingStats.prototype.start = function() { | |
| 106 this.initialStats_ = this.getRenderingStats_(); | |
| 107 } | |
| 108 GpuBenchmarkingRenderingStats.prototype.stop = function() { | |
| 109 this.finalStats_ = this.getRenderingStats_(); | |
| 110 } | |
| 111 | |
| 112 GpuBenchmarkingRenderingStats.prototype.getDeltas = function() { | |
| 113 if (!this.initialStats_) | |
| 114 throw new Error('Start not called.'); | |
| 115 | |
| 116 if (!this.finalStats_) | |
| 117 throw new Error('Stop was not called.'); | |
| 118 | |
| 119 var stats = this.finalStats_; | |
| 120 for (var key in stats) | |
| 121 stats[key] -= this.initialStats_[key]; | |
| 122 return stats; | |
| 123 }; | |
| 124 | |
| 125 GpuBenchmarkingRenderingStats.prototype.getRenderingStats_ = function() { | |
| 126 var stats = chrome.gpuBenchmarking.renderingStats(); | |
| 127 stats.totalTimeInSeconds = getTimeMs() / 1000; | |
| 128 return stats; | |
| 129 }; | |
| 130 | |
| 131 /** | |
| 132 * Tracks rendering performance using requestAnimationFrame. | |
| 133 * @constructor | |
| 134 */ | |
| 135 function RafRenderingStats() { | |
| 136 this.recording_ = false; | |
| 137 this.frameTimes_ = []; | |
| 138 } | |
| 139 | |
| 140 RafRenderingStats.prototype.start = function() { | |
| 141 if (this.recording_) | |
| 142 throw new Error('Already started.'); | |
| 143 this.recording_ = true; | |
| 144 requestAnimationFrame(this.recordFrameTime_.bind(this)); | |
| 145 } | |
| 146 | |
| 147 RafRenderingStats.prototype.stop = function() { | |
| 148 this.recording_ = false; | |
| 149 } | |
| 150 | |
| 151 RafRenderingStats.prototype.getDeltas = function() { | |
| 152 var results = {}; | |
| 153 results.numAnimationFrames = this.frameTimes_.length - 1; | |
| 154 results.numFramesSentToScreen = results.numAnimationFrames; | |
| 155 results.droppedFrameCount = this.getDroppedFrameCount_(this.frameTimes_); | |
| 156 results.totalTimeInSeconds = ( | |
| 157 this.frameTimes_[this.frameTimes_.length - 1] - | |
| 158 this.frameTimes_[0]) / 1000; | |
| 159 return results; | |
| 160 }; | |
| 161 | |
| 162 RafRenderingStats.prototype.recordFrameTime_ = function(timestamp) { | |
| 163 if (!this.recording_) | |
| 164 return; | |
| 165 | |
| 166 this.frameTimes_.push(timestamp); | |
| 167 requestAnimationFrame(this.recordFrameTime_.bind(this)); | |
| 168 }; | |
| 169 | |
| 170 RafRenderingStats.prototype.getDroppedFrameCount_ = function(frameTimes) { | |
| 171 var droppedFrameCount = 0; | |
| 172 for (var i = 1; i < frameTimes.length; i++) { | |
| 173 var frameTime = frameTimes[i] - frameTimes[i-1]; | |
| 174 if (frameTime > 1000 / 55) | |
| 175 droppedFrameCount++; | |
| 176 } | |
| 177 return droppedFrameCount; | |
| 178 }; | |
| 179 | |
| 180 // This class scrolls a page from the top to the bottom once. | |
| 181 // | |
| 182 // The page is scrolled down by a set of scroll gestures. These gestures | |
| 183 // correspond to a reading gesture on that platform. | |
| 184 // | |
| 185 // start -> startPass_ -> ...scrolling... -> onGestureComplete_ -> | |
| 186 // -> startPass_ -> .. scrolling... -> onGestureComplete_ -> callback_ | |
| 187 function ScrollTest(opt_callback) { | |
| 188 var self = this; | |
| 189 | |
| 190 this.callback_ = opt_callback; | |
| 191 } | |
| 192 | |
| 193 ScrollTest.prototype.start = function(opt_element) { | |
| 194 // Assign this.element_ here instead of constructor, because the constructor | |
| 195 // ensures this method will be called after the document is loaded. | |
| 196 this.element_ = opt_element || document.body; | |
| 197 // Some pages load more content when you scroll to the bottom. Record | |
| 198 // the original element height here and only scroll to that point. | |
| 199 this.scrollHeight_ = this.element_.scrollHeight | |
| 200 requestAnimationFrame(this.startPass_.bind(this)); | |
| 201 }; | |
| 202 | |
| 203 ScrollTest.prototype.startPass_ = function() { | |
| 204 this.element_.scrollTop = 0; | |
| 205 if (window.chrome && chrome.gpuBenchmarking && | |
| 206 chrome.gpuBenchmarking.renderingStats) | |
| 207 this.renderingStats_ = new GpuBenchmarkingRenderingStats(); | |
| 208 else | |
| 209 this.renderingStats_ = new RafRenderingStats(); | |
| 210 this.renderingStats_.start(); | |
| 211 | |
| 212 this.gesture_ = new SmoothScrollDownGesture(this.element_); | |
| 213 this.gesture_.start(this.onGestureComplete_.bind(this)); | |
| 214 }; | |
| 215 | |
| 216 ScrollTest.prototype.onGestureComplete_ = function(timestamp) { | |
| 217 // clientHeight is "special" for the body element. | |
| 218 var clientHeight; | |
| 219 if (this.element_ == document.body) | |
| 220 clientHeight = window.innerHeight; | |
| 221 else | |
| 222 clientHeight = this.element_.clientHeight; | |
| 223 | |
| 224 // If the scrollHeight went down, only scroll to the new scrollHeight. | |
| 225 this.scrollHeight_ = Math.min(this.scrollHeight_, | |
| 226 this.element_.scrollHeight); | |
| 227 | |
| 228 // -1 to allow for rounding errors on scaled viewports (like mobile). | |
| 229 var isPassComplete = | |
| 230 this.element_.scrollTop + clientHeight >= this.scrollHeight_ - 1; | |
| 231 | |
| 232 if (!isPassComplete) { | |
| 233 this.gesture_.start(this.onGestureComplete_.bind(this)); | |
| 234 return; | |
| 235 } | |
| 236 | |
| 237 this.endPass_(); | |
| 238 | |
| 239 // We're done. | |
| 240 if (this.callback_) | |
| 241 this.callback_(this.renderingStats_.getDeltas()); | |
| 242 else | |
| 243 console.log(this.renderingStats_.getDeltas()); | |
| 244 }; | |
| 245 | |
| 246 ScrollTest.prototype.endPass_ = function() { | |
| 247 this.renderingStats_.stop(); | |
| 248 }; | |
| 249 | |
| 250 | |
| 251 window.__ScrollTest = ScrollTest; | |
| 252 })(); | |
| OLD | NEW |