blob: 0d28cf901bc8930846e0f3272c51c4e374d7336c [file] [log] [blame]
# Copyright 2013 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.
import logging
import os
from telemetry.core import exceptions
from telemetry.core import util
from telemetry import decorators
from telemetry.internal.backends.chrome import chrome_browser_backend
from telemetry.internal.backends.chrome import misc_web_contents_backend
from telemetry.internal import forwarders
class CrOSBrowserBackend(chrome_browser_backend.ChromeBrowserBackend):
def __init__(self, cros_platform_backend, browser_options, cri, is_guest):
super(CrOSBrowserBackend, self).__init__(
cros_platform_backend, supports_tab_control=True,
supports_extensions=not is_guest,
browser_options=browser_options)
assert browser_options.IsCrosBrowserOptions()
# Initialize fields so that an explosion during init doesn't break in Close.
self._cri = cri
self._is_guest = is_guest
self._forwarder = None
self._remote_debugging_port = self._cri.GetRemotePort()
self._port = self._remote_debugging_port
extensions_to_load = browser_options.extensions_to_load
# Copy extensions to temp directories on the device.
# Note that we also perform this copy locally to ensure that
# the owner of the extensions is set to chronos.
for e in extensions_to_load:
extension_dir = cri.RunCmdOnDevice(
['mktemp', '-d', '/tmp/extension_XXXXX'])[0].rstrip()
e.local_path = os.path.join(extension_dir, os.path.basename(e.path))
cri.PushFile(e.path, extension_dir)
cri.Chown(extension_dir)
self._cri.RestartUI(self.browser_options.clear_enterprise_policy)
util.WaitFor(self.IsBrowserRunning, 20)
# Delete test user's cryptohome vault (user data directory).
if not self.browser_options.dont_override_profile:
self._cri.RunCmdOnDevice(['cryptohome', '--action=remove', '--force',
'--user=%s' % self._username])
@property
def log_file_path(self):
return None
def GetBrowserStartupArgs(self):
args = super(CrOSBrowserBackend, self).GetBrowserStartupArgs()
logging_patterns = ['*/chromeos/net/*',
'*/chromeos/login/*',
'*/dbus/*',
'application_lifetime',
'chrome_browser_main_posix']
vmodule = '--vmodule='
for pattern in logging_patterns:
vmodule += '%s=2,' % pattern
vmodule = vmodule.rstrip(',')
args.extend([
'--enable-smooth-scrolling',
'--enable-threaded-compositing',
# Allow devtools to connect to chrome.
'--remote-debugging-port=%i' % self._remote_debugging_port,
# Open a maximized window.
'--start-maximized',
# Disable system startup sound.
'--ash-disable-system-sounds',
# Ignore DMServer errors for policy fetches.
'--allow-failed-policy-fetch-for-test',
# Skip user image selection screen, and post login screens.
'--oobe-skip-postlogin',
# Debug logging.
vmodule])
# Disable GAIA services unless we're using GAIA login, or if there's an
# explicit request for it.
if (self.browser_options.disable_gaia_services and
not self.browser_options.gaia_login):
args.append('--disable-gaia-services')
trace_config_file = (self.platform_backend.tracing_controller_backend
.GetChromeTraceConfigFile())
if trace_config_file:
args.append('--trace-config-file=%s' % trace_config_file)
return args
@property
def pid(self):
return self._cri.GetChromePid()
@property
def browser_directory(self):
result = self._cri.GetChromeProcess()
if result and 'path' in result:
return os.path.dirname(result['path'])
return None
@property
def profile_directory(self):
return '/home/chronos/Default'
def __del__(self):
self.Close()
def Start(self):
# Escape all commas in the startup arguments we pass to Chrome
# because dbus-send delimits array elements by commas
startup_args = [a.replace(',', '\\,') for a in self.GetBrowserStartupArgs()]
# Restart Chrome with the login extension and remote debugging.
pid = self.pid
logging.info('Restarting Chrome (pid=%d) with remote port', pid)
args = ['dbus-send', '--system', '--type=method_call',
'--dest=org.chromium.SessionManager',
'/org/chromium/SessionManager',
'org.chromium.SessionManagerInterface.EnableChromeTesting',
'boolean:true',
'array:string:"%s"' % ','.join(startup_args)]
logging.info('Starting Chrome %s', args)
self._cri.RunCmdOnDevice(args)
if not self._cri.local:
# TODO(crbug.com/404771): Move port forwarding to network_controller.
self._port = util.GetUnreservedAvailableLocalPort()
self._forwarder = self._platform_backend.forwarder_factory.Create(
forwarders.PortPair(self._port, self._remote_debugging_port),
use_remote_port_forwarding=False)
# Wait for new chrome and oobe.
util.WaitFor(lambda: pid != self.pid, 15)
self._WaitForBrowserToComeUp()
self._InitDevtoolsClientBackend(
remote_devtools_port=self._remote_debugging_port)
util.WaitFor(lambda: self.oobe_exists, 30)
if self.browser_options.auto_login:
if self._is_guest:
pid = self.pid
self.oobe.NavigateGuestLogin()
# Guest browsing shuts down the current browser and launches an
# incognito browser in a separate process, which we need to wait for.
try:
# TODO(achuith): Reduce this timeout to 15 sec after crbug.com/631640
# is resolved.
util.WaitFor(lambda: pid != self.pid, 60)
except exceptions.TimeoutException:
self._RaiseOnLoginFailure(
'Failed to restart browser in guest mode (pid %d).' % pid)
elif self.browser_options.gaia_login:
self.oobe.NavigateGaiaLogin(self._username, self._password)
else:
self.oobe.NavigateFakeLogin(self._username, self._password,
self._gaia_id, not self.browser_options.disable_gaia_services)
try:
self._WaitForLogin()
except exceptions.TimeoutException:
self._RaiseOnLoginFailure('Timed out going through login screen. '
+ self._GetLoginStatus())
logging.info('Browser is up!')
def Close(self):
super(CrOSBrowserBackend, self).Close()
if self._cri:
self._cri.RestartUI(False) # Logs out.
self._cri.CloseConnection()
util.WaitFor(lambda: not self._IsCryptohomeMounted(), 180)
if self._forwarder:
self._forwarder.Close()
self._forwarder = None
if self._cri:
for e in self._extensions_to_load:
self._cri.RmRF(os.path.dirname(e.local_path))
self._cri = None
def IsBrowserRunning(self):
return bool(self.pid)
def GetStandardOutput(self):
return 'Cannot get standard output on CrOS'
def GetStackTrace(self):
return (False, 'Cannot get stack trace on CrOS')
def GetMostRecentMinidumpPath(self):
return None
def GetAllMinidumpPaths(self):
return None
def GetAllUnsymbolizedMinidumpPaths(self):
return None
def SymbolizeMinidump(self, minidump_path):
return None
@property
@decorators.Cache
def misc_web_contents_backend(self):
"""Access to chrome://oobe/login page."""
return misc_web_contents_backend.MiscWebContentsBackend(self)
@property
def oobe(self):
return self.misc_web_contents_backend.GetOobe()
@property
def oobe_exists(self):
return self.misc_web_contents_backend.oobe_exists
@property
def _username(self):
return self.browser_options.username
@property
def _password(self):
return self.browser_options.password
@property
def _gaia_id(self):
return self.browser_options.gaia_id
def _IsCryptohomeMounted(self):
username = '$guest' if self._is_guest else self._username
return self._cri.IsCryptohomeMounted(username, self._is_guest)
def _GetLoginStatus(self):
"""Returns login status. If logged in, empty string is returned."""
status = ''
if not self._IsCryptohomeMounted():
status += 'Cryptohome not mounted. '
if not self.HasBrowserFinishedLaunching():
status += 'Browser didn\'t launch. '
if self.oobe_exists:
status += 'OOBE not dismissed.'
return status
def _IsLoggedIn(self):
"""Returns True if cryptohome has mounted, the browser is
responsive to devtools requests, and the oobe has been dismissed."""
return not self._GetLoginStatus()
def _WaitForLogin(self):
# Wait for cryptohome to mount.
util.WaitFor(self._IsLoggedIn, 60)
# For incognito mode, the session manager actually relaunches chrome with
# new arguments, so we have to wait for the browser to come up.
self._WaitForBrowserToComeUp()
# Wait for extensions to load.
if self._supports_extensions:
self._WaitForExtensionsToLoad()
def _RaiseOnLoginFailure(self, error):
if self._platform_backend.CanTakeScreenshot():
self._cri.TakeScreenshotWithPrefix('login-screen')
raise exceptions.LoginException(error)