blob: f811ff16d23660729a0a96252850985d7ed43ef6 [file] [log] [blame]
# Copyright (c) 2016 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.
"""Library handling DevTools websocket interaction.
"""
import httplib
import json
import logging
import os
import sys
file_dir = os.path.dirname(__file__)
sys.path.append(os.path.join(file_dir, '..', '..', 'telemetry'))
from telemetry.internal.backends.chrome_inspector import inspector_websocket
from telemetry.internal.backends.chrome_inspector import websocket
class DevToolsConnectionException(Exception):
def __init__(self, message):
super(DevToolsConnectionException, self).__init__(message)
logging.warning("DevToolsConnectionException: " + message)
class DevToolsConnection(object):
"""Handles the communication with a DevTools server.
"""
def __init__(self, hostname, port):
"""Initializes the connection with a DevTools server.
Args:
hostname: server hostname.
port: port number.
"""
self._ws = self._Connect(hostname, port)
self._listeners = {}
self._domains_to_enable = set()
self._please_stop = False
def RegisterListener(self, name, listener):
"""Registers a listener for an event.
Also takes care of enabling the relevant domain before starting monitoring.
Args:
name: (str) Event the listener wants to listen to, e.g.
Network.requestWillBeSent.
listener: (Listener) listener instance.
"""
domain = name[:name.index('.')]
self._listeners[name] = listener
self._domains_to_enable.add(domain)
def UnregisterListener(self, listener):
"""Unregisters a listener.
Args:
listener: (Listener) listener to unregister.
"""
keys = [k for (k, v) in self._listeners if k == name]
assert keys, "Removing non-existent listener"
for key in keys:
del(self._listeners[key])
def SyncRequest(self, method, params=None):
"""Issues a synchronous request to the DevTools server.
Args:
method: (str) Method.
params: (dict) Optional parameters to the request.
Returns:
The answer.
"""
request = {'method': method}
if params:
request['params'] = params
return self._ws.SyncRequest(request)
def SendAndIgnoreResponse(self, method, params=None):
"""Issues a request to the DevTools server, do not wait for the response.
Args:
method: (str) Method.
params: (dict) Optional parameters to the request.
"""
request = {'method': method}
if params:
request['params'] = params
self._ws.SendAndIgnoreResponse(request)
def SetUpMonitoring(self):
for domain in self._domains_to_enable:
self._ws.RegisterDomain(domain, self._OnDataReceived)
self.SyncRequest('%s.enable' % domain)
def StartMonitoring(self):
"""Starts monitoring.
DevToolsConnection.SetUpMonitoring() has to be called first.
"""
while not self._please_stop:
try:
self._ws.DispatchNotifications()
except websocket.WebSocketTimeoutException as e:
break
if not self._please_stop:
logging.warning('Monitoring stopped on a timeout.')
self._TearDownMonitoring()
def StopMonitoring(self):
"""Stops the monitoring."""
self._please_stop = True
def _TearDownMonitoring(self):
for domain in self._domains_to_enable:
self.SyncRequest('%s.disable' % domain)
self._ws.UnregisterDomain(domain)
self._domains_to_enable.clear()
self._listeners.clear()
def _OnDataReceived(self, msg):
method = msg.get('method', None)
if method not in self._listeners:
return
self._listeners[method].Handle(method, msg)
@classmethod
def _GetWebSocketUrl(cls, hostname, port):
r = httplib.HTTPConnection(hostname, port)
r.request('GET', '/json')
response = r.getresponse()
if response.status != 200:
raise DevToolsConnectionException(
'Cannot connect to DevTools, reponse code %d' % response.status)
json_response = json.loads(response.read())
r.close()
websocket_url = json_response[0]['webSocketDebuggerUrl']
return websocket_url
@classmethod
def _Connect(cls, hostname, port):
websocket_url = cls._GetWebSocketUrl(hostname, port)
ws = inspector_websocket.InspectorWebsocket()
ws.Connect(websocket_url)
return ws
class Listener(object):
"""Listens to events forwarded by a DevToolsConnection instance."""
def __init__(self, connection):
"""Initializes a Listener instance.
Args:
connection: (DevToolsConnection).
"""
pass
def Handle(self, method, msg):
"""Handles an event this instance listens for.
Args:
event_name: (str) Event name, as registered.
event: (dict) complete event.
"""
pass
class Track(Listener):
"""Collects data from a DevTools server."""
def GetEvents(self):
"""Returns a list of collected events, finalizing the state if necessary."""
pass