blob: 9c24f4c2cf918d49383ccdcd924de94aa112fa51 [file] [log] [blame]
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
import pprint
import shlex
import sys
from telemetry.core import exceptions
from telemetry.core import util
from telemetry import decorators
from telemetry.internal.backends import browser_backend
from telemetry.internal.backends.chrome import extension_backend
from telemetry.internal.backends.chrome import system_info_backend
from telemetry.internal.backends.chrome import tab_list_backend
from telemetry.internal.backends.chrome_inspector import devtools_client_backend
from telemetry.internal.browser import user_agent
from telemetry.internal.browser import web_contents
from telemetry.testing import options_for_unittests
class ChromeBrowserBackend(browser_backend.BrowserBackend):
"""An abstract class for chrome browser backends. Provides basic functionality
once a remote-debugger port has been established."""
# It is OK to have abstract methods. pylint: disable=abstract-method
def __init__(self, platform_backend, supports_tab_control,
supports_extensions, browser_options):
super(ChromeBrowserBackend, self).__init__(
platform_backend=platform_backend,
supports_extensions=supports_extensions,
browser_options=browser_options,
tab_list_backend=tab_list_backend.TabListBackend)
self._port = None
self._supports_tab_control = supports_tab_control
self._devtools_client = None
self._system_info_backend = None
self._output_profile_path = browser_options.output_profile_path
self._extensions_to_load = browser_options.extensions_to_load
if (self.browser_options.dont_override_profile and
not options_for_unittests.AreSet()):
sys.stderr.write('Warning: Not overriding profile. This can cause '
'unexpected effects due to profile-specific settings, '
'such as about:flags settings, cookies, and '
'extensions.\n')
@property
def devtools_client(self):
return self._devtools_client
@property
@decorators.Cache
def extension_backend(self):
if not self.supports_extensions:
return None
return extension_backend.ExtensionBackendDict(self)
def _ArgsNeedProxyServer(self, args):
"""Returns True if args for Chrome indicate the need for proxy server."""
if '--enable-spdy-proxy-auth' in args:
return True
return [arg for arg in args if arg.startswith('--proxy-server=')]
def GetBrowserStartupArgs(self):
assert not '--no-proxy-server' in self.browser_options.extra_browser_args, (
'--no-proxy-server flag is disallowed as Chrome needs to be route to '
'ts_proxy_server')
args = []
args.extend(self.browser_options.extra_browser_args)
args.append('--enable-net-benchmarking')
args.append('--metrics-recording-only')
args.append('--no-default-browser-check')
args.append('--no-first-run')
# Turn on GPU benchmarking extension for all runs. The only side effect of
# the extension being on is that render stats are tracked. This is believed
# to be effectively free. And, by doing so here, it avoids us having to
# programmatically inspect a pageset's actions in order to determine if it
# might eventually scroll.
args.append('--enable-gpu-benchmarking')
if self.browser_options.disable_background_networking:
args.append('--disable-background-networking')
args.extend(self.GetReplayBrowserStartupArgs())
args.extend(user_agent.GetChromeUserAgentArgumentFromType(
self.browser_options.browser_user_agent_type))
extensions = [extension.local_path
for extension in self._extensions_to_load
if not extension.is_component]
extension_str = ','.join(extensions)
if len(extensions) > 0:
args.append('--load-extension=%s' % extension_str)
component_extensions = [extension.local_path
for extension in self._extensions_to_load
if extension.is_component]
component_extension_str = ','.join(component_extensions)
if len(component_extensions) > 0:
args.append('--load-component-extension=%s' % component_extension_str)
if self.browser_options.disable_component_extensions_with_background_pages:
args.append('--disable-component-extensions-with-background-pages')
# Disables the start page, as well as other external apps that can
# steal focus or make measurements inconsistent.
if self.browser_options.disable_default_apps:
args.append('--disable-default-apps')
if (self.browser_options.logging_verbosity ==
self.browser_options.NON_VERBOSE_LOGGING):
args.extend(['--enable-logging', '--v=0'])
elif (self.browser_options.logging_verbosity ==
self.browser_options.VERBOSE_LOGGING):
args.extend(['--enable-logging', '--v=1'])
return args
def GetReplayBrowserStartupArgs(self):
replay_args = []
network_backend = self.platform_backend.network_controller_backend
proxy_port = network_backend.forwarder.port_pair.remote_port
replay_args.append('--proxy-server=socks://localhost:%s' % proxy_port)
if not network_backend.is_replay_active:
return []
if not network_backend.is_test_ca_installed:
# Ignore certificate errors if the platform backend has not created
# and installed a root certificate.
replay_args.append('--ignore-certificate-errors')
return replay_args
def HasBrowserFinishedLaunching(self):
assert self._port, 'No DevTools port info available.'
return devtools_client_backend.IsDevToolsAgentAvailable(self._port, self)
def _InitDevtoolsClientBackend(self, remote_devtools_port=None):
""" Initiate the devtool client backend which allow browser connection
through browser' devtool.
Args:
remote_devtools_port: The remote devtools port, if
any. Otherwise assumed to be the same as self._port.
"""
assert not self._devtools_client, (
'Devtool client backend cannot be init twice')
self._devtools_client = devtools_client_backend.DevToolsClientBackend(
self._port, remote_devtools_port or self._port, self)
def _WaitForBrowserToComeUp(self):
""" Wait for browser to come up. """
try:
timeout = self.browser_options.browser_startup_timeout
util.WaitFor(self.HasBrowserFinishedLaunching, timeout=timeout)
except (exceptions.TimeoutException, exceptions.ProcessGoneException) as e:
if not self.IsBrowserRunning():
raise exceptions.BrowserGoneException(self.browser, e)
raise exceptions.BrowserConnectionGoneException(self.browser, e)
def _WaitForExtensionsToLoad(self):
""" Wait for all extensions to load.
Be sure to check whether the browser_backend supports_extensions before
calling this method.
"""
assert self._supports_extensions
assert self._devtools_client, (
'Waiting for extensions required devtool client to be initiated first')
try:
util.WaitFor(self._AllExtensionsLoaded, timeout=60)
except exceptions.TimeoutException:
logging.error('ExtensionsToLoad: ' +
repr([e.extension_id for e in self._extensions_to_load]))
logging.error('Extension list: ' +
pprint.pformat(self.extension_backend, indent=4))
raise
def _AllExtensionsLoaded(self):
# Extension pages are loaded from an about:blank page,
# so we need to check that the document URL is the extension
# page in addition to the ready state.
extension_ready_js = """
document.URL.lastIndexOf('chrome-extension://%s/', 0) == 0 &&
(document.readyState == 'complete' ||
document.readyState == 'interactive')
"""
for e in self._extensions_to_load:
try:
extension_objects = self.extension_backend[e.extension_id]
except KeyError:
return False
for extension_object in extension_objects:
try:
res = extension_object.EvaluateJavaScript(
extension_ready_js % e.extension_id)
except exceptions.EvaluateException:
# If the inspected page is not ready, we will get an error
# when we evaluate a JS expression, but we can just keep polling
# until the page is ready (crbug.com/251913).
res = None
# TODO(tengs): We don't have full support for getting the Chrome
# version before launch, so for now we use a generic workaround to
# check for an extension binding bug in old versions of Chrome.
# See crbug.com/263162 for details.
if res and extension_object.EvaluateJavaScript(
'chrome.runtime == null'):
extension_object.Reload()
if not res:
return False
return True
@property
def browser_directory(self):
raise NotImplementedError()
@property
def profile_directory(self):
raise NotImplementedError()
@property
def supports_tab_control(self):
return self._supports_tab_control
@property
def supports_tracing(self):
return True
def StartTracing(self, trace_options,
timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
"""
Args:
trace_options: An tracing_options.TracingOptions instance.
"""
return self.devtools_client.StartChromeTracing(trace_options, timeout)
def StopTracing(self):
self.devtools_client.StopChromeTracing()
def CollectTracingData(self, trace_data_builder):
self.devtools_client.CollectChromeTracingData(trace_data_builder)
def GetProcessName(self, cmd_line):
"""Returns a user-friendly name for the process of the given |cmd_line|."""
if not cmd_line:
# TODO(tonyg): Eventually we should make all of these known and add an
# assertion.
return 'unknown'
if 'nacl_helper_bootstrap' in cmd_line:
return 'nacl_helper_bootstrap'
if ':sandboxed_process' in cmd_line:
return 'renderer'
if ':privileged_process' in cmd_line:
return 'gpu-process'
args = shlex.split(cmd_line)
types = [arg.split('=')[1] for arg in args if arg.startswith('--type=')]
if not types:
return 'browser'
return types[0]
def Close(self):
if self._devtools_client:
self._devtools_client.Close()
self._devtools_client = None
@property
def supports_system_info(self):
return self.GetSystemInfo() != None
def GetSystemInfo(self):
if self._system_info_backend is None:
self._system_info_backend = system_info_backend.SystemInfoBackend(
self._port)
return self._system_info_backend.GetSystemInfo()
@property
def supports_memory_dumping(self):
return True
def DumpMemory(self, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
return self.devtools_client.DumpMemory(timeout)
@property
def supports_overriding_memory_pressure_notifications(self):
return True
def SetMemoryPressureNotificationsSuppressed(
self, suppressed, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
self.devtools_client.SetMemoryPressureNotificationsSuppressed(
suppressed, timeout)
def SimulateMemoryPressureNotification(
self, pressure_level, timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
self.devtools_client.SimulateMemoryPressureNotification(
pressure_level, timeout)
@property
def supports_cpu_metrics(self):
return True
@property
def supports_memory_metrics(self):
return True
@property
def supports_power_metrics(self):
return True