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 forwarders
16 from telemetry.core import user_agent
17 from telemetry.core import util
18 from telemetry.core import web_contents
19 from telemetry.core import wpr_modes
20 from telemetry.core import wpr_server
21 from telemetry.core.backends import browser_backend
22 from telemetry.core.backends.chrome import extension_dict_backend
23 from telemetry.core.backends.chrome import misc_web_contents_backend
24 from telemetry.core.backends.chrome import system_info_backend
25 from telemetry.core.backends.chrome import tab_list_backend
26 from telemetry.core.backends.chrome import tracing_backend
27 from telemetry.unittest import options_for_unittests
30 class ChromeBrowserBackend(browser_backend.BrowserBackend):
31 """An abstract class for chrome browser backends. Provides basic functionality
32 once a remote-debugger port has been established."""
33 # It is OK to have abstract methods. pylint: disable=W0223
35 def __init__(self, is_content_shell, supports_extensions, browser_options,
36 output_profile_path, extensions_to_load):
37 super(ChromeBrowserBackend, self).__init__(
38 is_content_shell=is_content_shell,
39 supports_extensions=supports_extensions,
40 browser_options=browser_options,
41 tab_list_backend=tab_list_backend.TabListBackend)
44 self._inspector_protocol_version = 0
45 self._chrome_branch_number = None
46 self._tracing_backend = None
47 self._system_info_backend = None
49 self._output_profile_path = output_profile_path
50 self._extensions_to_load = extensions_to_load
52 if browser_options.netsim:
53 self.wpr_port_pairs = forwarders.PortPairs(
54 http=forwarders.PortPair(80, 80),
55 https=forwarders.PortPair(443, 443),
56 dns=forwarders.PortPair(53, 53))
58 self.wpr_port_pairs = forwarders.PortPairs(
59 http=forwarders.PortPair(0, 0),
60 https=forwarders.PortPair(0, 0),
63 if (self.browser_options.dont_override_profile and
64 not options_for_unittests.AreSet()):
65 sys.stderr.write('Warning: Not overriding profile. This can cause '
66 'unexpected effects due to profile-specific settings, '
67 'such as about:flags settings, cookies, and '
69 self._misc_web_contents_backend = (
70 misc_web_contents_backend.MiscWebContentsBackend(self))
71 self._extension_dict_backend = None
72 if supports_extensions:
73 self._extension_dict_backend = (
74 extension_dict_backend.ExtensionDictBackend(self))
76 def AddReplayServerOptions(self, extra_wpr_args):
77 if self.browser_options.netsim:
78 extra_wpr_args.append('--net=%s' % self.browser_options.netsim)
80 extra_wpr_args.append('--no-dns_forwarding')
83 def misc_web_contents_backend(self):
84 """Access to chrome://oobe/login page which is neither a tab nor an
86 return self._misc_web_contents_backend
89 def extension_dict_backend(self):
90 return self._extension_dict_backend
92 def GetBrowserStartupArgs(self):
94 args.extend(self.browser_options.extra_browser_args)
95 args.append('--disable-background-networking')
96 args.append('--enable-net-benchmarking')
97 args.append('--metrics-recording-only')
98 args.append('--no-default-browser-check')
99 args.append('--no-first-run')
100 args.append('--no-proxy-server')
101 if self.browser_options.netsim:
102 args.append('--ignore-certificate-errors')
103 elif self.browser_options.wpr_mode != wpr_modes.WPR_OFF:
104 args.extend(wpr_server.GetChromeFlags(self.forwarder_factory.host_ip,
105 self.wpr_port_pairs))
106 args.extend(user_agent.GetChromeUserAgentArgumentFromType(
107 self.browser_options.browser_user_agent_type))
109 extensions = [extension.local_path
110 for extension in self._extensions_to_load
111 if not extension.is_component]
112 extension_str = ','.join(extensions)
113 if len(extensions) > 0:
114 args.append('--load-extension=%s' % extension_str)
116 component_extensions = [extension.local_path
117 for extension in self._extensions_to_load
118 if extension.is_component]
119 component_extension_str = ','.join(component_extensions)
120 if len(component_extensions) > 0:
121 args.append('--load-component-extension=%s' % component_extension_str)
123 if self.browser_options.no_proxy_server:
124 args.append('--no-proxy-server')
126 if self.browser_options.disable_component_extensions_with_background_pages:
127 args.append('--disable-component-extensions-with-background-pages')
131 def HasBrowserFinishedLaunching(self):
134 except (exceptions.BrowserGoneException,
135 exceptions.BrowserConnectionGoneException):
140 def _WaitForBrowserToComeUp(self, wait_for_extensions=True):
142 util.WaitFor(self.HasBrowserFinishedLaunching, timeout=30)
143 except (util.TimeoutException, exceptions.ProcessGoneException) as e:
144 raise exceptions.BrowserGoneException(self.GetStackTrace())
146 def AllExtensionsLoaded():
147 # Extension pages are loaded from an about:blank page,
148 # so we need to check that the document URL is the extension
149 # page in addition to the ready state.
150 extension_ready_js = """
151 document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 &&
152 (document.readyState == 'complete' ||
153 document.readyState == 'interactive')
155 for e in self._extensions_to_load:
156 if not e.extension_id in self._extension_dict_backend:
158 extension_object = self._extension_dict_backend[e.extension_id]
160 res = extension_object.EvaluateJavaScript(
161 extension_ready_js % e.extension_id)
162 except exceptions.EvaluateException:
163 # If the inspected page is not ready, we will get an error
164 # when we evaluate a JS expression, but we can just keep polling
165 # until the page is ready (crbug.com/251913).
168 # TODO(tengs): We don't have full support for getting the Chrome
169 # version before launch, so for now we use a generic workaround to
170 # check for an extension binding bug in old versions of Chrome.
171 # See crbug.com/263162 for details.
172 if res and extension_object.EvaluateJavaScript(
173 'chrome.runtime == null'):
174 extension_object.Reload()
178 if wait_for_extensions and self._supports_extensions:
180 util.WaitFor(AllExtensionsLoaded, timeout=60)
181 except util.TimeoutException:
182 logging.error('ExtensionsToLoad: ' +
183 repr([e.extension_id for e in self._extensions_to_load]))
184 logging.error('Extension list: ' +
185 pprint.pformat(self._extension_dict_backend.GetExtensionInfoList(),
190 def _PostBrowserStartupInitialization(self):
191 # Detect version information.
192 data = self.Request('version')
193 resp = json.loads(data)
194 if 'Protocol-Version' in resp:
195 self._inspector_protocol_version = resp['Protocol-Version']
197 if self._chrome_branch_number:
200 if 'Browser' in resp:
201 branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
204 branch_number_match = re.search(
205 'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
208 if branch_number_match:
209 self._chrome_branch_number = int(branch_number_match.group(1))
211 if not self._chrome_branch_number:
212 # Content Shell returns '' for Browser, WebViewShell returns '0'.
213 # For now we have to fall-back and assume branch 1025.
214 self._chrome_branch_number = 1025
217 # Detection has failed: assume 18.0.1025.168 ~= Chrome Android.
218 self._inspector_protocol_version = 1.0
219 self._chrome_branch_number = 1025
221 def Request(self, path, timeout=None, throw_network_exception=False):
222 url = 'http://127.0.0.1:%i/json' % self._port
226 proxy_handler = urllib2.ProxyHandler({}) # Bypass any system proxy.
227 opener = urllib2.build_opener(proxy_handler)
228 req = opener.open(url, timeout=timeout)
230 except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
231 if throw_network_exception:
233 if not self.IsBrowserRunning():
234 raise exceptions.BrowserGoneException(e)
235 raise exceptions.BrowserConnectionGoneException(e)
238 def browser_directory(self):
239 raise NotImplementedError()
242 def profile_directory(self):
243 raise NotImplementedError()
246 def chrome_branch_number(self):
247 assert self._chrome_branch_number
248 return self._chrome_branch_number
251 def supports_tab_control(self):
252 return self.chrome_branch_number >= 1303
255 def supports_tracing(self):
256 return self.is_content_shell or self.chrome_branch_number >= 1385
258 def StartTracing(self, custom_categories=None,
259 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
260 """ custom_categories is an optional string containing a list of
261 comma separated categories that will be traced instead of the
262 default category set. Example: use
263 "webkit,cc,disabled-by-default-cc.debug" to trace only those three
266 if self._tracing_backend is None:
267 self._tracing_backend = tracing_backend.TracingBackend(self._port)
268 return self._tracing_backend.StartTracing(custom_categories, timeout)
270 def StopTracing(self):
271 """ Stops tracing and returns the result as TraceResult object. """
272 for (i, debugger_url) in enumerate(self._browser.tabs):
273 tab = self.tab_list_backend.Get(i, None)
275 success = tab.EvaluateJavaScript(
276 "console.time('" + debugger_url + "');" +
277 "console.timeEnd('" + debugger_url + "');" +
278 "console.time.toString().indexOf('[native code]') != -1;")
280 raise Exception('Page stomped on console.time')
281 self._tracing_backend.AddTabToMarkerMapping(tab, debugger_url)
282 return self._tracing_backend.StopTracing()
284 def GetProcessName(self, cmd_line):
285 """Returns a user-friendly name for the process of the given |cmd_line|."""
287 # TODO(tonyg): Eventually we should make all of these known and add an
290 if 'nacl_helper_bootstrap' in cmd_line:
291 return 'nacl_helper_bootstrap'
292 if ':sandboxed_process' in cmd_line:
294 m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
300 if self._tracing_backend:
301 self._tracing_backend.Close()
302 self._tracing_backend = None
305 def supports_system_info(self):
306 return self.GetSystemInfo() != None
308 def GetSystemInfo(self):
309 if self._system_info_backend is None:
310 self._system_info_backend = system_info_backend.SystemInfoBackend(
312 return self._system_info_backend.GetSystemInfo()
314 def _SetBranchNumber(self, version):
316 self._chrome_branch_number = re.search(r'\d+\.\d+\.(\d+)\.\d+',