chrome_browser_backend.py revision a36e5920737c6adbddd3e43b760e5de8431db6e0
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 5import httplib 6import json 7import re 8import socket 9import sys 10import urllib2 11 12from telemetry.core import exceptions 13from telemetry.core import user_agent 14from telemetry.core import util 15from telemetry.core import web_contents 16from telemetry.core import wpr_modes 17from telemetry.core import wpr_server 18from telemetry.core.backends import browser_backend 19from telemetry.core.chrome import extension_dict_backend 20from telemetry.core.chrome import misc_web_contents_backend 21from telemetry.core.chrome import tab_list_backend 22from telemetry.core.chrome import tracing_backend 23from telemetry.unittest import options_for_unittests 24 25class ChromeBrowserBackend(browser_backend.BrowserBackend): 26 """An abstract class for chrome browser backends. Provides basic functionality 27 once a remote-debugger port has been established.""" 28 # It is OK to have abstract methods. pylint: disable=W0223 29 30 def __init__(self, is_content_shell, supports_extensions, options): 31 super(ChromeBrowserBackend, self).__init__( 32 is_content_shell=is_content_shell, 33 supports_extensions=supports_extensions, 34 options=options, 35 tab_list_backend=tab_list_backend.TabListBackend) 36 self._port = None 37 38 self._inspector_protocol_version = 0 39 self._chrome_branch_number = 0 40 self._tracing_backend = None 41 42 self.webpagereplay_local_http_port = util.GetAvailableLocalPort() 43 self.webpagereplay_local_https_port = util.GetAvailableLocalPort() 44 self.webpagereplay_remote_http_port = self.webpagereplay_local_http_port 45 self.webpagereplay_remote_https_port = self.webpagereplay_local_https_port 46 47 if options.dont_override_profile and not options_for_unittests.AreSet(): 48 sys.stderr.write('Warning: Not overriding profile. This can cause ' 49 'unexpected effects due to profile-specific settings, ' 50 'such as about:flags settings, cookies, and ' 51 'extensions.\n') 52 self._misc_web_contents_backend = ( 53 misc_web_contents_backend.MiscWebContentsBackend(self)) 54 self._extension_dict_backend = None 55 if supports_extensions: 56 self._extension_dict_backend = ( 57 extension_dict_backend.ExtensionDictBackend(self)) 58 59 def AddReplayServerOptions(self, options): 60 options.append('--no-dns_forwarding') 61 62 @property 63 def misc_web_contents_backend(self): 64 """Access to chrome://oobe/login page which is neither a tab nor an 65 extension.""" 66 return self._misc_web_contents_backend 67 68 @property 69 def extension_dict_backend(self): 70 return self._extension_dict_backend 71 72 def GetBrowserStartupArgs(self): 73 args = [] 74 args.extend(self.options.extra_browser_args) 75 args.append('--disable-background-networking') 76 args.append('--metrics-recording-only') 77 args.append('--no-first-run') 78 if self.options.wpr_mode != wpr_modes.WPR_OFF: 79 args.extend(wpr_server.GetChromeFlags( 80 self.WEBPAGEREPLAY_HOST, 81 self.webpagereplay_remote_http_port, 82 self.webpagereplay_remote_https_port)) 83 args.extend(user_agent.GetChromeUserAgentArgumentFromType( 84 self.options.browser_user_agent_type)) 85 86 extensions = [extension.local_path for extension in 87 self.options.extensions_to_load if not extension.is_component] 88 extension_str = ','.join(extensions) 89 if len(extensions) > 0: 90 args.append('--load-extension=%s' % extension_str) 91 92 component_extensions = [extension.local_path for extension in 93 self.options.extensions_to_load if extension.is_component] 94 component_extension_str = ','.join(component_extensions) 95 if len(component_extensions) > 0: 96 args.append('--load-component-extension=%s' % component_extension_str) 97 98 if self.options.no_proxy_server: 99 args.append('--no-proxy-server') 100 101 return args 102 103 def _WaitForBrowserToComeUp(self, timeout=None): 104 def IsBrowserUp(): 105 try: 106 self.Request('', timeout=timeout) 107 except (exceptions.BrowserGoneException, 108 exceptions.BrowserConnectionGoneException): 109 return False 110 else: 111 return True 112 try: 113 util.WaitFor(IsBrowserUp, timeout=30) 114 except util.TimeoutException: 115 raise exceptions.BrowserGoneException(self.GetStackTrace()) 116 117 def AllExtensionsLoaded(): 118 # Extension pages are loaded from an about:blank page, 119 # so we need to check that the document URL is the extension 120 # page in addition to the ready state. 121 extension_ready_js = """ 122 document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 && 123 (document.readyState == 'complete' || 124 document.readyState == 'interactive') 125 """ 126 for e in self.options.extensions_to_load: 127 if not e.extension_id in self._extension_dict_backend: 128 return False 129 extension_object = self._extension_dict_backend[e.extension_id] 130 res = extension_object.EvaluateJavaScript( 131 extension_ready_js % e.extension_id) 132 if not res: 133 return False 134 return True 135 if self._supports_extensions: 136 util.WaitFor(AllExtensionsLoaded, timeout=30) 137 138 def _PostBrowserStartupInitialization(self): 139 # Detect version information. 140 data = self.Request('version') 141 resp = json.loads(data) 142 if 'Protocol-Version' in resp: 143 self._inspector_protocol_version = resp['Protocol-Version'] 144 145 if 'Browser' in resp: 146 branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+', 147 resp['Browser']) 148 else: 149 branch_number_match = re.search( 150 'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari', 151 resp['User-Agent']) 152 153 if branch_number_match: 154 self._chrome_branch_number = int(branch_number_match.group(1)) 155 else: 156 # Content Shell returns '' for Browser, for now we have to 157 # fall-back and assume branch 1025. 158 self._chrome_branch_number = 1025 159 return 160 161 # Detection has failed: assume 18.0.1025.168 ~= Chrome Android. 162 self._inspector_protocol_version = 1.0 163 self._chrome_branch_number = 1025 164 165 def Request(self, path, timeout=None, throw_network_exception=False): 166 url = 'http://localhost:%i/json' % self._port 167 if path: 168 url += '/' + path 169 try: 170 req = urllib2.urlopen(url, timeout=timeout) 171 return req.read() 172 except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e: 173 if throw_network_exception: 174 raise e 175 if not self.IsBrowserRunning(): 176 raise exceptions.BrowserGoneException() 177 raise exceptions.BrowserConnectionGoneException() 178 179 @property 180 def chrome_branch_number(self): 181 return self._chrome_branch_number 182 183 @property 184 def supports_tab_control(self): 185 return self._chrome_branch_number >= 1303 186 187 @property 188 def supports_tracing(self): 189 return self.is_content_shell or self._chrome_branch_number >= 1385 190 191 def StartTracing(self, custom_categories=None, 192 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 193 """ custom_categories is an optional string containing a list of 194 comma separated categories that will be traced instead of the 195 default category set. Example: use 196 "webkit,cc,disabled-by-default-cc.debug" to trace only those three 197 event categories. 198 """ 199 if self._tracing_backend is None: 200 self._tracing_backend = tracing_backend.TracingBackend(self._port) 201 self._tracing_backend.BeginTracing(custom_categories, timeout) 202 203 def StopTracing(self): 204 self._tracing_backend.EndTracing() 205 206 def GetTraceResultAndReset(self): 207 return self._tracing_backend.GetTraceResultAndReset() 208 209 def GetProcessName(self, cmd_line): 210 """Returns a user-friendly name for the process of the given |cmd_line|.""" 211 if 'nacl_helper_bootstrap' in cmd_line: 212 return 'nacl_helper_bootstrap' 213 if ':sandboxed_process' in cmd_line: 214 return 'renderer' 215 m = re.match(r'.* --type=([^\s]*) .*', cmd_line) 216 if not m: 217 return 'browser' 218 return m.group(1) 219 220 def Close(self): 221 if self._tracing_backend: 222 self._tracing_backend.Close() 223 self._tracing_backend = None 224