Home | History | Annotate | Download | only in chrome
      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