blob: 3870f50817ffe6162cc16a6d12da2c722726a4fe [file] [log] [blame]
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import logging
import shutil
import time
import six.moves._thread # pylint: disable=import-error
import py_utils
from py_utils import exc_util
from telemetry import decorators
from telemetry.internal.backends.chrome import chrome_browser_backend
from telemetry.internal.backends.chrome import minidump_finder
class LacrosBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
def __init__(self, cros_platform_backend, browser_options,
browser_directory, profile_directory, env,
cros_browser_backend, build_dir=None):
"""
Args:
cros_platform_backend: The cros_platform_backend.CrOSPlatformBackend
instance to use.
browser_options: The browser_options.BrowserOptions instance to use.
browser_directory: A string containing the path to the directory on the
device where the browser is installed.
profile_directory: A string containing a path to the directory on the
device to store browser profile information in.
env: A list of strings containing environment variables to start the
browser with.
cros_browser_backend: The CrOs browser Lacros is running on top of.
Some actions (e.g. Close) are further delegated to this.
build_dir: A string containing a path to the directory on the host that
the browser was built in, for finding debug artifacts. Can be None if
the browser was not locally built, or the directory otherwise cannot
be determined.
"""
assert browser_options.IsCrosBrowserOptions()
super(LacrosBrowserBackend, self).__init__(
cros_platform_backend,
browser_options=browser_options,
browser_directory=browser_directory,
profile_directory=profile_directory,
supports_extensions=True,
supports_tab_control=True,
build_dir=build_dir)
self._cri = cros_platform_backend.cri
self._env = env
self._devtools_client_os = None
self._devtools_port_path = self._GetDevToolsActivePortPath()
self._cros_browser_backend = cros_browser_backend
self._is_browser_running = False
@property
def log_file_path(self):
return None
def _GetDevToolsActivePortPath(self):
return '/usr/local/lacros-chrome/user_data/DevToolsActivePort'
def _RunCommandAndLog(self, cmd):
results = self._cri.RunCmdOnDevice(cmd)
logging.info("stdout: " + results[0])
logging.info("stderr: " + results[1])
def _IsDevtoolsUp(self):
return self._cri.FileExistsOnDevice(self._GetDevToolsActivePortPath())
# TODO(crbug.com/1148868): Share this and others with CrOSBrowserBackend.
def _FindDevToolsPortAndTarget(self):
devtools_file_path = self._GetDevToolsActivePortPath()
# GetFileContents may rise IOError or OSError, the caller will retry.
lines = self._cri.GetFileContents(devtools_file_path).splitlines()
if not lines:
raise EnvironmentError('DevTools file empty')
devtools_port = int(lines[0])
browser_target = lines[1] if len(lines) >= 2 else None
return devtools_port, browser_target
def GetPid(self):
return self._cri.GetChromePid()
def __del__(self):
self.Close()
def _ReformatArg(self, startup_args, arg_name):
arg_str = '--' + arg_name + '='
for i, arg in enumerate(startup_args):
if arg_str in arg:
new_arg = arg
new_arg = new_arg.replace(arg_str, arg_str + "'")
new_arg = new_arg + "'"
new_arg = new_arg.replace(';', '\\;')
new_arg = new_arg.replace(',', '\\,')
startup_args[i] = new_arg
def _LaunchLacrosChromeHelper(self, startup_args):
self._cri.RunCmdOnDevice(['cp', '../usr/local/lacros-chrome/chrome',
'../usr/local/lacros-chrome/lacros-chrome'])
# Some args need escaping, etc.
self._ReformatArg(startup_args, 'enable-features')
self._ReformatArg(startup_args, 'disable-features')
self._ReformatArg(startup_args, 'force-fieldtrials')
self._ReformatArg(startup_args, 'force-fieldtrial-params')
self._ReformatArg(startup_args, 'proxy-bypass-list')
self._ReformatArg(startup_args, 'user-agent')
def _Launch():
# This will block until the launched browser is closed.
self._RunCommandAndLog(
['EGL_PLATFORM=surfaceless',
'XDG_RUNTIME_DIR=/run/chrome',
'python',
'/usr/local/bin/mojo_connection_lacros_launcher.py',
'-s', '/tmp/lacros.sock',
'./../usr/local/lacros-chrome/lacros-chrome',
'--ozone-platform=wayland',
'--user-data-dir=/usr/local/lacros-chrome/user_data',
'--enable-gpu-rasterization',
'--enable-oop-rasterization',
'--lang=en-US',
'--breakpad-dump-location=/usr/local/lacros-chrome/',
'--no-sandbox'] + startup_args)
# This will only exist if launch was successful.
return self._IsDevtoolsUp()
# TODO(crbug/1148534) - Launch only works sporadically.
# This will return either when the timeout is hit or the test closes Lacros,
# whichever is *later*
py_utils.WaitFor(_Launch, 40)
self._is_browser_running = False
def LaunchLacrosChrome(self, startup_args):
six.moves._thread.start_new_thread(
self._LaunchLacrosChromeHelper, (startup_args,))
py_utils.WaitFor(self._IsDevtoolsUp, 40)
self._is_browser_running = self._IsDevtoolsUp()
# TODO(crbug/1150455) - Find another condtion to wait on.
time.sleep(1)
print('Is Lacros up? ' + str(self._is_browser_running))
def Start(self, startup_args):
self._cri.OpenConnection()
# Remove the stale file with the devtools port / browser target
# prior to restarting chrome.
self._cri.RmRF(self._GetDevToolsActivePortPath())
self._dump_finder = minidump_finder.MinidumpFinder(
self.browser.platform.GetOSName(), self.browser.platform.GetArchName())
self.LaunchLacrosChrome(startup_args)
self.BindDevToolsClient()
def Background(self):
raise NotImplementedError
@exc_util.BestEffort
def Close(self):
super(LacrosBrowserBackend, self).Close()
if self._tmp_minidump_dir:
shutil.rmtree(self._tmp_minidump_dir, ignore_errors=True)
self._tmp_minidump_dir = None
# Underlying CrOS browser is responsible for closing the cri
self._cros_browser_backend.Close()
def IsBrowserRunning(self):
return self._is_browser_running
def GetStandardOutput(self):
return 'Cannot get standard output on Lacros'
def PullMinidumps(self):
if self._cri:
self._cri.PullDumps(self._tmp_minidump_dir)
else:
logging.error(
'Attempted to pull minidumps without CrOSInterface. Either the '
'browser is already closed or was never started.')
def SymbolizeMinidump(self, minidump_path):
return self._cros_browser_backend.SymbolizeMinidump(minidump_path)
def CollectDebugData(self, log_level):
"""Collects various information that may be useful for debugging.
Args:
log_level: The logging level to use from the logging module, e.g.
logging.ERROR.
Returns:
A debug_data.DebugData object containing the collected data.
"""
# TODO(crbug.com/1148528): Pull Lacros data.
self._cros_browser_backend.CollectDebugData(log_level)
@property
def screenshot_timeout(self):
# Screenshots fail when the screen is off, and we can flakily attempt to
# capture screenshots on failure when the screen is off. So, retry for a
# while if we run into that.
return 15
@property
def supports_overview_mode(self): # pylint: disable=invalid-name
return True
def EnterOverviewMode(self, timeout):
self._cros_browser_backend._devtools_client.window_manager_backend.EnterOverviewMode(timeout)
def ExitOverviewMode(self, timeout):
self._cros_browser_backend._devtools_client.window_manager_backend.ExitOverviewMode(timeout)
@property
@decorators.Cache
def misc_web_contents_backend(self):
"""Access to chrome://oobe/login page."""
return self._cros_browser_backend.misc_web_contents_backend
@property
def oobe(self):
return self._cros_browser_backend.misc_web_contents_backend.GetOobe()
@property
def oobe_exists(self):
return self._cros_browser_backend.misc_web_contents_backend.oobe_exists