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('--disable-background-networking')
87 args.append('--enable-net-benchmarking')
88 args.append('--metrics-recording-only')
89 args.append('--no-default-browser-check')
90 args.append('--no-first-run')
92 # Turn on GPU benchmarking extension for all runs. The only side effect of
93 # the extension being on is that render stats are tracked. This is believed
94 # to be effectively free. And, by doing so here, it avoids us having to
95 # programmatically inspect a pageset's actions in order to determine if it
96 # might eventually scroll.
97 args.append('--enable-gpu-benchmarking')
99 # Set --no-proxy-server to work around some XP issues unless
100 # some other flag indicates a proxy is needed.
101 if not '--enable-spdy-proxy-auth' in args:
102 args.append('--no-proxy-server')
104 if self.browser_options.netsim:
105 args.append('--ignore-certificate-errors')
106 elif self.browser_options.wpr_mode != wpr_modes.WPR_OFF:
107 args.extend(wpr_server.GetChromeFlags(self.forwarder_factory.host_ip,
108 self.wpr_port_pairs))
109 args.extend(user_agent.GetChromeUserAgentArgumentFromType(
110 self.browser_options.browser_user_agent_type))
112 extensions = [extension.local_path
113 for extension in self._extensions_to_load
114 if not extension.is_component]
115 extension_str = ','.join(extensions)
116 if len(extensions) > 0:
117 args.append('--load-extension=%s' % extension_str)
119 component_extensions = [extension.local_path
120 for extension in self._extensions_to_load
121 if extension.is_component]
122 component_extension_str = ','.join(component_extensions)
123 if len(component_extensions) > 0:
124 args.append('--load-component-extension=%s' % component_extension_str)
126 if self.browser_options.no_proxy_server:
127 args.append('--no-proxy-server')
129 if self.browser_options.disable_component_extensions_with_background_pages:
130 args.append('--disable-component-extensions-with-background-pages')
134 def HasBrowserFinishedLaunching(self):
137 except (exceptions.BrowserGoneException,
138 exceptions.BrowserConnectionGoneException):
143 def _WaitForBrowserToComeUp(self, wait_for_extensions=True):
145 util.WaitFor(self.HasBrowserFinishedLaunching, timeout=30)
146 except (util.TimeoutException, exceptions.ProcessGoneException) as e:
147 if not self.IsBrowserRunning():
148 raise exceptions.BrowserGoneException(self.browser, e)
149 raise exceptions.BrowserConnectionGoneException(self.browser, e)
151 def AllExtensionsLoaded():
152 # Extension pages are loaded from an about:blank page,
153 # so we need to check that the document URL is the extension
154 # page in addition to the ready state.
155 extension_ready_js = """
156 document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 &&
157 (document.readyState == 'complete' ||
158 document.readyState == 'interactive')
160 for e in self._extensions_to_load:
161 if not e.extension_id in self.extension_backend:
163 for extension_object in self.extension_backend[e.extension_id]:
165 res = extension_object.EvaluateJavaScript(
166 extension_ready_js % e.extension_id)
167 except exceptions.EvaluateException:
168 # If the inspected page is not ready, we will get an error
169 # when we evaluate a JS expression, but we can just keep polling
170 # until the page is ready (crbug.com/251913).
173 # TODO(tengs): We don't have full support for getting the Chrome
174 # version before launch, so for now we use a generic workaround to
175 # check for an extension binding bug in old versions of Chrome.
176 # See crbug.com/263162 for details.
177 if res and extension_object.EvaluateJavaScript(
178 'chrome.runtime == null'):
179 extension_object.Reload()
184 if wait_for_extensions and self._supports_extensions:
186 util.WaitFor(AllExtensionsLoaded, timeout=60)
187 except util.TimeoutException:
188 logging.error('ExtensionsToLoad: ' +
189 repr([e.extension_id for e in self._extensions_to_load]))
190 logging.error('Extension list: ' +
191 pprint.pformat(self.extension_backend, indent=4))
194 def ListInspectableContexts(self):
195 return json.loads(self.Request(''))
197 def Request(self, path, timeout=5, throw_network_exception=False):
198 url = 'http://127.0.0.1:%i/json' % self._port
202 proxy_handler = urllib2.ProxyHandler({}) # Bypass any system proxy.
203 opener = urllib2.build_opener(proxy_handler)
204 with contextlib.closing(opener.open(url, timeout=timeout)) as req:
206 except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
207 if throw_network_exception:
209 if not self.IsBrowserRunning():
210 raise exceptions.BrowserGoneException(self.browser, e)
211 raise exceptions.BrowserConnectionGoneException(self.browser, e)
214 def browser_directory(self):
215 raise NotImplementedError()
218 def profile_directory(self):
219 raise NotImplementedError()
223 def chrome_branch_number(self):
224 # Detect version information.
225 data = self.Request('version')
226 resp = json.loads(data)
227 if 'Protocol-Version' in resp:
228 if 'Browser' in resp:
229 branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
232 branch_number_match = re.search(
233 'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
236 if branch_number_match:
237 branch_number = int(branch_number_match.group(1))
241 # Branch number can't be determined, so fail any branch number checks.
245 def supports_tab_control(self):
246 return self._supports_tab_control
249 def supports_tracing(self):
252 def StartTracing(self, trace_options, custom_categories=None,
253 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
256 trace_options: An tracing_options.TracingOptions instance.
257 custom_categories: An optional string containing a list of
258 comma separated categories that will be traced
259 instead of the default category set. Example: use
260 "webkit,cc,disabled-by-default-cc.debug" to trace only
261 those three event categories.
263 assert trace_options and trace_options.enable_chrome_trace
264 if self._tracing_backend is None:
265 self._tracing_backend = tracing_backend.TracingBackend(self._port, self)
266 return self._tracing_backend.StartTracing(
267 trace_options, custom_categories, timeout)
270 def is_tracing_running(self):
271 if not self._tracing_backend:
273 return self._tracing_backend.is_tracing_running
275 def StopTracing(self):
276 """ Stops tracing and returns the result as TimelineData object. """
278 for (i, _) in enumerate(self._browser.tabs):
279 tab = self.tab_list_backend.Get(i, None)
281 success = tab.EvaluateJavaScript(
282 "console.time('" + tab.id + "');" +
283 "console.timeEnd('" + tab.id + "');" +
284 "console.time.toString().indexOf('[native code]') != -1;")
286 raise Exception('Page stomped on console.time')
287 tab_ids_list.append(tab.id)
288 trace_events = self._tracing_backend.StopTracing()
289 # Augment tab_ids data to trace events.
290 event_data = {'traceEvents' : trace_events, 'tabIds': tab_ids_list}
291 return tracing_timeline_data.TracingTimelineData(event_data)
293 def GetProcessName(self, cmd_line):
294 """Returns a user-friendly name for the process of the given |cmd_line|."""
296 # TODO(tonyg): Eventually we should make all of these known and add an
299 if 'nacl_helper_bootstrap' in cmd_line:
300 return 'nacl_helper_bootstrap'
301 if ':sandboxed_process' in cmd_line:
303 m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
309 if self._tracing_backend:
310 self._tracing_backend.Close()
311 self._tracing_backend = None
314 def supports_system_info(self):
315 return self.GetSystemInfo() != None
317 def GetSystemInfo(self):
318 if self._system_info_backend is None:
319 self._system_info_backend = system_info_backend.SystemInfoBackend(
321 return self._system_info_backend.GetSystemInfo()