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