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 import decorators 9from telemetry.core import exceptions 10from telemetry.core import forwarders 11from telemetry.core import util 12from telemetry.core.backends.chrome import chrome_browser_backend 13from telemetry.core.backends.chrome import misc_web_contents_backend 14from telemetry.core.forwarders import cros_forwarder 15 16 17class CrOSBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): 18 def __init__(self, browser_options, cri, is_guest, extensions_to_load): 19 super(CrOSBrowserBackend, self).__init__( 20 supports_tab_control=True, supports_extensions=not is_guest, 21 browser_options=browser_options, 22 output_profile_path=None, extensions_to_load=extensions_to_load) 23 24 # Initialize fields so that an explosion during init doesn't break in Close. 25 self._cri = cri 26 self._is_guest = is_guest 27 self._forwarder = None 28 29 from telemetry.core.backends.chrome import chrome_browser_options 30 assert isinstance(browser_options, 31 chrome_browser_options.CrosBrowserOptions) 32 33 self.wpr_port_pairs = forwarders.PortPairs( 34 http=forwarders.PortPair(self.wpr_port_pairs.http.local_port, 35 self.GetRemotePort( 36 self.wpr_port_pairs.http.local_port)), 37 https=forwarders.PortPair(self.wpr_port_pairs.https.local_port, 38 self.GetRemotePort( 39 self.wpr_port_pairs.http.local_port)), 40 dns=None) 41 self._remote_debugging_port = self._cri.GetRemotePort() 42 self._port = self._remote_debugging_port 43 44 # Copy extensions to temp directories on the device. 45 # Note that we also perform this copy locally to ensure that 46 # the owner of the extensions is set to chronos. 47 for e in extensions_to_load: 48 extension_dir = cri.RunCmdOnDevice( 49 ['mktemp', '-d', '/tmp/extension_XXXXX'])[0].rstrip() 50 cri.PushFile(e.path, extension_dir) 51 cri.Chown(extension_dir) 52 e.local_path = os.path.join(extension_dir, os.path.basename(e.path)) 53 54 self._cri.RestartUI(self.browser_options.clear_enterprise_policy) 55 util.WaitFor(self.IsBrowserRunning, 20) 56 57 # Delete test user's cryptohome vault (user data directory). 58 if not self.browser_options.dont_override_profile: 59 self._cri.RunCmdOnDevice(['cryptohome', '--action=remove', '--force', 60 '--user=%s' % self._username]) 61 if self.browser_options.profile_dir: 62 cri.RmRF(self.profile_directory) 63 cri.PushFile(self.browser_options.profile_dir + '/Default', 64 self.profile_directory) 65 cri.Chown(self.profile_directory) 66 67 def GetBrowserStartupArgs(self): 68 args = super(CrOSBrowserBackend, self).GetBrowserStartupArgs() 69 args.extend([ 70 '--enable-smooth-scrolling', 71 '--enable-threaded-compositing', 72 '--enable-per-tile-painting', 73 # Disables the start page, as well as other external apps that can 74 # steal focus or make measurements inconsistent. 75 '--disable-default-apps', 76 # Allow devtools to connect to chrome. 77 '--remote-debugging-port=%i' % self._remote_debugging_port, 78 # Open a maximized window. 79 '--start-maximized', 80 # Skip user image selection screen, and post login screens. 81 '--oobe-skip-postlogin', 82 # Debug logging. 83 '--vmodule=*/chromeos/net/*=2,*/chromeos/login/*=2']) 84 85 # Disable GAIA services unless we're using GAIA login, or if there's an 86 # explicit request for it. 87 if (self.browser_options.disable_gaia_services and 88 not self.browser_options.gaia_login): 89 args.append('--disable-gaia-services') 90 91 return args 92 93 @property 94 def pid(self): 95 return self._cri.GetChromePid() 96 97 @property 98 def browser_directory(self): 99 result = self._cri.GetChromeProcess() 100 if result and 'path' in result: 101 return os.path.dirname(result['path']) 102 return None 103 104 @property 105 def profile_directory(self): 106 return '/home/chronos/Default' 107 108 def GetRemotePort(self, port): 109 if self._cri.local: 110 return port 111 return self._cri.GetRemotePort() 112 113 def __del__(self): 114 self.Close() 115 116 def Start(self): 117 # Escape all commas in the startup arguments we pass to Chrome 118 # because dbus-send delimits array elements by commas 119 startup_args = [a.replace(',', '\\,') for a in self.GetBrowserStartupArgs()] 120 121 # Restart Chrome with the login extension and remote debugging. 122 logging.info('Restarting Chrome with flags and login') 123 args = ['dbus-send', '--system', '--type=method_call', 124 '--dest=org.chromium.SessionManager', 125 '/org/chromium/SessionManager', 126 'org.chromium.SessionManagerInterface.EnableChromeTesting', 127 'boolean:true', 128 'array:string:"%s"' % ','.join(startup_args)] 129 self._cri.RunCmdOnDevice(args) 130 131 if not self._cri.local: 132 self._port = util.GetUnreservedAvailableLocalPort() 133 self._forwarder = self.forwarder_factory.Create( 134 forwarders.PortPairs( 135 http=forwarders.PortPair(self._port, self._remote_debugging_port), 136 https=None, 137 dns=None), forwarding_flag='L') 138 139 # Wait for oobe. 140 self._WaitForBrowserToComeUp(wait_for_extensions=False) 141 util.WaitFor(lambda: self.oobe_exists, 10) 142 143 if self.browser_options.auto_login: 144 try: 145 if self._is_guest: 146 pid = self.pid 147 self.oobe.NavigateGuestLogin() 148 # Guest browsing shuts down the current browser and launches an 149 # incognito browser in a separate process, which we need to wait for. 150 util.WaitFor(lambda: pid != self.pid, 10) 151 elif self.browser_options.gaia_login: 152 self.oobe.NavigateGaiaLogin(self._username, self._password) 153 else: 154 self.oobe.NavigateFakeLogin(self._username, self._password) 155 self._WaitForLogin() 156 except util.TimeoutException: 157 self._cri.TakeScreenShot('login-screen') 158 raise exceptions.LoginException('Timed out going through login screen') 159 160 logging.info('Browser is up!') 161 162 def Close(self): 163 super(CrOSBrowserBackend, self).Close() 164 165 if self._cri: 166 self._cri.RestartUI(False) # Logs out. 167 self._cri.CloseConnection() 168 169 util.WaitFor(lambda: not self._IsCryptohomeMounted(), 30) 170 171 if self._forwarder: 172 self._forwarder.Close() 173 self._forwarder = None 174 175 if self._cri: 176 for e in self._extensions_to_load: 177 self._cri.RmRF(os.path.dirname(e.local_path)) 178 179 self._cri = None 180 181 @property 182 @decorators.Cache 183 def forwarder_factory(self): 184 return cros_forwarder.CrOsForwarderFactory(self._cri) 185 186 def IsBrowserRunning(self): 187 return bool(self.pid) 188 189 def GetStandardOutput(self): 190 return 'Cannot get standard output on CrOS' 191 192 def GetStackTrace(self): 193 return 'Cannot get stack trace on CrOS' 194 195 @property 196 @decorators.Cache 197 def misc_web_contents_backend(self): 198 """Access to chrome://oobe/login page.""" 199 return misc_web_contents_backend.MiscWebContentsBackend(self) 200 201 @property 202 def oobe(self): 203 return self.misc_web_contents_backend.GetOobe() 204 205 @property 206 def oobe_exists(self): 207 return self.misc_web_contents_backend.oobe_exists 208 209 @property 210 def _username(self): 211 return self.browser_options.username 212 213 @property 214 def _password(self): 215 return self.browser_options.password 216 217 def _IsCryptohomeMounted(self): 218 username = '$guest' if self._is_guest else self._username 219 return self._cri.IsCryptohomeMounted(username, self._is_guest) 220 221 def _IsLoggedIn(self): 222 """Returns True if cryptohome has mounted, the browser is 223 responsive to devtools requests, and the oobe has been dismissed.""" 224 return (self._IsCryptohomeMounted() and 225 self.HasBrowserFinishedLaunching() and 226 not self.oobe_exists) 227 228 def _WaitForLogin(self): 229 # Wait for cryptohome to mount. 230 util.WaitFor(self._IsLoggedIn, 60) 231 232 # Wait for extensions to load. 233 self._WaitForBrowserToComeUp() 234 235 # Workaround for crbug.com/374462 - the bug doesn't manifest in the guest 236 # session, which also starts with an open browser tab. 237 retries = 3 238 while not self._is_guest and not self.browser_options.gaia_login: 239 try: 240 # Open a new window/tab. 241 tab = self.tab_list_backend.New(timeout=30) 242 tab.Navigate('about:blank', timeout=10) 243 break 244 except (exceptions.TabCrashException, util.TimeoutException, 245 IndexError): 246 retries -= 1 247 logging.warning('TabCrashException/TimeoutException in ' 248 'new tab creation/navigation, ' 249 'remaining retries %d', retries) 250 if not retries: 251 raise 252