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