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.
15 from telemetry import decorators
16 from telemetry.core import exceptions
17 from telemetry.core import forwarders
18 from telemetry.core import user_agent
19 from telemetry.core import util
20 from telemetry.core import web_contents
21 from telemetry.core import wpr_modes
22 from telemetry.core import wpr_server
23 from telemetry.core.backends import browser_backend
24 from telemetry.core.backends.chrome import extension_backend
25 from telemetry.core.backends.chrome import system_info_backend
26 from telemetry.core.backends.chrome import tab_list_backend
27 from telemetry.core.backends.chrome import tracing_backend
28 from telemetry.timeline import tracing_timeline_data
29 from telemetry.unittest import options_for_unittests
32 class ChromeBrowserBackend(browser_backend.BrowserBackend):
33 """An abstract class for chrome browser backends. Provides basic functionality
34 once a remote-debugger port has been established."""
35 # It is OK to have abstract methods. pylint: disable=W0223
37 def __init__(self, supports_tab_control, supports_extensions, browser_options,
38 output_profile_path, extensions_to_load):
39 super(ChromeBrowserBackend, self).__init__(
40 supports_extensions=supports_extensions,
41 browser_options=browser_options,
42 tab_list_backend=tab_list_backend.TabListBackend)
45 self._supports_tab_control = supports_tab_control
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 '
70 def AddReplayServerOptions(self, extra_wpr_args):
71 if self.browser_options.netsim:
72 extra_wpr_args.append('--net=%s' % self.browser_options.netsim)
74 extra_wpr_args.append('--no-dns_forwarding')
78 def extension_backend(self):
79 if not self.supports_extensions:
81 return extension_backend.ExtensionBackendDict(self)
83 def GetBrowserStartupArgs(self):
85 args.extend(self.browser_options.extra_browser_args)
86 args.append('--enable-net-benchmarking')
87 args.append('--metrics-recording-only')
88 args.append('--no-default-browser-check')
89 args.append('--no-first-run')
91 # Turn on GPU benchmarking extension for all runs. The only side effect of
92 # the extension being on is that render stats are tracked. This is believed
93 # to be effectively free. And, by doing so here, it avoids us having to
94 # programmatically inspect a pageset's actions in order to determine if it
95 # might eventually scroll.
96 args.append('--enable-gpu-benchmarking')
98 # Set --no-proxy-server to work around some XP issues unless
99 # some other flag indicates a proxy is needed.
100 if not '--enable-spdy-proxy-auth' in args:
101 args.append('--no-proxy-server')
103 if self.browser_options.disable_background_networking:
104 args.append('--disable-background-networking')
106 if self.browser_options.netsim:
107 args.append('--ignore-certificate-errors')
108 elif self.browser_options.wpr_mode != wpr_modes.WPR_OFF:
109 args.extend(wpr_server.GetChromeFlags(self.forwarder_factory.host_ip,
110 self.wpr_port_pairs))
111 args.extend(user_agent.GetChromeUserAgentArgumentFromType(
112 self.browser_options.browser_user_agent_type))
114 extensions = [extension.local_path
115 for extension in self._extensions_to_load
116 if not extension.is_component]
117 extension_str = ','.join(extensions)
118 if len(extensions) > 0:
119 args.append('--load-extension=%s' % extension_str)
121 component_extensions = [extension.local_path
122 for extension in self._extensions_to_load
123 if extension.is_component]
124 component_extension_str = ','.join(component_extensions)
125 if len(component_extensions) > 0:
126 args.append('--load-component-extension=%s' % component_extension_str)
128 if self.browser_options.no_proxy_server:
129 args.append('--no-proxy-server')
131 if self.browser_options.disable_component_extensions_with_background_pages:
132 args.append('--disable-component-extensions-with-background-pages')
136 def HasBrowserFinishedLaunching(self):
138 self.Request('', timeout=.1)
139 except (exceptions.BrowserGoneException,
140 exceptions.BrowserConnectionGoneException):
145 def _WaitForBrowserToComeUp(self, wait_for_extensions=True):
147 util.WaitFor(self.HasBrowserFinishedLaunching, timeout=30)
148 except (util.TimeoutException, exceptions.ProcessGoneException) as e:
149 if not self.IsBrowserRunning():
150 raise exceptions.BrowserGoneException(self.browser, e)
151 raise exceptions.BrowserConnectionGoneException(self.browser, e)
153 def AllExtensionsLoaded():
154 # Extension pages are loaded from an about:blank page,
155 # so we need to check that the document URL is the extension
156 # page in addition to the ready state.
157 extension_ready_js = """
158 document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 &&
159 (document.readyState == 'complete' ||
160 document.readyState == 'interactive')
162 for e in self._extensions_to_load:
164 extension_objects = self.extension_backend[e.extension_id]
167 for extension_object in extension_objects:
169 res = extension_object.EvaluateJavaScript(
170 extension_ready_js % e.extension_id)
171 except exceptions.EvaluateException:
172 # If the inspected page is not ready, we will get an error
173 # when we evaluate a JS expression, but we can just keep polling
174 # until the page is ready (crbug.com/251913).
177 # TODO(tengs): We don't have full support for getting the Chrome
178 # version before launch, so for now we use a generic workaround to
179 # check for an extension binding bug in old versions of Chrome.
180 # See crbug.com/263162 for details.
181 if res and extension_object.EvaluateJavaScript(
182 'chrome.runtime == null'):
183 extension_object.Reload()
188 if wait_for_extensions and self._supports_extensions:
190 util.WaitFor(AllExtensionsLoaded, timeout=60)
191 except util.TimeoutException:
192 logging.error('ExtensionsToLoad: ' +
193 repr([e.extension_id for e in self._extensions_to_load]))
194 logging.error('Extension list: ' +
195 pprint.pformat(self.extension_backend, indent=4))
198 def ListInspectableContexts(self):
199 return json.loads(self.Request(''))
201 def Request(self, path, timeout=30, throw_network_exception=False):
202 url = 'http://127.0.0.1:%i/json' % self._port
206 proxy_handler = urllib2.ProxyHandler({}) # Bypass any system proxy.
207 opener = urllib2.build_opener(proxy_handler)
208 with contextlib.closing(opener.open(url, timeout=timeout)) as req:
210 except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
211 if throw_network_exception:
213 if not self.IsBrowserRunning():
214 raise exceptions.BrowserGoneException(self.browser, e)
215 raise exceptions.BrowserConnectionGoneException(self.browser, e)
218 def browser_directory(self):
219 raise NotImplementedError()
222 def profile_directory(self):
223 raise NotImplementedError()
227 def chrome_branch_number(self):
228 # Detect version information.
229 data = self.Request('version')
230 resp = json.loads(data)
231 if 'Protocol-Version' in resp:
232 if 'Browser' in resp:
233 branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
236 branch_number_match = re.search(
237 'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
240 if branch_number_match:
241 branch_number = int(branch_number_match.group(1))
245 # Branch number can't be determined, so fail any branch number checks.
249 def supports_tab_control(self):
250 return self._supports_tab_control
253 def supports_tracing(self):
256 def StartTracing(self, trace_options, custom_categories=None,
257 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
260 trace_options: An tracing_options.TracingOptions instance.
261 custom_categories: An optional string containing a list of
262 comma separated categories that will be traced
263 instead of the default category set. Example: use
264 "webkit,cc,disabled-by-default-cc.debug" to trace only
265 those three event categories.
267 assert trace_options and trace_options.enable_chrome_trace
268 if self._tracing_backend is None:
269 self._tracing_backend = tracing_backend.TracingBackend(self._port, self)
270 return self._tracing_backend.StartTracing(
271 trace_options, custom_categories, timeout)
274 def is_tracing_running(self):
275 if not self._tracing_backend:
277 return self._tracing_backend.is_tracing_running
279 def StopTracing(self):
280 """ Stops tracing and returns the result as TimelineData object. """
282 for (i, _) in enumerate(self._browser.tabs):
283 tab = self.tab_list_backend.Get(i, None)
285 success = tab.EvaluateJavaScript(
286 "console.time('" + tab.id + "');" +
287 "console.timeEnd('" + tab.id + "');" +
288 "console.time.toString().indexOf('[native code]') != -1;")
290 raise Exception('Page stomped on console.time')
291 tab_ids_list.append(tab.id)
292 trace_events = self._tracing_backend.StopTracing()
293 # Augment tab_ids data to trace events.
294 event_data = {'traceEvents' : trace_events, 'tabIds': tab_ids_list}
295 return tracing_timeline_data.TracingTimelineData(event_data)
297 def GetProcessName(self, cmd_line):
298 """Returns a user-friendly name for the process of the given |cmd_line|."""
300 # TODO(tonyg): Eventually we should make all of these known and add an
303 if 'nacl_helper_bootstrap' in cmd_line:
304 return 'nacl_helper_bootstrap'
305 if ':sandboxed_process' in cmd_line:
307 m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
313 if self._tracing_backend:
314 self._tracing_backend.Close()
315 self._tracing_backend = None
318 def supports_system_info(self):
319 return self.GetSystemInfo() != None
321 def GetSystemInfo(self):
322 if self._system_info_backend is None:
323 self._system_info_backend = system_info_backend.SystemInfoBackend(
325 return self._system_info_backend.GetSystemInfo()