1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import os 7 8from telemetry.core import exceptions 9from telemetry.core import util 10from telemetry import decorators 11from telemetry.internal.backends.chrome import chrome_browser_backend 12from telemetry.internal.backends.chrome import misc_web_contents_backend 13from telemetry.internal import forwarders 14 15import py_utils 16 17 18class CrOSBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): 19 def __init__(self, cros_platform_backend, browser_options, cri, is_guest): 20 super(CrOSBrowserBackend, self).__init__( 21 cros_platform_backend, supports_tab_control=True, 22 supports_extensions=not is_guest, 23 browser_options=browser_options) 24 assert browser_options.IsCrosBrowserOptions() 25 # Initialize fields so that an explosion during init doesn't break in Close. 26 self._cri = cri 27 self._is_guest = is_guest 28 self._forwarder = None 29 self._remote_debugging_port = self._cri.GetRemotePort() 30 self._port = self._remote_debugging_port 31 32 extensions_to_load = browser_options.extensions_to_load 33 34 # Copy extensions to temp directories on the device. 35 # Note that we also perform this copy locally to ensure that 36 # the owner of the extensions is set to chronos. 37 for e in extensions_to_load: 38 extension_dir = cri.RunCmdOnDevice( 39 ['mktemp', '-d', '/tmp/extension_XXXXX'])[0].rstrip() 40 e.local_path = os.path.join(extension_dir, os.path.basename(e.path)) 41 cri.PushFile(e.path, extension_dir) 42 cri.Chown(extension_dir) 43 44 self._cri.RestartUI(self.browser_options.clear_enterprise_policy) 45 py_utils.WaitFor(self.IsBrowserRunning, 20) 46 47 # Delete test user's cryptohome vault (user data directory). 48 if not self.browser_options.dont_override_profile: 49 self._cri.RunCmdOnDevice(['cryptohome', '--action=remove', '--force', 50 '--user=%s' % self._username]) 51 52 @property 53 def log_file_path(self): 54 return None 55 56 def GetBrowserStartupArgs(self): 57 args = super(CrOSBrowserBackend, self).GetBrowserStartupArgs() 58 59 logging_patterns = ['*/chromeos/net/*', 60 '*/chromeos/login/*', 61 'chrome_browser_main_posix'] 62 vmodule = '--vmodule=' 63 for pattern in logging_patterns: 64 vmodule += '%s=2,' % pattern 65 vmodule = vmodule.rstrip(',') 66 67 args.extend([ 68 '--enable-smooth-scrolling', 69 '--enable-threaded-compositing', 70 # Allow devtools to connect to chrome. 71 '--remote-debugging-port=%i' % self._remote_debugging_port, 72 # Open a maximized window. 73 '--start-maximized', 74 # Disable system startup sound. 75 '--ash-disable-system-sounds', 76 # Ignore DMServer errors for policy fetches. 77 '--allow-failed-policy-fetch-for-test', 78 # Skip user image selection screen, and post login screens. 79 '--oobe-skip-postlogin', 80 # Debug logging. 81 vmodule]) 82 83 # Disable GAIA services unless we're using GAIA login, or if there's an 84 # explicit request for it. 85 if (self.browser_options.disable_gaia_services and 86 not self.browser_options.gaia_login): 87 args.append('--disable-gaia-services') 88 89 trace_config_file = (self.platform_backend.tracing_controller_backend 90 .GetChromeTraceConfigFile()) 91 if trace_config_file: 92 args.append('--trace-config-file=%s' % trace_config_file) 93 94 return args 95 96 @property 97 def pid(self): 98 return self._cri.GetChromePid() 99 100 @property 101 def browser_directory(self): 102 result = self._cri.GetChromeProcess() 103 if result and 'path' in result: 104 return os.path.dirname(result['path']) 105 return None 106 107 @property 108 def profile_directory(self): 109 return '/home/chronos/Default' 110 111 def __del__(self): 112 self.Close() 113 114 def Start(self): 115 # Escape all commas in the startup arguments we pass to Chrome 116 # because dbus-send delimits array elements by commas 117 startup_args = [a.replace(',', '\\,') for a in self.GetBrowserStartupArgs()] 118 119 # Restart Chrome with the login extension and remote debugging. 120 pid = self.pid 121 logging.info('Restarting Chrome (pid=%d) with remote port', pid) 122 args = ['dbus-send', '--system', '--type=method_call', 123 '--dest=org.chromium.SessionManager', 124 '/org/chromium/SessionManager', 125 'org.chromium.SessionManagerInterface.EnableChromeTesting', 126 'boolean:true', 127 'array:string:"%s"' % ','.join(startup_args)] 128 logging.info(' '.join(args)) 129 self._cri.RunCmdOnDevice(args) 130 131 if not self._cri.local: 132 # TODO(crbug.com/404771): Move port forwarding to network_controller. 133 self._port = util.GetUnreservedAvailableLocalPort() 134 self._forwarder = self._platform_backend.forwarder_factory.Create( 135 forwarders.PortPair(self._port, self._remote_debugging_port), 136 use_remote_port_forwarding=False) 137 138 # Wait for new chrome and oobe. 139 py_utils.WaitFor(lambda: pid != self.pid, 15) 140 self._WaitForBrowserToComeUp() 141 self._InitDevtoolsClientBackend( 142 remote_devtools_port=self._remote_debugging_port) 143 py_utils.WaitFor(lambda: self.oobe_exists, 30) 144 145 if self.browser_options.auto_login: 146 if self._is_guest: 147 pid = self.pid 148 self.oobe.NavigateGuestLogin() 149 # Guest browsing shuts down the current browser and launches an 150 # incognito browser in a separate process, which we need to wait for. 151 try: 152 # TODO(achuith): Reduce this timeout to 15 sec after crbug.com/631640 153 # is resolved. 154 py_utils.WaitFor(lambda: pid != self.pid, 60) 155 except py_utils.TimeoutException: 156 self._RaiseOnLoginFailure( 157 'Failed to restart browser in guest mode (pid %d).' % pid) 158 159 elif self.browser_options.gaia_login: 160 self.oobe.NavigateGaiaLogin(self._username, self._password) 161 else: 162 self.oobe.NavigateFakeLogin(self._username, self._password, 163 self._gaia_id, not self.browser_options.disable_gaia_services) 164 165 try: 166 self._WaitForLogin() 167 except py_utils.TimeoutException: 168 self._RaiseOnLoginFailure('Timed out going through login screen. ' 169 + self._GetLoginStatus()) 170 171 logging.info('Browser is up!') 172 173 def Background(self): 174 raise NotImplementedError 175 176 def Close(self): 177 super(CrOSBrowserBackend, self).Close() 178 179 if self._cri: 180 self._cri.RestartUI(False) # Logs out. 181 self._cri.CloseConnection() 182 183 py_utils.WaitFor(lambda: not self._IsCryptohomeMounted(), 180) 184 185 if self._forwarder: 186 self._forwarder.Close() 187 self._forwarder = None 188 189 if self._cri: 190 for e in self._extensions_to_load: 191 self._cri.RmRF(os.path.dirname(e.local_path)) 192 193 self._cri = None 194 195 def IsBrowserRunning(self): 196 return bool(self.pid) 197 198 def GetStandardOutput(self): 199 return 'Cannot get standard output on CrOS' 200 201 def GetStackTrace(self): 202 return (False, 'Cannot get stack trace on CrOS') 203 204 def GetMostRecentMinidumpPath(self): 205 return None 206 207 def GetAllMinidumpPaths(self): 208 return None 209 210 def GetAllUnsymbolizedMinidumpPaths(self): 211 return None 212 213 def SymbolizeMinidump(self, minidump_path): 214 return None 215 216 @property 217 @decorators.Cache 218 def misc_web_contents_backend(self): 219 """Access to chrome://oobe/login page.""" 220 return misc_web_contents_backend.MiscWebContentsBackend(self) 221 222 @property 223 def oobe(self): 224 return self.misc_web_contents_backend.GetOobe() 225 226 @property 227 def oobe_exists(self): 228 return self.misc_web_contents_backend.oobe_exists 229 230 @property 231 def _username(self): 232 return self.browser_options.username 233 234 @property 235 def _password(self): 236 return self.browser_options.password 237 238 @property 239 def _gaia_id(self): 240 return self.browser_options.gaia_id 241 242 def _IsCryptohomeMounted(self): 243 username = '$guest' if self._is_guest else self._username 244 return self._cri.IsCryptohomeMounted(username, self._is_guest) 245 246 def _GetLoginStatus(self): 247 """Returns login status. If logged in, empty string is returned.""" 248 status = '' 249 if not self._IsCryptohomeMounted(): 250 status += 'Cryptohome not mounted. ' 251 if not self.HasBrowserFinishedLaunching(): 252 status += 'Browser didn\'t launch. ' 253 if self.oobe_exists: 254 status += 'OOBE not dismissed.' 255 return status 256 257 def _IsLoggedIn(self): 258 """Returns True if cryptohome has mounted, the browser is 259 responsive to devtools requests, and the oobe has been dismissed.""" 260 return not self._GetLoginStatus() 261 262 def _WaitForLogin(self): 263 # Wait for cryptohome to mount. 264 py_utils.WaitFor(self._IsLoggedIn, 900) 265 266 # For incognito mode, the session manager actually relaunches chrome with 267 # new arguments, so we have to wait for the browser to come up. 268 self._WaitForBrowserToComeUp() 269 270 # Wait for extensions to load. 271 if self._supports_extensions: 272 self._WaitForExtensionsToLoad() 273 274 def _RaiseOnLoginFailure(self, error): 275 if self._platform_backend.CanTakeScreenshot(): 276 self._cri.TakeScreenshotWithPrefix('login-screen') 277 raise exceptions.LoginException(error) 278