- add sources.
[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 httplib
6 import json
7 import logging
8 import pprint
9 import re
10 import socket
11 import sys
12 import urllib2
13
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
27
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
32
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)
40     self._port = None
41
42     self._inspector_protocol_version = 0
43     self._chrome_branch_number = None
44     self._tracing_backend = None
45     self._system_info_backend = None
46
47     self._output_profile_path = output_profile_path
48     self._extensions_to_load = extensions_to_load
49
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
54
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 '
60                        'extensions.\n')
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))
67
68   def AddReplayServerOptions(self, extra_wpr_args):
69     extra_wpr_args.append('--no-dns_forwarding')
70
71   @property
72   def misc_web_contents_backend(self):
73     """Access to chrome://oobe/login page which is neither a tab nor an
74     extension."""
75     return self._misc_web_contents_backend
76
77   @property
78   def extension_dict_backend(self):
79     return self._extension_dict_backend
80
81   def GetBrowserStartupArgs(self):
82     args = []
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))
96
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)
103
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)
110
111     if self.browser_options.no_proxy_server:
112       args.append('--no-proxy-server')
113
114     if self.browser_options.disable_component_extensions_with_background_pages:
115       args.append('--disable-component-extensions-with-background-pages')
116
117     return args
118
119   def _WaitForBrowserToComeUp(self, wait_for_extensions=True, timeout=None):
120     def IsBrowserUp():
121       try:
122         self.Request('', timeout=timeout)
123       except (exceptions.BrowserGoneException,
124               exceptions.BrowserConnectionGoneException):
125         return False
126       else:
127         return True
128     try:
129       util.WaitFor(IsBrowserUp, timeout=30)
130     except util.TimeoutException:
131       raise exceptions.BrowserGoneException(self.GetStackTrace())
132
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')
141       """
142       for e in self._extensions_to_load:
143         if not e.extension_id in self._extension_dict_backend:
144           return False
145         extension_object = self._extension_dict_backend[e.extension_id]
146         try:
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).
153           res = None
154
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()
162         if not res:
163           return False
164       return True
165     if wait_for_extensions and self._supports_extensions:
166       try:
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(),
173                            indent=4))
174         raise
175
176
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']
183
184       if self._chrome_branch_number:
185         return
186
187       if 'Browser' in resp:
188         branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
189                                         resp['Browser'])
190       else:
191         branch_number_match = re.search(
192             'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
193             resp['User-Agent'])
194
195       if branch_number_match:
196         self._chrome_branch_number = int(branch_number_match.group(1))
197
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
202       return
203
204     # Detection has failed: assume 18.0.1025.168 ~= Chrome Android.
205     self._inspector_protocol_version = 1.0
206     self._chrome_branch_number = 1025
207
208   def Request(self, path, timeout=None, throw_network_exception=False):
209     url = 'http://127.0.0.1:%i/json' % self._port
210     if path:
211       url += '/' + path
212     try:
213       proxy_handler = urllib2.ProxyHandler({})  # Bypass any system proxy.
214       opener = urllib2.build_opener(proxy_handler)
215       req = opener.open(url, timeout=timeout)
216       return req.read()
217     except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
218       if throw_network_exception:
219         raise e
220       if not self.IsBrowserRunning():
221         raise exceptions.BrowserGoneException(e)
222       raise exceptions.BrowserConnectionGoneException(e)
223
224   @property
225   def browser_directory(self):
226     raise NotImplementedError()
227
228   @property
229   def profile_directory(self):
230     raise NotImplementedError()
231
232   @property
233   def chrome_branch_number(self):
234     assert self._chrome_branch_number
235     return self._chrome_branch_number
236
237   @property
238   def supports_tab_control(self):
239     return self.chrome_branch_number >= 1303
240
241   @property
242   def supports_tracing(self):
243     return self.is_content_shell or self.chrome_branch_number >= 1385
244
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
251     event categories.
252     """
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)
256
257   def StopTracing(self):
258     """ Stops tracing and returns the result as TraceResult object. """
259     return self._tracing_backend.StopTracing()
260
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:
266       return 'renderer'
267     m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
268     if not m:
269       return 'browser'
270     return m.group(1)
271
272   def Close(self):
273     if self._tracing_backend:
274       self._tracing_backend.Close()
275       self._tracing_backend = None
276
277   @property
278   def supports_system_info(self):
279     return self.GetSystemInfo() != None
280
281   def GetSystemInfo(self):
282     if self._system_info_backend is None:
283       self._system_info_backend = system_info_backend.SystemInfoBackend(
284           self._port)
285     return self._system_info_backend.GetSystemInfo()
286
287   def _SetBranchNumber(self, version):
288     assert version
289     self._chrome_branch_number = re.search(r'\d+\.\d+\.(\d+)\.\d+',
290                                            version).group(1)