• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from __future__ import annotations
6
7import enum
8from argparse import ArgumentTypeError
9from typing import Any, Tuple
10
11from crossbench import compat
12
13
14@enum.unique
15class ViewportMode(compat.StrEnum):
16  SIZE = "size"
17  MAXIMIZED = "maximized"
18  FULLSCREEN = "fullscreen"
19  HEADLESS = "headless"
20
21
22class Viewport:
23  DEFAULT: Viewport
24  MAXIMIZED: Viewport
25  FULLSCREEN: Viewport
26  HEADLESS: Viewport
27
28  @classmethod
29  def parse_sized(cls, value: Any) -> Viewport:
30    if isinstance(value, Viewport):
31      viewport = value
32    elif isinstance(value, str):
33      viewport = cls.parse(value)
34    else:
35      raise ArgumentTypeError(f"Expected str, but got '{type(value)}': {value}")
36    if not viewport.has_size:
37      raise ArgumentTypeError("Expected viewport with explicit size, "
38                              f"but got {viewport}")
39    return viewport
40
41  @classmethod
42  def parse(cls, value: str) -> Viewport:
43    if not value:
44      return cls.DEFAULT
45    if value in ("m", "max", "maximised", ViewportMode.MAXIMIZED):
46      return cls.MAXIMIZED
47    if value in ("f", "full", ViewportMode.FULLSCREEN):
48      return cls.FULLSCREEN
49    if value == ViewportMode.HEADLESS:
50      return cls.HEADLESS
51    size, _, position = value.partition(",")
52    width, _, height = size.partition("x")
53    if not height:
54      raise ArgumentTypeError(f"Missing viewport height in input: {value}")
55    x = str(cls.DEFAULT.x)
56    y = str(cls.DEFAULT.y)
57    if position:
58      x, _, y = position.partition("x")
59      if not y:
60        raise ArgumentTypeError(
61            f"Missing viewport y position in input: {value}")
62    return Viewport(int(width), int(height), int(x), int(y))
63
64  def __init__(self,
65               width: int = 1500,
66               height: int = 1000,
67               x: int = 10,
68               y: int = 50,
69               mode: ViewportMode = ViewportMode.SIZE):
70    self._width = width
71    self._height = height
72    self._x = x
73    self._y = y
74    self._mode = mode
75    self._validate()
76
77  def _validate(self) -> None:
78    if self._mode == ViewportMode.SIZE:
79      if self._width <= 0:
80        raise ArgumentTypeError(f"width must be > 0, but got {self._width}")
81      if self._height <= 0:
82        raise ArgumentTypeError(f"height must be > 0, but got {self._height}")
83      if self._x < 0:
84        raise ArgumentTypeError(f"x must be >= 0, but got {self._x}")
85      if self._y < 0:
86        raise ArgumentTypeError(f"y must be >= 0, but got {self._y}")
87    else:
88      if self._width != 0:
89        raise ArgumentTypeError(
90            "Non-zero width only allowed with ViewportMode.SIZE")
91      if self._height != 0:
92        raise ArgumentTypeError(
93            "Non-zero height only allowed with ViewportMode.SIZE")
94      if self._x != 0:
95        raise ArgumentTypeError(
96            "Non-zero x only allowed with ViewportMode.SIZE")
97      if self._y != 0:
98        raise ArgumentTypeError(
99            "Non-zero y only allowed with ViewportMode.SIZE")
100
101  @property
102  def is_default(self) -> bool:
103    return self is Viewport.DEFAULT
104
105  @property
106  def is_maximized(self) -> bool:
107    return self._mode == ViewportMode.MAXIMIZED
108
109  @property
110  def is_fullscreen(self) -> bool:
111    return self._mode == ViewportMode.FULLSCREEN
112
113  @property
114  def is_headless(self) -> bool:
115    return self._mode == ViewportMode.HEADLESS
116
117  @property
118  def has_size(self) -> bool:
119    return self._mode == ViewportMode.SIZE
120
121  @property
122  def position(self) -> Tuple[int, int]:
123    assert self.has_size, f"Viewport has no explicit size: {self._mode}"
124    return (self._x, self._y)
125
126  @property
127  def size(self) -> Tuple[int, int]:
128    assert self.has_size, f"Viewport has no explicit size: {self._mode}"
129    return (self._width, self._height)
130
131  @property
132  def width(self) -> int:
133    assert self.has_size, f"Viewport has no explicit size: {self._mode}"
134    return self._width
135
136  @property
137  def height(self) -> int:
138    assert self.has_size, f"Viewport has no explicit size: {self._mode}"
139    return self._height
140
141  @property
142  def x(self) -> int:
143    assert self.has_size, f"Viewport has no explicit size: {self._mode}"
144    return self._x
145
146  @property
147  def y(self) -> int:
148    assert self.has_size, f"Viewport has no explicit size: {self._mode}"
149    return self._y
150
151  @property
152  def mode(self) -> ViewportMode:
153    return self._mode
154
155  @property
156  def key(self) -> Tuple[Tuple, ...]:
157    return (
158        ("mode", str(self.mode)),
159        ("x", self._x),
160        ("y", self._y),
161        ("width", self._width),
162        ("height", self._height),
163    )
164
165  def __str__(self) -> str:
166    if self.has_size:
167      return f"Viewport({self.width}x{self.height},{self.x}x{self.y})"
168    return f"Viewport({self.mode})"
169
170  def __eq__(self, other) -> bool:
171    if not isinstance(other, Viewport):
172      return False
173    if self is other:
174      return True
175    return self.key == other.key
176
177
178Viewport.DEFAULT = Viewport()
179Viewport.MAXIMIZED = Viewport(0, 0, 0, 0, ViewportMode.MAXIMIZED)
180Viewport.FULLSCREEN = Viewport(0, 0, 0, 0, ViewportMode.FULLSCREEN)
181Viewport.HEADLESS = Viewport(0, 0, 0, 0, ViewportMode.HEADLESS)
182