Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / backends / chrome / chrome_browser_backend.py
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
5 import contextlib
6 import httplib
7 import json
8 import logging
9 import pprint
10 import re
11 import socket
12 import sys
13 import urllib2
14
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
30
31
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
36
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)
43     self._port = None
44
45     self._supports_tab_control = supports_tab_control
46     self._tracing_backend = None
47     self._system_info_backend = None
48
49     self._output_profile_path = output_profile_path
50     self._extensions_to_load = extensions_to_load
51
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))
57     else:
58       self.wpr_port_pairs = forwarders.PortPairs(
59           http=forwarders.PortPair(0, 0),
60           https=forwarders.PortPair(0, 0),
61           dns=None)
62
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 '
68                        'extensions.\n')
69
70   def AddReplayServerOptions(self, extra_wpr_args):
71     if self.browser_options.netsim:
72       extra_wpr_args.append('--net=%s' % self.browser_options.netsim)
73     else:
74       extra_wpr_args.append('--no-dns_forwarding')
75
76   @property
77   @decorators.Cache
78   def extension_backend(self):
79     if not self.supports_extensions:
80       return None
81     return extension_backend.ExtensionBackendDict(self)
82
83   def GetBrowserStartupArgs(self):
84     args = []
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')
90
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')
97
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')
102
103     if self.browser_options.disable_background_networking:
104       args.append('--disable-background-networking')
105
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))
113
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)
120
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)
127
128     if self.browser_options.no_proxy_server:
129       args.append('--no-proxy-server')
130
131     if self.browser_options.disable_component_extensions_with_background_pages:
132       args.append('--disable-component-extensions-with-background-pages')
133
134     return args
135
136   def HasBrowserFinishedLaunching(self):
137     try:
138       self.Request('', timeout=.1)
139     except (exceptions.BrowserGoneException,
140             exceptions.BrowserConnectionGoneException):
141       return False
142     else:
143       return True
144
145   def _WaitForBrowserToComeUp(self, wait_for_extensions=True):
146     try:
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)
152
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')
161       """
162       for e in self._extensions_to_load:
163         try:
164           extension_objects = self.extension_backend[e.extension_id]
165         except KeyError:
166           return False
167         for extension_object in extension_objects:
168           try:
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).
175             res = None
176
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()
184           if not res:
185             return False
186       return True
187
188     if wait_for_extensions and self._supports_extensions:
189       try:
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))
196         raise
197
198   def ListInspectableContexts(self):
199     return json.loads(self.Request(''))
200
201   def Request(self, path, timeout=30, throw_network_exception=False):
202     url = 'http://127.0.0.1:%i/json' % self._port
203     if path:
204       url += '/' + path
205     try:
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:
209         return req.read()
210     except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
211       if throw_network_exception:
212         raise e
213       if not self.IsBrowserRunning():
214         raise exceptions.BrowserGoneException(self.browser, e)
215       raise exceptions.BrowserConnectionGoneException(self.browser, e)
216
217   @property
218   def browser_directory(self):
219     raise NotImplementedError()
220
221   @property
222   def profile_directory(self):
223     raise NotImplementedError()
224
225   @property
226   @decorators.Cache
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+',
234                                         resp['Browser'])
235       else:
236         branch_number_match = re.search(
237             'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
238             resp['User-Agent'])
239
240       if branch_number_match:
241         branch_number = int(branch_number_match.group(1))
242         if branch_number:
243           return branch_number
244
245     # Branch number can't be determined, so fail any branch number checks.
246     return 0
247
248   @property
249   def supports_tab_control(self):
250     return self._supports_tab_control
251
252   @property
253   def supports_tracing(self):
254     return True
255
256   def StartTracing(self, trace_options, custom_categories=None,
257                    timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
258     """
259     Args:
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.
266     """
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)
272
273   @property
274   def is_tracing_running(self):
275     if not self._tracing_backend:
276       return None
277     return self._tracing_backend.is_tracing_running
278
279   def StopTracing(self):
280     """ Stops tracing and returns the result as TimelineData object. """
281     tab_ids_list = []
282     for (i, _) in enumerate(self._browser.tabs):
283       tab = self.tab_list_backend.Get(i, None)
284       if tab:
285         success = tab.EvaluateJavaScript(
286             "console.time('" + tab.id + "');" +
287             "console.timeEnd('" + tab.id + "');" +
288             "console.time.toString().indexOf('[native code]') != -1;")
289         if not success:
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)
296
297   def GetProcessName(self, cmd_line):
298     """Returns a user-friendly name for the process of the given |cmd_line|."""
299     if not cmd_line:
300       # TODO(tonyg): Eventually we should make all of these known and add an
301       # assertion.
302       return 'unknown'
303     if 'nacl_helper_bootstrap' in cmd_line:
304       return 'nacl_helper_bootstrap'
305     if ':sandboxed_process' in cmd_line:
306       return 'renderer'
307     m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
308     if not m:
309       return 'browser'
310     return m.group(1)
311
312   def Close(self):
313     if self._tracing_backend:
314       self._tracing_backend.Close()
315       self._tracing_backend = None
316
317   @property
318   def supports_system_info(self):
319     return self.GetSystemInfo() != None
320
321   def GetSystemInfo(self):
322     if self._system_info_backend is None:
323       self._system_info_backend = system_info_backend.SystemInfoBackend(
324           self._port)
325     return self._system_info_backend.GetSystemInfo()