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 // This file provides the ScrollAction object, which scrolls a page 6 // from top to bottom: 7 // 1. var action = new __ScrollAction(callback) 8 // 2. action.start(scroll_options) 9 'use strict'; 10 11 (function() { 12 var MAX_SCROLL_LENGTH_PIXELS = 5000; 13 14 var getTimeMs = (function() { 15 if (window.performance) 16 return (performance.now || 17 performance.mozNow || 18 performance.msNow || 19 performance.oNow || 20 performance.webkitNow).bind(window.performance); 21 else 22 return function() { return new Date().getTime(); }; 23 })(); 24 25 var requestAnimationFrame = (function() { 26 return window.requestAnimationFrame || 27 window.webkitRequestAnimationFrame || 28 window.mozRequestAnimationFrame || 29 window.oRequestAnimationFrame || 30 window.msRequestAnimationFrame || 31 function(callback) { 32 window.setTimeout(callback, 1000 / 60); 33 }; 34 })().bind(window); 35 36 function ScrollGestureOptions(opt_options) { 37 if (opt_options) { 38 this.element_ = opt_options.element; 39 this.left_start_percentage_ = opt_options.left_start_percentage; 40 this.top_start_percentage_ = opt_options.top_start_percentage; 41 } else { 42 this.element_ = document.body; 43 this.left_start_percentage_ = 0.5; 44 this.top_start_percentage_ = 0.5; 45 } 46 } 47 48 /** 49 * Scrolls a given element down a certain amount to emulate user scroll. 50 * Uses smooth scroll capabilities provided by the platform, if available. 51 * @constructor 52 */ 53 function SmoothScrollDownGesture(options) { 54 this.options_ = options; 55 }; 56 57 function min(a, b) { 58 if (a > b) { 59 return b; 60 } 61 return a; 62 }; 63 64 function getBoundingVisibleRect(el) { 65 var bound = el.getBoundingClientRect(); 66 var rect = { top: bound.top, 67 left: bound.left, 68 width: bound.width, 69 height: bound.height }; 70 var outsideHeight = (rect.top + rect.height) - window.innerHeight; 71 var outsideWidth = (rect.left + rect.width) - window.innerWidth; 72 73 if (outsideHeight > 0) { 74 rect.height -= outsideHeight; 75 } 76 if (outsideWidth > 0) { 77 rect.width -= outsideWidth; 78 } 79 return rect; 80 }; 81 82 SmoothScrollDownGesture.prototype.start = function(distance, callback) { 83 this.callback_ = callback; 84 if (window.chrome && 85 chrome.gpuBenchmarking && 86 chrome.gpuBenchmarking.smoothScrollBy) { 87 var rect = getBoundingVisibleRect(this.options_.element_); 88 var start_left = 89 rect.left + rect.width * this.options_.left_start_percentage_; 90 var start_top = 91 rect.top + rect.height * this.options_.top_start_percentage_; 92 chrome.gpuBenchmarking.smoothScrollBy(distance, function() { 93 callback(); 94 }, start_left, start_top); 95 return; 96 } 97 98 var SCROLL_DELTA = 100; 99 this.options_.element_.scrollTop += SCROLL_DELTA; 100 requestAnimationFrame(callback); 101 }; 102 103 // This class scrolls a page from the top to the bottom once. 104 // 105 // The page is scrolled down by a set of scroll gestures. These gestures 106 // correspond to a reading gesture on that platform. 107 // 108 // start -> startPass_ -> ...scroll... -> onGestureComplete_ -> 109 // -> startPass_ -> .. scroll... -> onGestureComplete_ -> callback_ 110 function ScrollAction(opt_callback, opt_remaining_distance_func) { 111 var self = this; 112 113 this.beginMeasuringHook = function() {} 114 this.endMeasuringHook = function() {} 115 116 this.callback_ = opt_callback; 117 this.remaining_distance_func_ = opt_remaining_distance_func; 118 } 119 120 ScrollAction.prototype.getRemainingScrollDistance_ = function() { 121 if (this.remaining_distance_func_) 122 return this.remaining_distance_func_(); 123 124 var clientHeight; 125 // clientHeight is "special" for the body element. 126 if (this.element_ == document.body) 127 clientHeight = window.innerHeight; 128 else 129 clientHeight = this.element_.clientHeight; 130 131 return this.scrollHeight_ - this.element_.scrollTop - clientHeight; 132 } 133 134 ScrollAction.prototype.start = function(opt_options) { 135 this.options_ = new ScrollGestureOptions(opt_options); 136 // Assign this.element_ here instead of constructor, because the constructor 137 // ensures this method will be called after the document is loaded. 138 this.element_ = this.options_.element_; 139 // Some pages load more content when you scroll to the bottom. Record 140 // the original element height here and only scroll to that point. 141 // -1 to allow for rounding errors on scaled viewports (like mobile). 142 this.scrollHeight_ = Math.min(MAX_SCROLL_LENGTH_PIXELS, 143 this.element_.scrollHeight - 1); 144 requestAnimationFrame(this.startPass_.bind(this)); 145 }; 146 147 ScrollAction.prototype.startPass_ = function() { 148 this.element_.scrollTop = 0; 149 150 this.beginMeasuringHook(); 151 152 this.gesture_ = new SmoothScrollDownGesture(this.options_); 153 this.gesture_.start(this.getRemainingScrollDistance_(), 154 this.onGestureComplete_.bind(this)); 155 }; 156 157 ScrollAction.prototype.getResults = function() { 158 return this.renderingStats_; 159 } 160 161 ScrollAction.prototype.onGestureComplete_ = function() { 162 // If the scrollHeight went down, only scroll to the new scrollHeight. 163 // -1 to allow for rounding errors on scaled viewports (like mobile). 164 this.scrollHeight_ = Math.min(this.scrollHeight_, 165 this.element_.scrollHeight - 1); 166 167 if (this.getRemainingScrollDistance_() > 0) { 168 this.gesture_.start(this.getRemainingScrollDistance_(), 169 this.onGestureComplete_.bind(this)); 170 return; 171 } 172 173 this.endMeasuringHook(); 174 175 // We're done. 176 if (this.callback_) 177 this.callback_(); 178 }; 179 180 window.__ScrollAction = ScrollAction; 181 window.__ScrollAction_GetBoundingVisibleRect = getBoundingVisibleRect; 182 })(); 183