• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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