blob: 06acf3a2f2de0e8fb0f3cdc074eb6314551324c7 [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, output_profile_path,
extensions_to_load):
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 = output_profile_path
self._extensions_to_load = 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):
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')
# Set --no-proxy-server to work around some XP issues unless
# some other flag indicates a proxy is needed.
if not self._ArgsNeedProxyServer(args):
self.browser_options.no_proxy_server = True
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.no_proxy_server:
args.append('--no-proxy-server')
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.enable_logging:
args.append('--enable-logging')
args.append('--v=1')
return args
def GetReplayBrowserStartupArgs(self):
network_backend = self.platform_backend.network_controller_backend
if not network_backend.is_replay_active:
return []
replay_args = []
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')
# Force hostnames to resolve to the replay's host_ip.
replay_args.append('--host-resolver-rules=MAP * %s,EXCLUDE localhost' %
network_backend.host_ip)
# Force the browser to send HTTP/HTTPS requests to fixed ports if they
# are not the standard HTTP/HTTPS ports.
device_ports = network_backend.wpr_device_ports
if device_ports.http != 80:
replay_args.append('--testing-fixed-http-port=%s' % device_ports.http)
if device_ports.https != 443:
replay_args.append('--testing-fixed-https-port=%s' % device_ports.https)
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, custom_categories=None,
timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
"""
Args:
trace_options: An tracing_options.TracingOptions instance.
custom_categories: An optional string containing a list of
comma separated categories that will be traced
instead of the default category set. Example: use
"webkit,cc,disabled-by-default-cc.debug" to trace only
those three event categories.
"""
return self.devtools_client.StartChromeTracing(
trace_options, custom_categories, timeout)
def StopTracing(self, trace_data_builder):
self.devtools_client.StopChromeTracing(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