1# Copyright 2016 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.internal.actions import page_action 6from telemetry.internal.actions.scroll import ScrollAction 7from telemetry.util import js_template 8 9 10class ScrollToElementAction(page_action.PageAction): 11 12 13 def __init__(self, selector=None, element_function=None, 14 container_selector=None, container_element_function=None, 15 speed_in_pixels_per_second=800): 16 """Perform scroll gesture on container until an element is in view. 17 18 Both the element and the container can be specified by a CSS selector 19 xor a JavaScript function, provided as a string, which returns an element. 20 The element is required so exactly one of selector and element_function 21 must be provided. The container is optional so at most one of 22 container_selector and container_element_function can be provided. 23 The container defaults to document.scrollingElement or document.body if 24 scrollingElement is not set. 25 26 Args: 27 selector: A CSS selector describing the element. 28 element_function: A JavaScript function (as string) that is used 29 to retrieve the element. For example: 30 'function() { return foo.element; }'. 31 container_selector: A CSS selector describing the container element. 32 container_element_function: A JavaScript function (as a string) that is 33 used to retrieve the container element. 34 speed_in_pixels_per_second: Speed to scroll. 35 """ 36 super(ScrollToElementAction, self).__init__() 37 self._selector = selector 38 self._element_function = element_function 39 self._container_selector = container_selector 40 self._container_element_function = container_element_function 41 self._speed = speed_in_pixels_per_second 42 self._distance = None 43 self._direction = None 44 self._scroller = None 45 assert (self._selector or self._element_function), ( 46 'Must have either selector or element function') 47 48 def WillRunAction(self, tab): 49 if self._selector: 50 element = js_template.Render( 51 'document.querySelector({{ selector }})', selector=self._selector) 52 else: 53 element = self._element_function 54 55 self._distance = tab.EvaluateJavaScript(''' 56 (function(elem){ 57 var rect = elem.getBoundingClientRect(); 58 if (rect.bottom < 0) { 59 // The bottom of the element is above the viewport. 60 // Scroll up until the top of the element is on screen. 61 return rect.top - (window.innerHeight / 2); 62 } 63 if (rect.top - window.innerHeight >= 0) { 64 // rect.top provides the pixel offset of the element from the 65 // top of the page. Because that exceeds the viewport's height, 66 // we know that the element is below the viewport. 67 return rect.top - (window.innerHeight / 2); 68 } 69 return 0; 70 })({{ @element }}); 71 ''', element=element) 72 self._direction = 'down' if self._distance > 0 else 'up' 73 self._distance = abs(self._distance) 74 self._scroller = ScrollAction( 75 direction=self._direction, 76 selector=self._container_selector, 77 element_function=self._container_element_function, 78 distance=self._distance, 79 speed_in_pixels_per_second=self._speed) 80 81 def RunAction(self, tab): 82 if self._distance == 0: # Element is already in view. 83 return 84 self._scroller.WillRunAction(tab) 85 self._scroller.RunAction(tab) 86