# Copyright 2024 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import annotations import dataclasses import datetime as dt import shlex import subprocess from typing import TYPE_CHECKING import crossbench.path as pth from crossbench.action_runner.action import all as i_action from crossbench.action_runner.basic_action_runner import BasicActionRunner from crossbench.action_runner.display_rectangle import DisplayRectangle from crossbench.action_runner.element_not_found_error import \ ElementNotFoundError from crossbench.benchmarks.loading.point import Point from crossbench.parse import NumberParser if TYPE_CHECKING: from typing import Optional, Tuple, Type from crossbench.runner.actions import Actions from crossbench.runner.run import Run SCRIPTS_DIR = pth.LocalPath(__file__).parent / "chromeos_scripts" class ChromeOSViewportInfo: def __init__(self, device_pixel_ratio, window_outer_width, window_inner_width, window_inner_height, screen_width, screen_height, screen_avail_width, screen_avail_height, window_offset_x, window_offset_y, element_rect: Optional[DisplayRectangle]) -> None: # The actual screen width and height in pixels. # Corrects for any zoom/scaling factors. # 80 is a common factor of most display pixel widths, so use it as a common # factor to ensure integer division. screen_width_pixels = round( screen_width * device_pixel_ratio / (window_outer_width / window_inner_width) / 80) * 80 # 60 is a common factor of most display pixel heights, so use it as a common # factor to ensure integer division. screen_height_pixels = round( screen_height * device_pixel_ratio / (window_outer_width / window_inner_width) / 60) * 60 self._actual_pixel_ratio = screen_width_pixels / screen_avail_width screen_avail_width = round(self.css_to_native_distance(screen_avail_width)) screen_avail_height = round( self.css_to_native_distance(screen_avail_height)) window_inner_width = round(self.css_to_native_distance(window_inner_width)) window_inner_height = round( self.css_to_native_distance(window_inner_height)) window_offset_x = round(self.css_to_native_distance(window_offset_x)) window_offset_y = round(self.css_to_native_distance(window_offset_y)) window_offset_y += (screen_avail_height - window_inner_height) visible_width = min(window_inner_width, screen_avail_width - window_offset_x) visible_height = min(window_inner_height, round(screen_avail_height - window_offset_y)) self._native_screen = DisplayRectangle( Point(0, 0), screen_width_pixels, screen_height_pixels) self._browser_viewable = DisplayRectangle( Point(window_offset_x, window_offset_y), visible_width, visible_height) self._element_rect: Optional[DisplayRectangle] = None if element_rect: self._element_rect = self._dom_rect_to_native_rect(element_rect) @property def browser_viewable(self) -> DisplayRectangle: return self._browser_viewable @property def native_screen(self) -> DisplayRectangle: return self._native_screen @property def element_rect(self) -> Optional[DisplayRectangle]: return self._element_rect def _dom_rect_to_native_rect(self, dom_rect: DisplayRectangle) -> DisplayRectangle: browser_viewable = self.browser_viewable correct_ratio_rect = dom_rect * self._actual_pixel_ratio adjusted_left = correct_ratio_rect.left + browser_viewable.left adjusted_top = correct_ratio_rect.top + browser_viewable.top adjusted_width = min(correct_ratio_rect.width, self._native_screen.width - correct_ratio_rect.left) adjusted_height = min(correct_ratio_rect.height, self._native_screen.height - correct_ratio_rect.top) return DisplayRectangle( Point(adjusted_left, adjusted_top), adjusted_width, adjusted_height) def css_to_native_distance(self, distance: float) -> float: return distance * self._actual_pixel_ratio @dataclasses.dataclass(frozen=True) # Stores the configuration of the touchscreen device for the Chromebook. class TouchDevice: # The path of the device. device_path: str # The maximum X value for a touch input. x_max: int # The maximum Y value for a touch input. y_max: int @classmethod def parse_str(cls: Type[TouchDevice], config: str) -> TouchDevice: # The first line of output is always 'Performing autotest_lib import' # Followed by the output we care about. touch_device_values = config.splitlines()[1].split(" ") return TouchDevice(touch_device_values[0], NumberParser.positive_zero_int(touch_device_values[1]), NumberParser.positive_zero_int(touch_device_values[2])) def __str__(self) -> str: return f"{self.device_path} {self.x_max} {self.y_max}" def is_valid_tap_position(self, position: Point) -> bool: return (0 <= position.x and position.x <= self.x_max and 0 <= position.y and position.y <= self.y_max) @dataclasses.dataclass(frozen=True) class ChromeOSTouchEvent: touch_device: TouchDevice # The viewport in which the start and end positions lie. viewport: DisplayRectangle # The start position in terms of the device's screen resolution start_position: Point # The end position in terms of the device's screen resolution end_position: Optional[Point] = None duration: dt.timedelta = dt.timedelta() # Touch event data recorded with evemu-record on a dedede. # This has been tested to work on dedede, brya, and volteer. # Some devices, however, may use a different x-y orientation # (such as kukui in landscape mode) and are not currently supported. _TAP_DOWN = """E: