chrome_browser_backend.py revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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 httplib
6import json
7import re
8import socket
9import sys
10import urllib2
11
12from telemetry.core import exceptions
13from telemetry.core import user_agent
14from telemetry.core import util
15from telemetry.core import web_contents
16from telemetry.core import wpr_modes
17from telemetry.core import wpr_server
18from telemetry.core.backends import browser_backend
19from telemetry.core.chrome import extension_dict_backend
20from telemetry.core.chrome import misc_web_contents_backend
21from telemetry.core.chrome import tab_list_backend
22from telemetry.core.chrome import tracing_backend
23from telemetry.unittest import options_for_unittests
24
25class ChromeBrowserBackend(browser_backend.BrowserBackend):
26  """An abstract class for chrome browser backends. Provides basic functionality
27  once a remote-debugger port has been established."""
28  # It is OK to have abstract methods. pylint: disable=W0223
29
30  def __init__(self, is_content_shell, supports_extensions, options):
31    super(ChromeBrowserBackend, self).__init__(
32        is_content_shell=is_content_shell,
33        supports_extensions=supports_extensions,
34        options=options,
35        tab_list_backend=tab_list_backend.TabListBackend)
36    self._port = None
37
38    self._inspector_protocol_version = 0
39    self._chrome_branch_number = 0
40    self._tracing_backend = None
41
42    self.webpagereplay_local_http_port = util.GetAvailableLocalPort()
43    self.webpagereplay_local_https_port = util.GetAvailableLocalPort()
44    self.webpagereplay_remote_http_port = self.webpagereplay_local_http_port
45    self.webpagereplay_remote_https_port = self.webpagereplay_local_https_port
46
47    if options.dont_override_profile and not options_for_unittests.AreSet():
48      sys.stderr.write('Warning: Not overriding profile. This can cause '
49                       'unexpected effects due to profile-specific settings, '
50                       'such as about:flags settings, cookies, and '
51                       'extensions.\n')
52    self._misc_web_contents_backend = (
53        misc_web_contents_backend.MiscWebContentsBackend(self))
54    self._extension_dict_backend = None
55    if supports_extensions:
56      self._extension_dict_backend = (
57          extension_dict_backend.ExtensionDictBackend(self))
58
59  def AddReplayServerOptions(self, options):
60    options.append('--no-dns_forwarding')
61
62  @property
63  def misc_web_contents_backend(self):
64    """Access to chrome://oobe/login page which is neither a tab nor an
65    extension."""
66    return self._misc_web_contents_backend
67
68  @property
69  def extension_dict_backend(self):
70    return self._extension_dict_backend
71
72  def GetBrowserStartupArgs(self):
73    args = []
74    args.extend(self.options.extra_browser_args)
75    args.append('--disable-background-networking')
76    args.append('--metrics-recording-only')
77    args.append('--no-first-run')
78    if self.options.wpr_mode != wpr_modes.WPR_OFF:
79      args.extend(wpr_server.GetChromeFlags(
80          self.WEBPAGEREPLAY_HOST,
81          self.webpagereplay_remote_http_port,
82          self.webpagereplay_remote_https_port))
83    args.extend(user_agent.GetChromeUserAgentArgumentFromType(
84        self.options.browser_user_agent_type))
85
86    extensions = [extension.local_path for extension in
87                  self.options.extensions_to_load if not extension.is_component]
88    extension_str = ','.join(extensions)
89    if len(extensions) > 0:
90      args.append('--load-extension=%s' % extension_str)
91
92    component_extensions = [extension.local_path for extension in
93                  self.options.extensions_to_load if extension.is_component]
94    component_extension_str = ','.join(component_extensions)
95    if len(component_extensions) > 0:
96      args.append('--load-component-extension=%s' % component_extension_str)
97
98    if self.options.no_proxy_server:
99      args.append('--no-proxy-server')
100
101    return args
102
103  def _WaitForBrowserToComeUp(self, timeout=None):
104    def IsBrowserUp():
105      try:
106        self.Request('', timeout=timeout)
107      except (exceptions.BrowserGoneException,
108              exceptions.BrowserConnectionGoneException):
109        return False
110      else:
111        return True
112    try:
113      util.WaitFor(IsBrowserUp, timeout=30)
114    except util.TimeoutException:
115      raise exceptions.BrowserGoneException(self.GetStackTrace())
116
117    def AllExtensionsLoaded():
118      # Extension pages are loaded from an about:blank page,
119      # so we need to check that the document URL is the extension
120      # page in addition to the ready state.
121      extension_ready_js = """
122          document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 &&
123          (document.readyState == 'complete' ||
124           document.readyState == 'interactive')
125      """
126      for e in self.options.extensions_to_load:
127        if not e.extension_id in self._extension_dict_backend:
128          return False
129        extension_object = self._extension_dict_backend[e.extension_id]
130        res = extension_object.EvaluateJavaScript(
131            extension_ready_js % e.extension_id)
132        if not res:
133          return False
134      return True
135    if self._supports_extensions:
136      util.WaitFor(AllExtensionsLoaded, timeout=30)
137
138  def _PostBrowserStartupInitialization(self):
139    # Detect version information.
140    data = self.Request('version')
141    resp = json.loads(data)
142    if 'Protocol-Version' in resp:
143      self._inspector_protocol_version = resp['Protocol-Version']
144
145      if 'Browser' in resp:
146        branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
147                                        resp['Browser'])
148      else:
149        branch_number_match = re.search(
150            'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
151            resp['User-Agent'])
152
153      if branch_number_match:
154        self._chrome_branch_number = int(branch_number_match.group(1))
155      else:
156        # Content Shell returns '' for Browser, for now we have to
157        # fall-back and assume branch 1025.
158        self._chrome_branch_number = 1025
159      return
160
161    # Detection has failed: assume 18.0.1025.168 ~= Chrome Android.
162    self._inspector_protocol_version = 1.0
163    self._chrome_branch_number = 1025
164
165  def Request(self, path, timeout=None, throw_network_exception=False):
166    url = 'http://localhost:%i/json' % self._port
167    if path:
168      url += '/' + path
169    try:
170      req = urllib2.urlopen(url, timeout=timeout)
171      return req.read()
172    except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
173      if throw_network_exception:
174        raise e
175      if not self.IsBrowserRunning():
176        raise exceptions.BrowserGoneException()
177      raise exceptions.BrowserConnectionGoneException()
178
179  @property
180  def chrome_branch_number(self):
181    return self._chrome_branch_number
182
183  @property
184  def supports_tab_control(self):
185    return self._chrome_branch_number >= 1303
186
187  @property
188  def supports_tracing(self):
189    return self.is_content_shell or self._chrome_branch_number >= 1385
190
191  def StartTracing(self, custom_categories=None,
192                   timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
193    """ custom_categories is an optional string containing a list of
194    comma separated categories that will be traced instead of the
195    default category set.  Example: use
196    "webkit,cc,disabled-by-default-cc.debug" to trace only those three
197    event categories.
198    """
199    if self._tracing_backend is None:
200      self._tracing_backend = tracing_backend.TracingBackend(self._port)
201    self._tracing_backend.BeginTracing(custom_categories, timeout)
202
203  def StopTracing(self):
204    self._tracing_backend.EndTracing()
205
206  def GetTraceResultAndReset(self):
207    return self._tracing_backend.GetTraceResultAndReset()
208
209  def GetProcessName(self, cmd_line):
210    """Returns a user-friendly name for the process of the given |cmd_line|."""
211    if 'nacl_helper_bootstrap' in cmd_line:
212      return 'nacl_helper_bootstrap'
213    if ':sandboxed_process' in cmd_line:
214      return 'renderer'
215    m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
216    if not m:
217      return 'browser'
218    return m.group(1)
219
220  def Close(self):
221    if self._tracing_backend:
222      self._tracing_backend.Close()
223      self._tracing_backend = None
224