1# Copyright 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 5from telemetry import decorators 6from telemetry.internal.actions import page_action 7from telemetry.internal.actions import scroll 8from telemetry.internal.actions import utils 9from telemetry.testing import tab_test_case 10 11 12class ScrollActionTest(tab_test_case.TabTestCase): 13 14 def _MakePageVerticallyScrollable(self): 15 # Make page taller than window so it's scrollable vertically. 16 self._tab.ExecuteJavaScript( 17 'document.body.style.height =' 18 '(3 * __GestureCommon_GetWindowHeight() + 1) + "px";') 19 20 def _MakePageHorizontallyScrollable(self): 21 # Make page wider than window so it's scrollable horizontally. 22 self._tab.ExecuteJavaScript( 23 'document.body.style.width =' 24 '(3 * __GestureCommon_GetWindowWidth() + 1) + "px";') 25 26 def setUp(self): 27 tab_test_case.TabTestCase.setUp(self) 28 self.Navigate('blank.html') 29 utils.InjectJavaScript(self._tab, 'gesture_common.js') 30 31 def _RunScrollDistanceTest(self, distance, speed, source, maxError): 32 # TODO(bokan): Distance tests will fail on versions of Chrome that haven't 33 # been fixed. The fixes landed at the same time as the 34 # setBrowserControlsShown method was added so only run the test if that's 35 # available. Once that rolls into ref builds we can remove this check. 36 distanceFixedInChrome = self._tab.EvaluateJavaScript( 37 "'setBrowserControlsShown' in chrome.gpuBenchmarking") 38 if not distanceFixedInChrome: 39 return 40 41 # Hide the URL bar so we can measure scrolled distance without worrying 42 # about the URL bar consuming delta. 43 self._tab.ExecuteJavaScript( 44 'chrome.gpuBenchmarking.setBrowserControlsShown(false);') 45 46 # Make the document tall enough to accomodate the requested distance but 47 # also leave enough space so we can tell if the scroll overshoots the 48 # target. 49 screenHeight = self._tab.EvaluateJavaScript('window.visualViewport.height') 50 documentHeight = (screenHeight + distance) * 2 51 52 self._tab.ExecuteJavaScript( 53 'document.body.style.height = "' + str(documentHeight) + 'px";') 54 self.assertEquals( 55 self._tab.EvaluateJavaScript('document.scrollingElement.scrollTop'), 0) 56 57 # Allow for some visual viewport offset. For example, if the test doesn't 58 # want any visual viewport offset due to animation handoff error between 59 # the two viewports. 60 start_offset = self._tab.EvaluateJavaScript('window.visualViewport.pageTop') 61 62 i = scroll.ScrollAction( 63 distance=distance, 64 direction="down", 65 speed_in_pixels_per_second=speed, 66 synthetic_gesture_source=source) 67 i.WillRunAction(self._tab) 68 i.RunAction(self._tab) 69 70 actual = self._tab.EvaluateJavaScript( 71 'window.visualViewport.pageTop') - start_offset 72 73 # TODO(bokan): setBrowserControlsShown isn't quite enough. Chrome will hide 74 # the browser controls but then they animate in after a timeout. We'll need 75 # to add a way to lock them to hidden. Until then, just increase the 76 # allowed error. 77 urlBarError = 150 78 79 self.assertAlmostEqual(distance, actual, delta=maxError + urlBarError) 80 81 @decorators.Disabled('chromeos', 'linux') # crbug.com/1006789 82 def testScrollDistanceFastTouch(self): 83 # Just pass the test on platforms that don't support touch (i.e. Mac) 84 if not page_action.IsGestureSourceTypeSupported(self._tab, 'touch'): 85 return 86 87 # Scrolling distance for touch will have some error from the excess delta 88 # of the event that crosses the slop threshold but isn't applied, also 89 # scroll resampling can increase the error amount.. 90 self._RunScrollDistanceTest( 91 500000, 200000, page_action.GESTURE_SOURCE_TOUCH, 200) 92 93 @decorators.Disabled('android-reference') # crbug.com/934649 94 def testScrollDistanceFastWheel(self): 95 # Wheel scrolling will have a much greater error than touch. There's 2 96 # reasons: 1) synthetic wheel gesture accumulate the sent deltas and use 97 # that to determine how much delta to send at each event dispatch time. 98 # This assumes that the entire sent delta is applied which is wrong due to 99 # physical pixel snapping which accumulates over the gesture. 100 # 2) We can only send delta as ticks of the wheel. If the total delta is 101 # not a multiple of the tick size, we'll "lose" the remainder. 102 self._RunScrollDistanceTest( 103 500000, 200000, page_action.GESTURE_SOURCE_MOUSE, 15000) 104 105 def testScrollDistanceSlowTouch(self): 106 # Just pass the test on platforms that don't support touch (i.e. Mac) 107 if not page_action.IsGestureSourceTypeSupported(self._tab, 'touch'): 108 return 109 110 # Scrolling slowly produces larger error since each event will have a 111 # smaller delta. Thus error from snapping in each event will be a larger 112 # share of the total delta. 113 self._RunScrollDistanceTest( 114 1000, 300, page_action.GESTURE_SOURCE_TOUCH, 10) 115 116 @decorators.Disabled('android-reference') # crbug.com/934649 117 def testScrollDistanceSlowWheel(self): 118 self._RunScrollDistanceTest( 119 1000, 300, page_action.GESTURE_SOURCE_MOUSE, 200) 120 121 @decorators.Disabled('android-reference') # crbug.com/934649 122 @decorators.Disabled('win-reference') # crbug.com/805523 123 def testWheelScrollDistanceWhileZoomed(self): 124 # TODO(bokan): This API was added recently so only run the test once it's 125 # available. Remove this check once it rolls into stable builds. 126 chromeSupportsSetPageScaleFactor = self._tab.EvaluateJavaScript( 127 "'setPageScaleFactor' in chrome.gpuBenchmarking") 128 if not chromeSupportsSetPageScaleFactor: 129 return 130 131 self._tab.EvaluateJavaScript('chrome.gpuBenchmarking.setPageScaleFactor(2)') 132 133 # Wheel scrolling can cause animated scrolls. This is a problem here since 134 # Chrome currently doesn't hand off the animation between the visual and 135 # layout viewports. To account for this, scroll the visual viewport to it's 136 # maximum extent so that the entire scroll goes to the layout viewport. 137 screenHeight = self._tab.EvaluateJavaScript('window.visualViewport.height') 138 139 i = scroll.ScrollAction( 140 distance=screenHeight*2, 141 direction="down", 142 speed_in_pixels_per_second=5000, 143 synthetic_gesture_source=page_action.GESTURE_SOURCE_MOUSE) 144 i.WillRunAction(self._tab) 145 i.RunAction(self._tab) 146 147 # Ensure the layout viewport isn't scrolled but the visual is. 148 self.assertGreater( 149 self._tab.EvaluateJavaScript('window.visualViewport.offsetTop'), 150 screenHeight / 2 - 1) 151 self.assertEqual(self._tab.EvaluateJavaScript('window.scrollY'), 0) 152 153 self._RunScrollDistanceTest( 154 2000, 2000, page_action.GESTURE_SOURCE_MOUSE, 60) 155 156 def testTouchScrollDistanceWhileZoomed(self): 157 # Just pass the test on platforms that don't support touch (i.e. Mac) 158 if not page_action.IsGestureSourceTypeSupported(self._tab, 'touch'): 159 return 160 161 # TODO(bokan): This API was added recently so only run the test once it's 162 # available. Remove this check once it rolls into stable builds. 163 chromeSupportsSetPageScaleFactor = self._tab.EvaluateJavaScript( 164 "'setPageScaleFactor' in chrome.gpuBenchmarking") 165 if not chromeSupportsSetPageScaleFactor: 166 return 167 168 self._tab.EvaluateJavaScript('chrome.gpuBenchmarking.setPageScaleFactor(2)') 169 self._RunScrollDistanceTest( 170 2000, 2000, page_action.GESTURE_SOURCE_TOUCH, 20) 171 172 def testScrollAction(self): 173 174 self._MakePageVerticallyScrollable() 175 self.assertEquals( 176 self._tab.EvaluateJavaScript('document.scrollingElement.scrollTop'), 0) 177 178 i = scroll.ScrollAction() 179 i.WillRunAction(self._tab) 180 181 self._tab.ExecuteJavaScript(""" 182 window.__scrollAction.beginMeasuringHook = function() { 183 window.__didBeginMeasuring = true; 184 }; 185 window.__scrollAction.endMeasuringHook = function() { 186 window.__didEndMeasuring = true; 187 };""") 188 i.RunAction(self._tab) 189 190 self.assertTrue(self._tab.EvaluateJavaScript('window.__didBeginMeasuring')) 191 self.assertTrue(self._tab.EvaluateJavaScript('window.__didEndMeasuring')) 192 193 scroll_position = self._tab.EvaluateJavaScript( 194 'document.scrollingElement.scrollTop') 195 self.assertTrue( 196 scroll_position != 0, msg='scroll_position=%d;' % (scroll_position)) 197 198 # https://github.com/catapult-project/catapult/issues/3099 199 @decorators.Disabled('android') 200 @decorators.Disabled('chromeos') # crbug.com/984016 201 def testDiagonalScrollAction(self): 202 self._MakePageVerticallyScrollable() 203 self.assertEquals( 204 self._tab.EvaluateJavaScript('document.scrollingElement.scrollTop'), 0) 205 206 self._MakePageHorizontallyScrollable() 207 self.assertEquals( 208 self._tab.EvaluateJavaScript('document.scrollingElement.scrollLeft'), 0) 209 210 i = scroll.ScrollAction(direction='downright') 211 i.WillRunAction(self._tab) 212 213 i.RunAction(self._tab) 214 215 viewport_top = self._tab.EvaluateJavaScript( 216 'document.scrollingElement.scrollTop') 217 self.assertTrue(viewport_top != 0, msg='viewport_top=%d;' % viewport_top) 218 219 viewport_left = self._tab.EvaluateJavaScript( 220 'document.scrollingElement.scrollLeft') 221 self.assertTrue(viewport_left != 0, msg='viewport_left=%d;' % viewport_left) 222 223 def testBoundingClientRect(self): 224 # Verify that the rect returned by getBoundingVisibleRect() in scroll.js is 225 # completely contained within the viewport. Scroll events dispatched by the 226 # scrolling API use the center of this rect as their location, and this 227 # location needs to be within the viewport bounds to correctly decide 228 # between main-thread and impl-thread scroll. If the scrollable area were 229 # not clipped to the viewport bounds, then the instance used here (the 230 # scrollable area being more than twice as tall as the viewport) would 231 # result in a scroll location outside of the viewport bounds. 232 self._MakePageVerticallyScrollable() 233 self.assertEquals( 234 self._tab.EvaluateJavaScript('document.scrollingElement.scrollTop'), 0) 235 236 self._MakePageHorizontallyScrollable() 237 self.assertEquals( 238 self._tab.EvaluateJavaScript('document.scrollingElement.scrollLeft'), 0) 239 240 self._tab.ExecuteJavaScript(""" 241 window.scrollTo(__GestureCommon_GetWindowWidth(), 242 __GestureCommon_GetWindowHeight());""") 243 244 rect_top = int( 245 self._tab.EvaluateJavaScript( 246 '__GestureCommon_GetBoundingVisibleRect(document.body).top')) 247 rect_height = int( 248 self._tab.EvaluateJavaScript( 249 '__GestureCommon_GetBoundingVisibleRect(document.body).height')) 250 rect_bottom = rect_top + rect_height 251 252 rect_left = int( 253 self._tab.EvaluateJavaScript( 254 '__GestureCommon_GetBoundingVisibleRect(document.body).left')) 255 rect_width = int( 256 self._tab.EvaluateJavaScript( 257 '__GestureCommon_GetBoundingVisibleRect(document.body).width')) 258 rect_right = rect_left + rect_width 259 260 viewport_height = int( 261 self._tab.EvaluateJavaScript('__GestureCommon_GetWindowHeight()')) 262 viewport_width = int( 263 self._tab.EvaluateJavaScript('__GestureCommon_GetWindowWidth()')) 264 265 self.assertTrue(rect_top >= 0, msg='%s >= %s' % (rect_top, 0)) 266 self.assertTrue(rect_left >= 0, msg='%s >= %s' % (rect_left, 0)) 267 self.assertTrue( 268 rect_bottom <= viewport_height, 269 msg='%s + %s <= %s' % (rect_top, rect_height, viewport_height)) 270 self.assertTrue( 271 rect_right <= viewport_width, 272 msg='%s + %s <= %s' % (rect_left, rect_width, viewport_width)) 273