1# Copyright (c) 2013 The Chromium OS 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, os, re
6
7from autotest_lib.client.common_lib.cros import arc_util
8from autotest_lib.client.cros import constants
9from autotest_lib.client.bin import utils
10from telemetry.core import cros_interface, exceptions, util
11from telemetry.internal.browser import browser_finder, browser_options
12from telemetry.internal.browser import extension_to_load
13
14CAP_USERNAME = 'crosautotest@gmail.com'
15CAP_URL = ('https://sites.google.com/a/chromium.org/dev/chromium-os'
16           '/testing/cros-autotest/cap')
17
18Error = exceptions.Error
19
20def NormalizeEmail(username):
21    """Remove dots from username. Add @gmail.com if necessary.
22
23    TODO(achuith): Get rid of this when crbug.com/358427 is fixed.
24
25    @param username: username/email to be scrubbed.
26    """
27    parts = re.split('@', username)
28    parts[0] = re.sub('\.', '', parts[0])
29
30    if len(parts) == 1:
31        parts.append('gmail.com')
32    return '@'.join(parts)
33
34
35class Chrome(object):
36    """Wrapper for creating a telemetry browser instance with extensions.
37
38    The recommended way to use this class is to create the instance using the
39    with statement:
40
41    >>> with chrome.Chrome(...) as cr:
42    >>>     # Do whatever you need with cr.
43    >>>     pass
44
45    This will make sure all the clean-up functions are called.  If you really
46    need to use this class without the with statement, make sure to call the
47    close() method once you're done with the Chrome instance.
48    """
49
50
51    BROWSER_TYPE_LOGIN = 'system'
52    BROWSER_TYPE_GUEST = 'system-guest'
53
54
55    def __init__(self, logged_in=True, extension_paths=[], autotest_ext=False,
56                 num_tries=3, extra_browser_args=None,
57                 clear_enterprise_policy=True, dont_override_profile=False,
58                 disable_gaia_services=True, disable_default_apps = True,
59                 auto_login=True, gaia_login=False,
60                 username=None, password=None, gaia_id=None,
61                 arc_mode=None, disable_arc_opt_in=True,
62                 init_network_controller=False):
63        """
64        Constructor of telemetry wrapper.
65
66        @param logged_in: Regular user (True) or guest user (False).
67        @param extension_paths: path of unpacked extension to install.
68        @param autotest_ext: Load a component extension with privileges to
69                             invoke chrome.autotestPrivate.
70        @param num_tries: Number of attempts to log in.
71        @param extra_browser_args: Additional argument(s) to pass to the
72                                   browser. It can be a string or a list.
73        @param clear_enterprise_policy: Clear enterprise policy before
74                                        logging in.
75        @param dont_override_profile: Don't delete cryptohome before login.
76                                      Telemetry will output a warning with this
77                                      option.
78        @param disable_gaia_services: For enterprise autotests, this option may
79                                      be used to enable policy fetch.
80        @param disable_default_apps: For tests that exercise default apps.
81        @param auto_login: Does not login automatically if this is False.
82                           Useful if you need to examine oobe.
83        @param gaia_login: Logs in to real gaia.
84        @param username: Log in using this username instead of the default.
85        @param password: Log in using this password instead of the default.
86        @param gaia_id: Log in using this gaia_id instead of the default.
87        @param arc_mode: How ARC instance should be started.  Default is to not
88                         start.
89        @param disable_arc_opt_in: For opt in flow autotest. This option is used
90                                   to disable the arc opt in flow.
91        """
92        self._autotest_ext_path = None
93        if autotest_ext:
94            self._autotest_ext_path = os.path.join(os.path.dirname(__file__),
95                                                   'autotest_private_ext')
96            extension_paths.append(self._autotest_ext_path)
97
98        finder_options = browser_options.BrowserFinderOptions()
99        if utils.is_arc_available() and arc_util.should_start_arc(arc_mode):
100            if disable_arc_opt_in:
101                finder_options.browser_options.AppendExtraBrowserArgs(
102                        arc_util.get_extra_chrome_flags())
103            logged_in = True
104
105        self._browser_type = (self.BROWSER_TYPE_LOGIN
106                if logged_in else self.BROWSER_TYPE_GUEST)
107        finder_options.browser_type = self.browser_type
108        if extra_browser_args:
109            finder_options.browser_options.AppendExtraBrowserArgs(
110                    extra_browser_args)
111
112        # finder options must be set before parse_args(), browser options must
113        # be set before Create().
114        # TODO(crbug.com/360890) Below MUST be '2' so that it doesn't inhibit
115        # autotest debug logs
116        finder_options.verbosity = 2
117        finder_options.CreateParser().parse_args(args=[])
118        b_options = finder_options.browser_options
119        b_options.disable_component_extensions_with_background_pages = False
120        b_options.create_browser_with_oobe = True
121        b_options.clear_enterprise_policy = clear_enterprise_policy
122        b_options.dont_override_profile = dont_override_profile
123        b_options.disable_gaia_services = disable_gaia_services
124        b_options.disable_default_apps = disable_default_apps
125        b_options.disable_component_extensions_with_background_pages = disable_default_apps
126
127        b_options.auto_login = auto_login
128        b_options.gaia_login = gaia_login
129
130        if utils.is_arc_available() and not disable_arc_opt_in:
131            arc_util.set_browser_options_for_opt_in(b_options)
132
133        self.username = b_options.username if username is None else username
134        self.password = b_options.password if password is None else password
135        self.username = NormalizeEmail(self.username)
136        b_options.username = self.username
137        b_options.password = self.password
138        self.gaia_id = b_options.gaia_id if gaia_id is None else gaia_id
139        b_options.gaia_id = self.gaia_id
140
141        self.arc_mode = arc_mode
142
143        if logged_in:
144            extensions_to_load = b_options.extensions_to_load
145            for path in extension_paths:
146                extension = extension_to_load.ExtensionToLoad(
147                        path, self.browser_type)
148                extensions_to_load.append(extension)
149            self._extensions_to_load = extensions_to_load
150
151        # Turn on collection of Chrome coredumps via creation of a magic file.
152        # (Without this, Chrome coredumps are trashed.)
153        open(constants.CHROME_CORE_MAGIC_FILE, 'w').close()
154
155        for i in range(num_tries):
156            try:
157                browser_to_create = browser_finder.FindBrowser(finder_options)
158                self._browser = browser_to_create.Create(finder_options)
159                if utils.is_arc_available():
160                    if disable_arc_opt_in:
161                        if arc_util.should_start_arc(arc_mode):
162                            arc_util.enable_arc_setting(self.browser)
163                    else:
164                        arc_util.opt_in(self.browser)
165                    arc_util.post_processing_after_browser(self)
166                break
167            except exceptions.LoginException as e:
168                logging.error('Timed out logging in, tries=%d, error=%s',
169                              i, repr(e))
170                if i == num_tries-1:
171                    raise
172        if init_network_controller:
173          self._browser.platform.network_controller.InitializeIfNeeded()
174
175    def __enter__(self):
176        return self
177
178
179    def __exit__(self, *args):
180        self.close()
181
182
183    @property
184    def browser(self):
185        """Returns a telemetry browser instance."""
186        return self._browser
187
188
189    def get_extension(self, extension_path):
190        """Fetches a telemetry extension instance given the extension path."""
191        for ext in self._extensions_to_load:
192            if extension_path == ext.path:
193                return self.browser.extensions[ext]
194        return None
195
196
197    @property
198    def autotest_ext(self):
199        """Returns the autotest extension."""
200        return self.get_extension(self._autotest_ext_path)
201
202
203    @property
204    def login_status(self):
205        """Returns login status."""
206        ext = self.autotest_ext
207        if not ext:
208            return None
209
210        ext.ExecuteJavaScript('''
211            window.__login_status = null;
212            chrome.autotestPrivate.loginStatus(function(s) {
213              window.__login_status = s;
214            });
215        ''')
216        return ext.EvaluateJavaScript('window.__login_status')
217
218
219    def get_visible_notifications(self):
220        """Returns an array of visible notifications of Chrome.
221
222        For specific type of each notification, please refer to Chromium's
223        chrome/common/extensions/api/autotest_private.idl.
224        """
225        ext = self.autotest_ext
226        if not ext:
227            return None
228
229        ext.ExecuteJavaScript('''
230            window.__items = null;
231            chrome.autotestPrivate.getVisibleNotifications(function(items) {
232              window.__items  = items;
233            });
234        ''')
235        if ext.EvaluateJavaScript('window.__items') is None:
236            return None
237        return ext.EvaluateJavaScript('window.__items')
238
239
240    @property
241    def browser_type(self):
242        """Returns the browser_type."""
243        return self._browser_type
244
245
246    @staticmethod
247    def did_browser_crash(func):
248        """Runs func, returns True if the browser crashed, False otherwise.
249
250        @param func: function to run.
251
252        """
253        try:
254            func()
255        except Error:
256            return True
257        return False
258
259
260    @staticmethod
261    def wait_for_browser_restart(func):
262        """Runs func, and waits for a browser restart.
263
264        @param func: function to run.
265
266        """
267        _cri = cros_interface.CrOSInterface()
268        pid = _cri.GetChromePid()
269        Chrome.did_browser_crash(func)
270        utils.poll_for_condition(lambda: pid != _cri.GetChromePid(), timeout=60)
271
272
273    def wait_for_browser_to_come_up(self):
274        """Waits for the browser to come up. This should only be called after a
275        browser crash.
276        """
277        def _BrowserReady(cr):
278            tabs = []  # Wrapper for pass by reference.
279            if self.did_browser_crash(
280                    lambda: tabs.append(cr.browser.tabs.New())):
281                return False
282            try:
283                tabs[0].Close()
284            except:
285                # crbug.com/350941
286                logging.error('Timed out closing tab')
287            return True
288        util.WaitFor(lambda: _BrowserReady(self), timeout=10)
289
290
291    def close(self):
292        """Closes the browser.
293        """
294        try:
295            if utils.is_arc_available():
296                arc_util.pre_processing_before_close(self)
297        finally:
298            # Calling platform.StopAllLocalServers() to tear down the telemetry
299            # server processes such as the one started by
300            # platform.SetHTTPServerDirectories().  Not calling this function
301            # will leak the process and may affect test results.
302            # (crbug.com/663387)
303            self._browser.platform.StopAllLocalServers()
304            self._browser.Close()
305            self._browser.platform.network_controller.Close()
306