• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 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 argparse
8import logging
9import re
10from typing import TYPE_CHECKING, Optional, TextIO, Tuple, cast
11
12from crossbench import path as pth
13from crossbench import plt
14from crossbench.browsers.attributes import BrowserAttributes
15from crossbench.browsers.browser import Browser
16from crossbench.browsers.browser_helper import convert_flags_to_label
17from crossbench.browsers.viewport import Viewport
18from crossbench.flags.chrome import ChromeFeatures, ChromeFlags
19from crossbench.types import JsonDict
20
21if TYPE_CHECKING:
22  from crossbench.browsers.settings import Settings
23  from crossbench.flags.base import Flags, FlagsData
24  from crossbench.flags.js_flags import JSFlags
25  from crossbench.runner.groups.session import BrowserSessionRunGroup
26
27
28class Chromium(Browser):
29  MIN_HEADLESS_NEW_VERSION: int = 112
30  DEFAULT_FLAGS: Tuple[str, ...] = (
31      "--no-default-browser-check",
32      "--disable-component-update",
33      "--disable-sync",
34      "--disable-extensions",
35      "--no-first-run",
36      # This could be enabled via feature-flags as well.
37      "--disable-search-engine-choice-screen",
38  )
39  FLAGS_FOR_DISABLING_BACKGROUND_INTERVENTIONS: Tuple[str, ...] = (
40      "--disable-background-timer-throttling",
41      "--disable-renderer-backgrounding",
42  )
43  # All flags that might affect how finch / field-trials are loaded.
44  FIELD_TRIAL_FLAGS: Tuple[str, ...] = (
45      "--force-fieldtrials",
46      "--variations-server-url",
47      "--variations-insecure-server-url",
48      "--variations-test-seed-path",
49      "--enable-field-trial-config",
50      "--disable-variations-safe-mode",
51  )
52  NO_EXPERIMENTS_FLAGS: Tuple[str, ...] = (
53      "--no-experiments",
54      "--enable-benchmarking",
55      "--disable-field-trial-config",
56  )
57
58  @classmethod
59  def default_path(cls, platform: plt.Platform) -> pth.AnyPath:
60    return platform.search_app_or_executable(
61        "Chromium",
62        macos=["Chromium.app"],
63        linux=["google-chromium", "chromium"],
64        win=["Google/Chromium/Application/chromium.exe"])
65
66  @classmethod
67  def default_flags(cls, initial_data: FlagsData = None) -> ChromeFlags:
68    return ChromeFlags(initial_data)
69
70  def __init__(self,
71               label: str,
72               path: pth.AnyPath,
73               settings: Optional[Settings] = None):
74    super().__init__(label, path, settings=settings)
75    self._stdout_log_file: Optional[TextIO] = None
76    assert isinstance(self._flags, ChromeFlags)
77
78  def _setup_flags(self, settings: Settings) -> ChromeFlags:
79    flags: Flags = settings.flags
80    js_flags: Flags = settings.js_flags
81    self._flags = self.default_flags(self.DEFAULT_FLAGS)
82    self._flags.update(flags)
83
84    if "--allow-background-interventions" in self._flags.data:
85      # The --allow-background-interventions flag should have no value.
86      assert self._flags.get("--allow-background-interventions") is None
87    else:
88      self._flags.update(self.FLAGS_FOR_DISABLING_BACKGROUND_INTERVENTIONS)
89
90    # Explicitly disable field-trials by default on all chrome flavours:
91    # By default field-trials are enabled on non-Chrome branded builds, but
92    # are auto-enabled on everything else. This gives very confusing results
93    # when comparing local builds to official binaries.
94    field_trial_flags = [
95        flag for flag in self.FIELD_TRIAL_FLAGS if flag in self._flags
96    ]
97    if not field_trial_flags:
98      logging.info("Disabling experiments/finch/field-trials for %s", self)
99      for flag in self.NO_EXPERIMENTS_FLAGS:
100        self._flags.set(flag)
101    else:
102      logging.warning("Running with field-trials or finch experiments.")
103      no_finch_flags = [
104          flag for flag in self.NO_EXPERIMENTS_FLAGS if flag in self._flags
105      ]
106      if no_finch_flags:
107        raise argparse.ArgumentTypeError(
108            "Conflicting flag groups set: "
109            f"{field_trial_flags} vs {no_finch_flags}.\n"
110            "Cannot enable and disable finch / field-trials at the same time.")
111
112    self.js_flags.update(js_flags)
113    self._maybe_disable_gpu_compositing()
114    return self._flags
115
116  def _maybe_disable_gpu_compositing(self) -> None:
117    # Chrome Remote Desktop provide no GPU and older chrome versions
118    # don't handle this well.
119    if self.major_version > 92 or ("CHROME_REMOTE_DESKTOP_SESSION"
120                                   not in self.platform.environ):
121      return
122    self.flags.set("--disable-gpu-compositing")
123    self.flags.set("--no-sandbox")
124
125  def _setup_cache_dir(self, settings: Settings) -> None:
126    cache_dir = settings.cache_dir
127    if cache_dir is None:
128      maybe_cache_dir = self._flags.get("--user-data-dir", None)
129      if maybe_cache_dir:
130        cache_dir = pth.AnyPath(maybe_cache_dir)
131    if cache_dir is None:
132      self.cache_dir = self.platform.mkdtemp(prefix=self.type_name)
133      self.clear_cache_dir = True
134    else:
135      self.cache_dir = cache_dir
136      self.clear_cache_dir = False
137
138  def _extract_version(self) -> str:
139    assert self.path
140    version_string = self.platform.app_version(self.path)
141    # Sample output: "Chromium 90.0.4430.212 dev" => "90.0.4430.212"
142    matches = re.findall(r"[\d\.]+", version_string)
143    if not matches:
144      raise ValueError(
145          f"Could not extract version number from '{version_string}' "
146          f"for '{self.path}'")
147    return str(matches[0])
148
149  @property
150  def type_name(self) -> str:
151    return "chromium"
152
153  @property
154  def attributes(self) -> BrowserAttributes:
155    return BrowserAttributes.CHROMIUM | BrowserAttributes.CHROMIUM_BASED
156
157  @property
158  def is_headless(self) -> bool:
159    return "--headless" in self._flags
160
161  @property
162  def chrome_log_file(self) -> pth.AnyPath:
163    assert self.log_file
164    return self.log_file.with_suffix(f".{self.type_name}.log")
165
166  @property
167  def flags(self) -> ChromeFlags:
168    return cast(ChromeFlags, self._flags)
169
170  @property
171  def js_flags(self) -> JSFlags:
172    return cast(ChromeFlags, self._flags).js_flags
173
174  @property
175  def features(self) -> ChromeFeatures:
176    return cast(ChromeFlags, self._flags).features
177
178  def details_json(self) -> JsonDict:
179    details: JsonDict = super().details_json()
180    if self.log_file:
181      log = cast(JsonDict, details["log"])
182      log[self.type_name] = str(self.chrome_log_file)
183      log["stdout"] = str(self.stdout_log_file)
184    details["js_flags"] = tuple(self.js_flags)
185    return details
186
187  def _get_browser_flags_for_session(
188      self, session: BrowserSessionRunGroup) -> Tuple[str, ...]:
189    js_flags_copy = self.js_flags.copy()
190    js_flags_copy.update(session.extra_js_flags)
191
192    flags_copy = self.flags.copy()
193    flags_copy.update(session.extra_flags)
194    flags_copy.update(self.network.extra_flags(self.attributes))
195    self._handle_viewport_flags(flags_copy)
196
197    if len(js_flags_copy):
198      flags_copy["--js-flags"] = str(js_flags_copy)
199    if user_data_dir := self.flags.get("--user-data-dir"):
200      assert user_data_dir == str(
201          self.cache_dir), (f"--user-data-dir path: {user_data_dir} was passed "
202                            f"but does not match cache-dir: {self.cache_dir}")
203    if self.cache_dir:
204      flags_copy["--user-data-dir"] = str(self.cache_dir)
205    if self.log_file:
206      flags_copy.set("--enable-logging")
207      flags_copy["--log-file"] = str(self.chrome_log_file)
208
209    flags_copy = self._filter_flags_for_run(flags_copy)
210
211    return tuple(flags_copy)
212
213  def _handle_viewport_flags(self, flags: Flags) -> None:
214    self._sync_viewport_flag(flags, "--start-fullscreen",
215                             self.viewport.is_fullscreen, Viewport.FULLSCREEN)
216    self._sync_viewport_flag(flags, "--start-maximized",
217                             self.viewport.is_maximized, Viewport.MAXIMIZED)
218    self._sync_viewport_flag(flags, "--headless", self.viewport.is_headless,
219                             Viewport.HEADLESS)
220    # M112 added --headless=new as replacement for --headless
221    if "--headless" in flags and (self.major_version >=
222                                  self.MIN_HEADLESS_NEW_VERSION):
223      if flags["--headless"] is None:
224        logging.info("Replacing --headless with --headless=new")
225        flags.set("--headless", "new", override=True)
226
227    if self.viewport.is_default:
228      update_viewport = False
229      width, height = self.viewport.size
230      x, y = self.viewport.position
231      if "--window-size" in flags:
232        update_viewport = True
233        width, height = map(int, flags["--window-size"].split(","))
234      if "--window-position" in flags:
235        update_viewport = True
236        x, y = map(int, flags["--window-position"].split(","))
237      if update_viewport:
238        self.viewport = Viewport(width, height, x, y)
239    if self.viewport.has_size:
240      flags["--window-size"] = f"{self.viewport.width},{self.viewport.height}"
241      flags["--window-position"] = f"{self.viewport.x},{self.viewport.y}"
242    else:
243      for flag in ("--window-position", "--window-size"):
244        if flag in flags:
245          flag_value = flags[flag]
246          raise ValueError(f"Viewport {self.viewport} conflicts with flag "
247                           f"{flag}={flag_value}")
248
249  def get_label_from_flags(self) -> str:
250    return convert_flags_to_label(*self.flags, *self.js_flags)
251
252  def quit(self) -> None:
253    super().quit()
254    if self._stdout_log_file:
255      self._stdout_log_file.close()
256      self._stdout_log_file = None
257