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