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 logging 6 import pprint 7 import shlex 8 import sys 9 10 from telemetry.core import exceptions 11 from telemetry.core import util 12 from telemetry import decorators 13 from telemetry.internal.backends import browser_backend 14 from telemetry.internal.backends.chrome import extension_backend 15 from telemetry.internal.backends.chrome import system_info_backend 16 from telemetry.internal.backends.chrome import tab_list_backend 17 from telemetry.internal.backends.chrome_inspector import devtools_client_backend 18 from telemetry.internal.browser import user_agent 19 from telemetry.internal.browser import web_contents 20 from telemetry.testing import options_for_unittests 21 22 23 class ChromeBrowserBackend(browser_backend.BrowserBackend): 24 """An abstract class for chrome browser backends. Provides basic functionality 25 once a remote-debugger port has been established.""" 26 # It is OK to have abstract methods. pylint: disable=abstract-method 27 28 def __init__(self, platform_backend, supports_tab_control, 29 supports_extensions, browser_options): 30 super(ChromeBrowserBackend, self).__init__( 31 platform_backend=platform_backend, 32 supports_extensions=supports_extensions, 33 browser_options=browser_options, 34 tab_list_backend=tab_list_backend.TabListBackend) 35 self._port = None 36 37 self._supports_tab_control = supports_tab_control 38 self._devtools_client = None 39 self._system_info_backend = None 40 41 self._output_profile_path = browser_options.output_profile_path 42 self._extensions_to_load = browser_options.extensions_to_load 43 44 if (self.browser_options.dont_override_profile and 45 not options_for_unittests.AreSet()): 46 sys.stderr.write('Warning: Not overriding profile. This can cause ' 47 'unexpected effects due to profile-specific settings, ' 48 'such as about:flags settings, cookies, and ' 49 'extensions.\n') 50 51 @property 52 def devtools_client(self): 53 return self._devtools_client 54 55 @property 56 @decorators.Cache 57 def extension_backend(self): 58 if not self.supports_extensions: 59 return None 60 return extension_backend.ExtensionBackendDict(self) 61 62 def _ArgsNeedProxyServer(self, args): 63 """Returns True if args for Chrome indicate the need for proxy server.""" 64 if '--enable-spdy-proxy-auth' in args: 65 return True 66 return [arg for arg in args if arg.startswith('--proxy-server=')] 67 68 def GetBrowserStartupArgs(self): 69 assert not '--no-proxy-server' in self.browser_options.extra_browser_args, ( 70 '--no-proxy-server flag is disallowed as Chrome needs to be route to ' 71 'ts_proxy_server') 72 args = [] 73 args.extend(self.browser_options.extra_browser_args) 74 args.append('--enable-net-benchmarking') 75 args.append('--metrics-recording-only') 76 args.append('--no-default-browser-check') 77 args.append('--no-first-run') 78 79 # Turn on GPU benchmarking extension for all runs. The only side effect of 80 # the extension being on is that render stats are tracked. This is believed 81 # to be effectively free. And, by doing so here, it avoids us having to 82 # programmatically inspect a pageset's actions in order to determine if it 83 # might eventually scroll. 84 args.append('--enable-gpu-benchmarking') 85 86 if self.browser_options.disable_background_networking: 87 args.append('--disable-background-networking') 88 args.extend(self.GetReplayBrowserStartupArgs()) 89 args.extend(user_agent.GetChromeUserAgentArgumentFromType( 90 self.browser_options.browser_user_agent_type)) 91 92 extensions = [extension.local_path 93 for extension in self._extensions_to_load 94 if not extension.is_component] 95 extension_str = ','.join(extensions) 96 if len(extensions) > 0: 97 args.append('--load-extension=%s' % extension_str) 98 99 component_extensions = [extension.local_path 100 for extension in self._extensions_to_load 101 if extension.is_component] 102 component_extension_str = ','.join(component_extensions) 103 if len(component_extensions) > 0: 104 args.append('--load-component-extension=%s' % component_extension_str) 105 106 if self.browser_options.disable_component_extensions_with_background_pages: 107 args.append('--disable-component-extensions-with-background-pages') 108 109 # Disables the start page, as well as other external apps that can 110 # steal focus or make measurements inconsistent. 111 if self.browser_options.disable_default_apps: 112 args.append('--disable-default-apps') 113 114 if (self.browser_options.logging_verbosity == 115 self.browser_options.NON_VERBOSE_LOGGING): 116 args.extend(['--enable-logging', '--v=0']) 117 elif (self.browser_options.logging_verbosity == 118 self.browser_options.VERBOSE_LOGGING): 119 args.extend(['--enable-logging', '--v=1']) 120 121 return args 122 123 def GetReplayBrowserStartupArgs(self): 124 replay_args = [] 125 network_backend = self.platform_backend.network_controller_backend 126 proxy_port = network_backend.forwarder.port_pair.remote_port 127 replay_args.append('--proxy-server=socks://localhost:%s' % proxy_port) 128 if not network_backend.is_replay_active: 129 return [] 130 if not network_backend.is_test_ca_installed: 131 # Ignore certificate errors if the platform backend has not created 132 # and installed a root certificate. 133 replay_args.append('--ignore-certificate-errors') 134 return replay_args 135 136 def HasBrowserFinishedLaunching(self): 137 assert self._port, 'No DevTools port info available.' 138 return devtools_client_backend.IsDevToolsAgentAvailable(self._port, self) 139 140 def _InitDevtoolsClientBackend(self, remote_devtools_port=None): 141 """ Initiate the devtool client backend which allow browser connection 142 through browser' devtool. 143 144 Args: 145 remote_devtools_port: The remote devtools port, if 146 any. Otherwise assumed to be the same as self._port. 147 """ 148 assert not self._devtools_client, ( 149 'Devtool client backend cannot be init twice') 150 self._devtools_client = devtools_client_backend.DevToolsClientBackend( 151 self._port, remote_devtools_port or self._port, self) 152 153 def _WaitForBrowserToComeUp(self): 154 """ Wait for browser to come up. """ 155 try: 156 timeout = self.browser_options.browser_startup_timeout 157 util.WaitFor(self.HasBrowserFinishedLaunching, timeout=timeout) 158 except (exceptions.TimeoutException, exceptions.ProcessGoneException) as e: 159 if not self.IsBrowserRunning(): 160 raise exceptions.BrowserGoneException(self.browser, e) 161 raise exceptions.BrowserConnectionGoneException(self.browser, e) 162 163 def _WaitForExtensionsToLoad(self): 164 """ Wait for all extensions to load. 165 Be sure to check whether the browser_backend supports_extensions before 166 calling this method. 167 """ 168 assert self._supports_extensions 169 assert self._devtools_client, ( 170 'Waiting for extensions required devtool client to be initiated first') 171 try: 172 util.WaitFor(self._AllExtensionsLoaded, timeout=60) 173 except exceptions.TimeoutException: 174 logging.error('ExtensionsToLoad: ' + 175 repr([e.extension_id for e in self._extensions_to_load])) 176 logging.error('Extension list: ' + 177 pprint.pformat(self.extension_backend, indent=4)) 178 raise 179 180 def _AllExtensionsLoaded(self): 181 # Extension pages are loaded from an about:blank page, 182 # so we need to check that the document URL is the extension 183 # page in addition to the ready state. 184 extension_ready_js = """ 185 document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 && 186 (document.readyState == 'complete' || 187 document.readyState == 'interactive') 188 """ 189 for e in self._extensions_to_load: 190 try: 191 extension_objects = self.extension_backend[e.extension_id] 192 except KeyError: 193 return False 194 for extension_object in extension_objects: 195 try: 196 res = extension_object.EvaluateJavaScript( 197 extension_ready_js % e.extension_id) 198 except exceptions.EvaluateException: 199 # If the inspected page is not ready, we will get an error 200 # when we evaluate a JS expression, but we can just keep polling 201 # until the page is ready (crbug.com/251913). 202 res = None 203 204 # TODO(tengs): We don't have full support for getting the Chrome 205 # version before launch, so for now we use a generic workaround to 206 # check for an extension binding bug in old versions of Chrome. 207 # See crbug.com/263162 for details. 208 if res and extension_object.EvaluateJavaScript( 209 'chrome.runtime == null'): 210 extension_object.Reload() 211 if not res: 212 return False 213 return True 214 215 @property 216 def browser_directory(self): 217 raise NotImplementedError() 218 219 @property 220 def profile_directory(self): 221 raise NotImplementedError() 222 223 @property 224 def supports_tab_control(self): 225 return self._supports_tab_control 226 227 @property 228 def supports_tracing(self): 229 return True 230 231 def StartTracing(self, trace_options, 232 timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 233 """ 234 Args: 235 trace_options: An tracing_options.TracingOptions instance. 236 """ 237 return self.devtools_client.StartChromeTracing(trace_options, timeout) 238 239 def StopTracing(self): 240 self.devtools_client.StopChromeTracing() 241 242 def CollectTracingData(self, trace_data_builder): 243 self.devtools_client.CollectChromeTracingData(trace_data_builder) 244 245 def GetProcessName(self, cmd_line): 246 """Returns a user-friendly name for the process of the given |cmd_line|.""" 247 if not cmd_line: 248 # TODO(tonyg): Eventually we should make all of these known and add an 249 # assertion. 250 return 'unknown' 251 if 'nacl_helper_bootstrap' in cmd_line: 252 return 'nacl_helper_bootstrap' 253 if ':sandboxed_process' in cmd_line: 254 return 'renderer' 255 if ':privileged_process' in cmd_line: 256 return 'gpu-process' 257 args = shlex.split(cmd_line) 258 types = [arg.split('=')[1] for arg in args if arg.startswith('--type=')] 259 if not types: 260 return 'browser' 261 return types[0] 262 263 def Close(self): 264 if self._devtools_client: 265 self._devtools_client.Close() 266 self._devtools_client = None 267 268 @property 269 def supports_system_info(self): 270 return self.GetSystemInfo() != None 271 272 def GetSystemInfo(self): 273 if self._system_info_backend is None: 274 self._system_info_backend = system_info_backend.SystemInfoBackend( 275 self._port) 276 return self._system_info_backend.GetSystemInfo() 277 278 @property 279 def supports_memory_dumping(self): 280 return True 281 282 def DumpMemory(self, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 283 return self.devtools_client.DumpMemory(timeout) 284 285 @property 286 def supports_overriding_memory_pressure_notifications(self): 287 return True 288 289 def SetMemoryPressureNotificationsSuppressed( 290 self, suppressed, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 291 self.devtools_client.SetMemoryPressureNotificationsSuppressed( 292 suppressed, timeout) 293 294 def SimulateMemoryPressureNotification( 295 self, pressure_level, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT): 296 self.devtools_client.SimulateMemoryPressureNotification( 297 pressure_level, timeout) 298 299 @property 300 def supports_cpu_metrics(self): 301 return True 302 303 @property 304 def supports_memory_metrics(self): 305 return True 306 307 @property 308 def supports_power_metrics(self): 309 return True 310