Index: tools/telemetry/telemetry/core/chrome/cros_browser_backend.py |
diff --git a/tools/telemetry/telemetry/core/chrome/cros_browser_backend.py b/tools/telemetry/telemetry/core/chrome/cros_browser_backend.py |
deleted file mode 100644 |
index fd4b1621b5e02e42fee921a5fcbf400ff0236003..0000000000000000000000000000000000000000 |
--- a/tools/telemetry/telemetry/core/chrome/cros_browser_backend.py |
+++ /dev/null |
@@ -1,434 +0,0 @@ |
-# Copyright (c) 2012 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 |
-import subprocess |
- |
-from telemetry.core import exceptions |
-from telemetry.core import util |
-from telemetry.core.backends import browser_backend |
-from telemetry.core.backends.chrome import chrome_browser_backend |
- |
-class CrOSBrowserBackend(chrome_browser_backend.ChromeBrowserBackend): |
- # Some developers' workflow includes running the Chrome process from |
- # /usr/local/... instead of the default location. We have to check for both |
- # paths in order to support this workflow. |
- CHROME_PATHS = ['/opt/google/chrome/chrome ', |
- '/usr/local/opt/google/chrome/chrome '] |
- |
- def __init__(self, browser_type, options, cri, is_guest): |
- super(CrOSBrowserBackend, self).__init__( |
- is_content_shell=False, supports_extensions=not is_guest, |
- options=options) |
- # Initialize fields so that an explosion during init doesn't break in Close. |
- self._browser_type = browser_type |
- self._options = options |
- self._cri = cri |
- self._is_guest = is_guest |
- |
- self._remote_debugging_port = self._cri.GetRemotePort() |
- self._port = self._remote_debugging_port |
- self._forwarder = None |
- |
- self._SetBranchNumber(self._GetChromeVersion()) |
- |
- self._login_ext_dir = os.path.join(os.path.dirname(__file__), |
- 'chromeos_login_ext') |
- |
- # Push a dummy login extension to the device. |
- # This extension automatically logs in as test@test.test |
- # Note that we also perform this copy locally to ensure that |
- # the owner of the extensions is set to chronos. |
- logging.info('Copying dummy login extension to the device') |
- cri.PushFile(self._login_ext_dir, '/tmp/') |
- self._login_ext_dir = '/tmp/chromeos_login_ext' |
- cri.RunCmdOnDevice(['chown', '-R', 'chronos:chronos', |
- self._login_ext_dir]) |
- |
- # 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 options.extensions_to_load: |
- output = cri.RunCmdOnDevice(['mktemp', '-d', '/tmp/extension_XXXXX']) |
- extension_dir = output[0].rstrip() |
- cri.PushFile(e.path, extension_dir) |
- cri.RunCmdOnDevice(['chown', '-R', 'chronos:chronos', extension_dir]) |
- e.local_path = os.path.join(extension_dir, os.path.basename(e.path)) |
- |
- # Ensure the UI is running and logged out. |
- self._RestartUI() |
- util.WaitFor(lambda: self.IsBrowserRunning(), 20) # pylint: disable=W0108 |
- |
- # Delete test@test.test's cryptohome vault (user data directory). |
- if not options.dont_override_profile: |
- logging.info('Deleting user\'s cryptohome vault (the user data dir)') |
- self._cri.RunCmdOnDevice( |
- ['cryptohome', '--action=remove', '--force', '--user=test@test.test']) |
- if options.profile_dir: |
- profile_dir = '/home/chronos/Default' |
- cri.RunCmdOnDevice(['rm', '-rf', profile_dir]) |
- cri.PushFile(options.profile_dir + '/Default', profile_dir) |
- cri.RunCmdOnDevice(['chown', '-R', 'chronos:chronos', profile_dir]) |
- |
- def GetBrowserStartupArgs(self): |
- self.webpagereplay_remote_http_port = self._cri.GetRemotePort() |
- self.webpagereplay_remote_https_port = self._cri.GetRemotePort() |
- |
- args = super(CrOSBrowserBackend, self).GetBrowserStartupArgs() |
- |
- args.extend([ |
- '--enable-smooth-scrolling', |
- '--enable-threaded-compositing', |
- '--enable-per-tile-painting', |
- '--force-compositing-mode', |
- # Disables the start page, as well as other external apps that can |
- # steal focus or make measurements inconsistent. |
- '--disable-default-apps', |
- # Skip user image selection screen, and post login screens. |
- '--oobe-skip-postlogin', |
- # Allow devtools to connect to chrome. |
- '--remote-debugging-port=%i' % self._remote_debugging_port, |
- # Open a maximized window. |
- '--start-maximized', |
- # Debug logging for login flake (crbug.com/263527). |
- '--vmodule=*/browser/automation/*=2,*/chromeos/net/*=2,' + |
- '*/chromeos/login/*=2']) |
- |
- |
- if self.chrome_branch_number <= 1599: |
- args.extend([ |
- # Jump to the login screen, skipping network selection, eula, etc. |
- '--login-screen=login', |
- # Skip hwid check, for VMs and pre-MP lab devices. |
- '--skip-hwid-check',]) |
- if not self._is_guest: |
- # This extension bypasses gaia and logs us in. |
- args.append('--auth-ext-path=%s' % self._login_ext_dir) |
- |
- return args |
- |
- def _GetSessionManagerPid(self, procs): |
- """Returns the pid of the session_manager process, given the list of |
- processes.""" |
- for pid, process, _, _ in procs: |
- if process.startswith('/sbin/session_manager '): |
- return pid |
- return None |
- |
- def _GetChromeProcess(self): |
- """Locates the the main chrome browser process. |
- |
- Chrome on cros is usually in /opt/google/chrome, but could be in |
- /usr/local/ for developer workflows - debug chrome is too large to fit on |
- rootfs. |
- |
- Chrome spawns multiple processes for renderers. pids wrap around after they |
- are exhausted so looking for the smallest pid is not always correct. We |
- locate the session_manager's pid, and look for the chrome process that's an |
- immediate child. This is the main browser process. |
- """ |
- procs = self._cri.ListProcesses() |
- session_manager_pid = self._GetSessionManagerPid(procs) |
- if not session_manager_pid: |
- return None |
- |
- # Find the chrome process that is the child of the session_manager. |
- for pid, process, ppid, _ in procs: |
- if ppid != session_manager_pid: |
- continue |
- for path in self.CHROME_PATHS: |
- if process.startswith(path): |
- return {'pid': pid, 'path': path} |
- return None |
- |
- def _GetChromeVersion(self): |
- util.WaitFor(lambda: self._GetChromeProcess(), # pylint: disable=W0108 |
- timeout=30) |
- result = self._GetChromeProcess() |
- assert result and result['path'] |
- (version, _) = self._cri.RunCmdOnDevice([result['path'], '--version']) |
- assert version |
- return version |
- |
- @property |
- def pid(self): |
- result = self._GetChromeProcess() |
- if result and 'pid' in result: |
- return result['pid'] |
- return None |
- |
- @property |
- def browser_directory(self): |
- result = self._GetChromeProcess() |
- if result and 'path' in result: |
- return os.path.dirname(result['path']) |
- return None |
- |
- @property |
- def profile_directory(self): |
- return '/home/chronos/Default' |
- |
- @property |
- def hwid(self): |
- return self._cri.RunCmdOnDevice(['/usr/bin/crossystem', 'hwid'])[0] |
- |
- def GetRemotePort(self, _): |
- return self._cri.GetRemotePort() |
- |
- 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. |
- logging.info('Restarting Chrome with flags and login') |
- 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)] |
- self._cri.RunCmdOnDevice(args) |
- |
- if not self._cri.local: |
- # Find a free local port. |
- self._port = util.GetAvailableLocalPort() |
- |
- # Forward the remote debugging port. |
- logging.info('Forwarding remote debugging port') |
- self._forwarder = SSHForwarder( |
- self._cri, 'L', |
- util.PortPair(self._port, self._remote_debugging_port)) |
- |
- # Wait for the browser to come up. |
- logging.info('Waiting for browser to be ready') |
- try: |
- self._WaitForBrowserToComeUp() |
- self._PostBrowserStartupInitialization() |
- except: |
- import traceback |
- traceback.print_exc() |
- self.Close() |
- raise |
- |
- # chrome_branch_number is set in _PostBrowserStartupInitialization. |
- # Without --skip-hwid-check (introduced in crrev.com/203397), devices/VMs |
- # will be stuck on the bad hwid screen. |
- if self.chrome_branch_number <= 1500 and not self.hwid: |
- raise exceptions.LoginException( |
- 'Hardware id not set on device/VM. --skip-hwid-check not supported ' |
- 'with chrome branches 1500 or earlier.') |
- |
- if self._is_guest: |
- pid = self.pid |
- self._NavigateGuestLogin() |
- # Guest browsing shuts down the current browser and launches an incognito |
- # browser in a separate process, which we need to wait for. |
- util.WaitFor(lambda: pid != self.pid, 10) |
- self._WaitForBrowserToComeUp() |
- else: |
- self._NavigateLogin() |
- |
- logging.info('Browser is up!') |
- |
- def Close(self): |
- super(CrOSBrowserBackend, self).Close() |
- |
- self._RestartUI() # Logs out. |
- |
- if not self._cri.local: |
- if self._forwarder: |
- self._forwarder.Close() |
- self._forwarder = None |
- |
- if self._login_ext_dir: |
- self._cri.RmRF(self._login_ext_dir) |
- self._login_ext_dir = None |
- |
- for e in self._options.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 'Cannot get stack trace on CrOS' |
- |
- def CreateForwarder(self, *port_pairs): |
- assert self._cri |
- return (browser_backend.DoNothingForwarder(*port_pairs) if self._cri.local |
- else SSHForwarder(self._cri, 'R', *port_pairs)) |
- |
- def _RestartUI(self): |
- if self._cri: |
- logging.info('(Re)starting the ui (logs the user out)') |
- if self._cri.IsServiceRunning('ui'): |
- self._cri.RunCmdOnDevice(['restart', 'ui']) |
- else: |
- self._cri.RunCmdOnDevice(['start', 'ui']) |
- |
- @property |
- def oobe(self): |
- return self.misc_web_contents_backend.GetOobe() |
- |
- def _SigninUIState(self): |
- """Returns the signin ui state of the oobe. HIDDEN: 0, GAIA_SIGNIN: 1, |
- ACCOUNT_PICKER: 2, WRONG_HWID_WARNING: 3, MANAGED_USER_CREATION_FLOW: 4. |
- These values are in |
- chrome/browser/resources/chromeos/login/display_manager.js |
- """ |
- return self.oobe.EvaluateJavaScript(''' |
- loginHeader = document.getElementById('login-header-bar') |
- if (loginHeader) { |
- loginHeader.signinUIState_; |
- } |
- ''') |
- |
- def _IsCryptohomeMounted(self): |
- """Returns True if a cryptohome vault is mounted at /home/chronos/user.""" |
- return self._cri.FilesystemMountedAt('/home/chronos/user').startswith( |
- '/home/.shadow/') |
- |
- def _HandleUserImageSelectionScreen(self): |
- """If we're stuck on the user image selection screen, we click the ok |
- button. |
- """ |
- oobe = self.oobe |
- if oobe: |
- try: |
- oobe.EvaluateJavaScript(""" |
- var ok = document.getElementById("ok-button"); |
- if (ok) { |
- ok.click(); |
- } |
- """) |
- except (exceptions.TabCrashException): |
- pass |
- |
- def _IsLoggedIn(self): |
- """Returns True if we're logged in (cryptohome has mounted), and the oobe |
- has been dismissed.""" |
- if self.chrome_branch_number <= 1547: |
- self._HandleUserImageSelectionScreen() |
- return self._IsCryptohomeMounted() and not self.oobe |
- |
- def _StartupWindow(self): |
- """Closes the startup window, which is an extension on official builds, |
- and a webpage on chromiumos""" |
- startup_window_ext_id = 'honijodknafkokifofgiaalefdiedpko' |
- return (self.extension_dict_backend[startup_window_ext_id] |
- if startup_window_ext_id in self.extension_dict_backend |
- else self.tab_list_backend.Get(0, None)) |
- |
- def _WaitForAccountPicker(self): |
- """Waits for the oobe screen to be in the account picker state.""" |
- util.WaitFor(lambda: self._SigninUIState() == 2, 20) |
- |
- def _ClickBrowseAsGuest(self): |
- """Click the Browse As Guest button on the account picker screen. This will |
- restart the browser, and we could have a tab crash or a browser crash.""" |
- try: |
- self.oobe.EvaluateJavaScript(""" |
- var guest = document.getElementById("guest-user-button"); |
- if (guest) { |
- guest.click(); |
- } |
- """) |
- except (exceptions.TabCrashException, |
- exceptions.BrowserConnectionGoneException): |
- pass |
- |
- def _WaitForGuestFsMounted(self): |
- """Waits for /home/chronos/user to be mounted as guestfs""" |
- util.WaitFor(lambda: (self._cri.FilesystemMountedAt('/home/chronos/user') == |
- 'guestfs'), 20) |
- |
- def _NavigateGuestLogin(self): |
- """Navigates through oobe login screen as guest""" |
- assert self.oobe |
- self._WaitForAccountPicker() |
- self._ClickBrowseAsGuest() |
- self._WaitForGuestFsMounted() |
- |
- def _NavigateLogin(self): |
- """Navigates through oobe login screen""" |
- if self.chrome_branch_number > 1599: |
- util.WaitFor(lambda: self.oobe, 10) |
- util.WaitFor(lambda: self.oobe.EvaluateJavaScript( |
- 'typeof Oobe !== \'undefined\''), 10) |
- |
- if self.oobe.EvaluateJavaScript( |
- 'typeof Oobe.loginForTesting == \'undefined\''): |
- raise exceptions.LoginException('Oobe.loginForTesting js api missing') |
- |
- username = 'test@test.test' |
- password = '' |
- self.oobe.ExecuteJavaScript( |
- 'Oobe.loginForTesting(\'%s\', \'%s\');' % (username, password)) |
- |
- try: |
- util.WaitFor(lambda: self._IsLoggedIn(), 60) # pylint: disable=W0108 |
- except util.TimeoutException: |
- self._cri.TakeScreenShot('login-screen') |
- raise exceptions.LoginException( |
- 'Timed out going through oobe screen. Make sure the custom auth ' |
- 'extension passed through --auth-ext-path is valid and belongs ' |
- 'to user "chronos".') |
- |
- if self.chrome_branch_number < 1500: |
- # Wait for the startup window, then close it. Startup window doesn't exist |
- # post-M27. crrev.com/197900 |
- util.WaitFor(lambda: self._StartupWindow() is not None, 20) |
- self._StartupWindow().Close() |
- else: |
- # Open a new window/tab. |
- self.tab_list_backend.New(15) |
- |
- |
-class SSHForwarder(object): |
- def __init__(self, cri, forwarding_flag, *port_pairs): |
- self._proc = None |
- |
- if forwarding_flag == 'R': |
- self._host_port = port_pairs[0].remote_port |
- command_line = ['-%s%i:localhost:%i' % (forwarding_flag, |
- port_pair.remote_port, |
- port_pair.local_port) |
- for port_pair in port_pairs] |
- else: |
- self._host_port = port_pairs[0].local_port |
- command_line = ['-%s%i:localhost:%i' % (forwarding_flag, |
- port_pair.local_port, |
- port_pair.remote_port) |
- for port_pair in port_pairs] |
- |
- self._device_port = port_pairs[0].remote_port |
- |
- self._proc = subprocess.Popen( |
- cri.FormSSHCommandLine(['sleep', '999999999'], command_line), |
- stdout=subprocess.PIPE, |
- stderr=subprocess.PIPE, |
- stdin=subprocess.PIPE, |
- shell=False) |
- |
- util.WaitFor(lambda: cri.IsHTTPServerRunningOnPort(self._device_port), 60) |
- |
- @property |
- def url(self): |
- assert self._proc |
- return 'http://localhost:%i' % self._host_port |
- |
- def Close(self): |
- if self._proc: |
- self._proc.kill() |
- self._proc = None |
- |