class ScrollToElementAction(page_action.PageAction): def __init__(self, selector=None, element_function=None, speed_in_pixels_per_second=800): """ Args: selector: Css selector to find element with. element_function: js string that evaluates to an element. speed_in_pixels_per_second: Speed in pixels per second to scroll. """ super(ScrollToElementAction, self).__init__() self._selector = selector self._element_function = element_function self._speed = speed_in_pixels_per_second self._distance = None self._direction = None self._scroller = None assert (self._selector or self._element_function), ( 'Must have either selector or element function') def WillRunAction(self, tab): if self._selector: # TODO(catapult:#3028): Fix interpolation of JavaScript values. element = 'document.querySelector("%s")' % self._selector else: element = self._element_function # TODO(catapult:#3028): Fix interpolation of JavaScript values. get_distance_js = ''' (function(elem){ var rect = elem.getBoundingClientRect(); if (rect.bottom < 0) { // The bottom of the element is above the viewport. // Scroll up until the top of the element is on screen. return rect.top - (window.innerHeight / 2); } if (rect.top - window.innerHeight >= 0) { // rect.top provides the pixel offset of the element from the // top of the page. Because that exceeds the viewport's height, // we know that the element is below the viewport. return rect.top - (window.innerHeight / 2); } return 0; })(%s); ''' % element self._distance = tab.EvaluateJavaScript(get_distance_js) self._direction = 'down' if self._distance > 0 else 'up' self._distance = abs(self._distance) self._scroller = ScrollAction(direction=self._direction, distance=self._distance, speed_in_pixels_per_second=self._speed) def RunAction(self, tab): if self._distance == 0: # Element is already in view. return self._scroller.WillRunAction(tab) self._scroller.RunAction(tab)
class ScrollToElementAction(page_action.PageAction): def __init__(self, selector=None, element_function=None, container_selector=None, container_element_function=None, speed_in_pixels_per_second=800): """Perform scroll gesture on container until an element is in view. Both the element and the container can be specified by a CSS selector xor a JavaScript function, provided as a string, which returns an element. The element is required so exactly one of selector and element_function must be provided. The container is optional so at most one of container_selector and container_element_function can be provided. The container defaults to document.scrollingElement or document.body if scrollingElement is not set. Args: selector: A CSS selector describing the element. element_function: A JavaScript function (as string) that is used to retrieve the element. For example: 'function() { return foo.element; }'. container_selector: A CSS selector describing the container element. container_element_function: A JavaScript function (as a string) that is used to retrieve the container element. speed_in_pixels_per_second: Speed to scroll. """ super(ScrollToElementAction, self).__init__() self._selector = selector self._element_function = element_function self._container_selector = container_selector self._container_element_function = container_element_function self._speed = speed_in_pixels_per_second self._distance = None self._direction = None self._scroller = None assert (self._selector or self._element_function), ( 'Must have either selector or element function') def WillRunAction(self, tab): if self._selector: element = js_template.Render( 'document.querySelector({{ selector }})', selector=self._selector) else: element = self._element_function self._distance = tab.EvaluateJavaScript(''' (function(elem){ var rect = elem.getBoundingClientRect(); if (rect.bottom < 0) { // The bottom of the element is above the viewport. // Scroll up until the top of the element is on screen. return rect.top - (window.innerHeight / 2); } if (rect.top - window.innerHeight >= 0) { // rect.top provides the pixel offset of the element from the // top of the page. Because that exceeds the viewport's height, // we know that the element is below the viewport. return rect.top - (window.innerHeight / 2); } return 0; })({{ @element }}); ''', element=element) self._direction = 'down' if self._distance > 0 else 'up' self._distance = abs(self._distance) self._scroller = ScrollAction( direction=self._direction, selector=self._container_selector, element_function=self._container_element_function, distance=self._distance, speed_in_pixels_per_second=self._speed) def RunAction(self, tab): if self._distance == 0: # Element is already in view. return self._scroller.WillRunAction(tab) self._scroller.RunAction(tab)