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.
14 from telemetry.core import exceptions
15 from telemetry.core import user_agent
16 from telemetry.core import util
17 from telemetry.core import web_contents
18 from telemetry.core import wpr_modes
19 from telemetry.core import wpr_server
20 from telemetry.core.backends import browser_backend
21 from telemetry.core.backends.chrome import extension_dict_backend
22 from telemetry.core.backends.chrome import misc_web_contents_backend
23 from telemetry.core.backends.chrome import system_info_backend
24 from telemetry.core.backends.chrome import tab_list_backend
25 from telemetry.core.backends.chrome import tracing_backend
26 from telemetry.unittest import options_for_unittests
28 class ChromeBrowserBackend(browser_backend.BrowserBackend):
29 """An abstract class for chrome browser backends. Provides basic functionality
30 once a remote-debugger port has been established."""
31 # It is OK to have abstract methods. pylint: disable=W0223
33 def __init__(self, is_content_shell, supports_extensions, browser_options,
34 output_profile_path, extensions_to_load):
35 super(ChromeBrowserBackend, self).__init__(
36 is_content_shell=is_content_shell,
37 supports_extensions=supports_extensions,
38 browser_options=browser_options,
39 tab_list_backend=tab_list_backend.TabListBackend)
42 self._inspector_protocol_version = 0
43 self._chrome_branch_number = None
44 self._tracing_backend = None
45 self._system_info_backend = None
47 self._output_profile_path = output_profile_path
48 self._extensions_to_load = extensions_to_load
50 self.webpagereplay_local_http_port = util.GetAvailableLocalPort()
51 self.webpagereplay_local_https_port = util.GetAvailableLocalPort()
52 self.webpagereplay_remote_http_port = self.webpagereplay_local_http_port
53 self.webpagereplay_remote_https_port = self.webpagereplay_local_https_port
55 if (self.browser_options.dont_override_profile and
56 not options_for_unittests.AreSet()):
57 sys.stderr.write('Warning: Not overriding profile. This can cause '
58 'unexpected effects due to profile-specific settings, '
59 'such as about:flags settings, cookies, and '
61 self._misc_web_contents_backend = (
62 misc_web_contents_backend.MiscWebContentsBackend(self))
63 self._extension_dict_backend = None
64 if supports_extensions:
65 self._extension_dict_backend = (
66 extension_dict_backend.ExtensionDictBackend(self))
68 def AddReplayServerOptions(self, extra_wpr_args):
69 extra_wpr_args.append('--no-dns_forwarding')
72 def misc_web_contents_backend(self):
73 """Access to chrome://oobe/login page which is neither a tab nor an
75 return self._misc_web_contents_backend
78 def extension_dict_backend(self):
79 return self._extension_dict_backend
81 def GetBrowserStartupArgs(self):
83 args.extend(self.browser_options.extra_browser_args)
84 args.append('--disable-background-networking')
85 args.append('--metrics-recording-only')
86 args.append('--no-first-run')
87 args.append('--no-default-browser-check')
88 args.append('--no-proxy-server')
89 if self.browser_options.wpr_mode != wpr_modes.WPR_OFF:
90 args.extend(wpr_server.GetChromeFlags(
91 self.WEBPAGEREPLAY_HOST,
92 self.webpagereplay_remote_http_port,
93 self.webpagereplay_remote_https_port))
94 args.extend(user_agent.GetChromeUserAgentArgumentFromType(
95 self.browser_options.browser_user_agent_type))
97 extensions = [extension.local_path
98 for extension in self._extensions_to_load
99 if not extension.is_component]
100 extension_str = ','.join(extensions)
101 if len(extensions) > 0:
102 args.append('--load-extension=%s' % extension_str)
104 component_extensions = [extension.local_path
105 for extension in self._extensions_to_load
106 if extension.is_component]
107 component_extension_str = ','.join(component_extensions)
108 if len(component_extensions) > 0:
109 args.append('--load-component-extension=%s' % component_extension_str)
111 if self.browser_options.no_proxy_server:
112 args.append('--no-proxy-server')
114 if self.browser_options.disable_component_extensions_with_background_pages:
115 args.append('--disable-component-extensions-with-background-pages')
119 def _WaitForBrowserToComeUp(self, wait_for_extensions=True, timeout=None):
122 self.Request('', timeout=timeout)
123 except (exceptions.BrowserGoneException,
124 exceptions.BrowserConnectionGoneException):
129 util.WaitFor(IsBrowserUp, timeout=30)
130 except util.TimeoutException:
131 raise exceptions.BrowserGoneException(self.GetStackTrace())
133 def AllExtensionsLoaded():
134 # Extension pages are loaded from an about:blank page,
135 # so we need to check that the document URL is the extension
136 # page in addition to the ready state.
137 extension_ready_js = """
138 document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 &&
139 (document.readyState == 'complete' ||
140 document.readyState == 'interactive')
142 for e in self._extensions_to_load:
143 if not e.extension_id in self._extension_dict_backend:
145 extension_object = self._extension_dict_backend[e.extension_id]
147 res = extension_object.EvaluateJavaScript(
148 extension_ready_js % e.extension_id)
149 except exceptions.EvaluateException:
150 # If the inspected page is not ready, we will get an error
151 # when we evaluate a JS expression, but we can just keep polling
152 # until the page is ready (crbug.com/251913).
155 # TODO(tengs): We don't have full support for getting the Chrome
156 # version before launch, so for now we use a generic workaround to
157 # check for an extension binding bug in old versions of Chrome.
158 # See crbug.com/263162 for details.
159 if res and extension_object.EvaluateJavaScript(
160 'chrome.runtime == null'):
161 extension_object.Reload()
165 if wait_for_extensions and self._supports_extensions:
167 util.WaitFor(AllExtensionsLoaded, timeout=60)
168 except util.TimeoutException:
169 logging.error('ExtensionsToLoad: ' +
170 repr([e.extension_id for e in self._extensions_to_load]))
171 logging.error('Extension list: ' +
172 pprint.pformat(self._extension_dict_backend.GetExtensionInfoList(),
177 def _PostBrowserStartupInitialization(self):
178 # Detect version information.
179 data = self.Request('version')
180 resp = json.loads(data)
181 if 'Protocol-Version' in resp:
182 self._inspector_protocol_version = resp['Protocol-Version']
184 if self._chrome_branch_number:
187 if 'Browser' in resp:
188 branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
191 branch_number_match = re.search(
192 'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
195 if branch_number_match:
196 self._chrome_branch_number = int(branch_number_match.group(1))
198 if not self._chrome_branch_number:
199 # Content Shell returns '' for Browser, WebViewShell returns '0'.
200 # For now we have to fall-back and assume branch 1025.
201 self._chrome_branch_number = 1025
204 # Detection has failed: assume 18.0.1025.168 ~= Chrome Android.
205 self._inspector_protocol_version = 1.0
206 self._chrome_branch_number = 1025
208 def Request(self, path, timeout=None, throw_network_exception=False):
209 url = 'http://127.0.0.1:%i/json' % self._port
213 proxy_handler = urllib2.ProxyHandler({}) # Bypass any system proxy.
214 opener = urllib2.build_opener(proxy_handler)
215 req = opener.open(url, timeout=timeout)
217 except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
218 if throw_network_exception:
220 if not self.IsBrowserRunning():
221 raise exceptions.BrowserGoneException(e)
222 raise exceptions.BrowserConnectionGoneException(e)
225 def browser_directory(self):
226 raise NotImplementedError()
229 def profile_directory(self):
230 raise NotImplementedError()
233 def chrome_branch_number(self):
234 assert self._chrome_branch_number
235 return self._chrome_branch_number
238 def supports_tab_control(self):
239 return self.chrome_branch_number >= 1303
242 def supports_tracing(self):
243 return self.is_content_shell or self.chrome_branch_number >= 1385
245 def StartTracing(self, custom_categories=None,
246 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
247 """ custom_categories is an optional string containing a list of
248 comma separated categories that will be traced instead of the
249 default category set. Example: use
250 "webkit,cc,disabled-by-default-cc.debug" to trace only those three
253 if self._tracing_backend is None:
254 self._tracing_backend = tracing_backend.TracingBackend(self._port)
255 return self._tracing_backend.StartTracing(custom_categories, timeout)
257 def StopTracing(self):
258 """ Stops tracing and returns the result as TraceResult object. """
259 return self._tracing_backend.StopTracing()
261 def GetProcessName(self, cmd_line):
262 """Returns a user-friendly name for the process of the given |cmd_line|."""
263 if 'nacl_helper_bootstrap' in cmd_line:
264 return 'nacl_helper_bootstrap'
265 if ':sandboxed_process' in cmd_line:
267 m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
273 if self._tracing_backend:
274 self._tracing_backend.Close()
275 self._tracing_backend = None
278 def supports_system_info(self):
279 return self.GetSystemInfo() != None
281 def GetSystemInfo(self):
282 if self._system_info_backend is None:
283 self._system_info_backend = system_info_backend.SystemInfoBackend(
285 return self._system_info_backend.GetSystemInfo()
287 def _SetBranchNumber(self, version):
289 self._chrome_branch_number = re.search(r'\d+\.\d+\.(\d+)\.\d+',