blob: 13bb286bd8d4e9c68b76f1bde55a998a3181d402 [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 httplib
import json
import re
import socket
import sys
import urllib2
from telemetry.core import exceptions
from telemetry.core import user_agent
from telemetry.core import util
from telemetry.core import web_contents
from telemetry.core import wpr_modes
from telemetry.core import wpr_server
from telemetry.core.backends import browser_backend
from telemetry.core.chrome import extension_dict_backend
from telemetry.core.chrome import misc_web_contents_backend
from telemetry.core.chrome import tab_list_backend
from telemetry.core.chrome import tracing_backend
from telemetry.unittest 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=W0223
def __init__(self, is_content_shell, supports_extensions, options):
super(ChromeBrowserBackend, self).__init__(
is_content_shell=is_content_shell,
supports_extensions=supports_extensions,
options=options,
tab_list_backend=tab_list_backend.TabListBackend)
self._port = None
self._inspector_protocol_version = 0
self._chrome_branch_number = 0
self._tracing_backend = None
self.webpagereplay_local_http_port = util.GetAvailableLocalPort()
self.webpagereplay_local_https_port = util.GetAvailableLocalPort()
self.webpagereplay_remote_http_port = self.webpagereplay_local_http_port
self.webpagereplay_remote_https_port = self.webpagereplay_local_https_port
if 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')
self._misc_web_contents_backend = (
misc_web_contents_backend.MiscWebContentsBackend(self))
self._extension_dict_backend = None
if supports_extensions:
self._extension_dict_backend = (
extension_dict_backend.ExtensionDictBackend(self))
def AddReplayServerOptions(self, options):
options.append('--no-dns_forwarding')
@property
def misc_web_contents_backend(self):
"""Access to chrome://oobe/login page which is neither a tab nor an
extension."""
return self._misc_web_contents_backend
@property
def extension_dict_backend(self):
return self._extension_dict_backend
def GetBrowserStartupArgs(self):
args = []
args.extend(self.options.extra_browser_args)
args.append('--disable-background-networking')
args.append('--metrics-recording-only')
args.append('--no-first-run')
if self.options.wpr_mode != wpr_modes.WPR_OFF:
args.extend(wpr_server.GetChromeFlags(
self.WEBPAGEREPLAY_HOST,
self.webpagereplay_remote_http_port,
self.webpagereplay_remote_https_port))
args.extend(user_agent.GetChromeUserAgentArgumentFromType(
self.options.browser_user_agent_type))
extensions = [extension.local_path for extension in
self.options.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.options.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.options.no_proxy_server:
args.append('--no-proxy-server')
return args
def _WaitForBrowserToComeUp(self, timeout=None):
def IsBrowserUp():
try:
self.Request('', timeout=timeout)
except (exceptions.BrowserGoneException,
exceptions.BrowserConnectionGoneException):
return False
else:
return True
try:
util.WaitFor(IsBrowserUp, timeout=30)
except util.TimeoutException:
raise exceptions.BrowserGoneException(self.GetStackTrace())
def AllExtensionsLoaded():
# 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.options.extensions_to_load:
if not e.extension_id in self._extension_dict_backend:
return False
extension_object = self._extension_dict_backend[e.extension_id]
res = extension_object.EvaluateJavaScript(
extension_ready_js % e.extension_id)
if not res:
return False
return True
if self._supports_extensions:
util.WaitFor(AllExtensionsLoaded, timeout=30)
def _PostBrowserStartupInitialization(self):
# Detect version information.
data = self.Request('version')
resp = json.loads(data)
if 'Protocol-Version' in resp:
self._inspector_protocol_version = resp['Protocol-Version']
if 'Browser' in resp:
branch_number_match = re.search('Chrome/\d+\.\d+\.(\d+)\.\d+',
resp['Browser'])
else:
branch_number_match = re.search(
'Chrome/\d+\.\d+\.(\d+)\.\d+ (Mobile )?Safari',
resp['User-Agent'])
if branch_number_match:
self._chrome_branch_number = int(branch_number_match.group(1))
else:
# Content Shell returns '' for Browser, for now we have to
# fall-back and assume branch 1025.
self._chrome_branch_number = 1025
return
# Detection has failed: assume 18.0.1025.168 ~= Chrome Android.
self._inspector_protocol_version = 1.0
self._chrome_branch_number = 1025
def Request(self, path, timeout=None, throw_network_exception=False):
url = 'http://localhost:%i/json' % self._port
if path:
url += '/' + path
try:
req = urllib2.urlopen(url, timeout=timeout)
return req.read()
except (socket.error, httplib.BadStatusLine, urllib2.URLError) as e:
if throw_network_exception:
raise e
if not self.IsBrowserRunning():
raise exceptions.BrowserGoneException()
raise exceptions.BrowserConnectionGoneException()
@property
def chrome_branch_number(self):
return self._chrome_branch_number
@property
def supports_tab_control(self):
return self._chrome_branch_number >= 1303
@property
def supports_tracing(self):
return self.is_content_shell or self._chrome_branch_number >= 1385
def StartTracing(self, custom_categories=None,
timeout=web_contents.DEFAULT_WEB_CONTENTS_TIMEOUT):
""" custom_categories is 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.
"""
if self._tracing_backend is None:
self._tracing_backend = tracing_backend.TracingBackend(self._port)
self._tracing_backend.BeginTracing(custom_categories, timeout)
def StopTracing(self):
self._tracing_backend.EndTracing()
def GetTraceResultAndReset(self):
return self._tracing_backend.GetTraceResultAndReset()
def GetProcessName(self, cmd_line):
"""Returns a user-friendly name for the process of the given |cmd_line|."""
if 'nacl_helper_bootstrap' in cmd_line:
return 'nacl_helper_bootstrap'
if ':sandboxed_process' in cmd_line:
return 'renderer'
m = re.match(r'.* --type=([^\s]*) .*', cmd_line)
if not m:
return 'browser'
return m.group(1)
def Close(self):
if self._tracing_backend:
self._tracing_backend.Close()
self._tracing_backend = None