1424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# Use of this source code is governed by a BSD-style license that can be
3eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# found in the LICENSE file.
4a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)import logging
5a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
6a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from telemetry.core import util
7a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)from telemetry.core.backends.chrome import timeline_recorder
8f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)from telemetry.timeline import inspector_timeline_data
9a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
10a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
11a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class InspectorNetworkException(Exception):
12a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  pass
13a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
14a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class InspectorNetworkResponseData(object):
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def __init__(self, inspector_network, params):
17a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_network = inspector_network
18a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._request_id = params['requestId']
19a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._timestamp = params['timestamp']
20a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
21a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._response = params['response']
22a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if not self._response:
23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      raise InspectorNetworkException('response must exist')
24a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
25a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Response headers.
26a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    headers = self._response['headers']
27a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._header_map = {}
28a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for k, v in headers.iteritems():
29a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      # Camel-case header keys.
30a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._header_map[k.title()] = v
31a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
32a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Request headers.
33a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._request_header_map = {}
34a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if 'requestHeaders' in self._response:
35a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      # Camel-case header keys.
36a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      for k, v in self._response['requestHeaders'].iteritems():
37a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        self._request_header_map[k.title()] = v
38a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
39a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._body = None
40a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._base64_encoded = False
41a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if self._inspector_network:
42a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._served_from_cache = (
43a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          self._inspector_network.HTTPResponseServedFromCache(self._request_id))
44a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    else:
45a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._served_from_cache = False
46a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
47a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Whether constructed from a timeline event.
48a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._from_event = False
49a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
51a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def status(self):
52a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._response['status']
53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def status_text(self):
55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._response['status_text']
56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def headers(self):
59a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._header_map
60a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
61a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
62a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def request_headers(self):
63a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._request_header_map
64a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
65a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
66a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def timestamp(self):
67a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._timestamp
68a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
69a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
70a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def timing(self):
71a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if 'timing' in self._response:
72a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return self._response['timing']
73a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return None
74a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
75a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
76a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def url(self):
77a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._response['url']
78a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
79a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
80a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def request_id(self):
81a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._request_id
82a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
83a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
84a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def served_from_cache(self):
8523730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)    return self._served_from_cache
86a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def GetHeader(self, name):
88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if name in self.headers:
89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return self.headers[name]
90a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return None
91a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
92a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def GetBody(self, timeout=60):
93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if not self._body and not self._from_event:
94a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._body, self._base64_encoded = (
95a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        self._inspector_network.GetHTTPResponseBody(self._request_id, timeout))
96a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._body, self._base64_encoded
97a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def AsTimelineEvent(self):
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event = {}
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['type'] = 'HTTPResponse'
101a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['startTime'] = self.timestamp
102a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # There is no end time. Just return the timestamp instead.
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['endTime'] = self.timestamp
104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['requestId'] = self.request_id
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['response'] = self._response
106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['body'], event['base64_encoded_body'] = self.GetBody()
107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    event['served_from_cache'] = self.served_from_cache
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return event
109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @staticmethod
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def FromTimelineEvent(event):
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    assert event.name == 'HTTPResponse'
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    params = {}
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    params['timestamp'] = event.start
115a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    params['requestId'] = event.args['requestId']
116a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    params['response'] = event.args['response']
117a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    recorded = InspectorNetworkResponseData(None, params)
118a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    recorded._body = event.args['body']
119a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    recorded._base64_encoded = event.args['base64_encoded_body']
120a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    recorded._served_from_cache = event.args['served_from_cache']
121a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    recorded._from_event = True
122a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return recorded
123a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
124eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochclass InspectorNetwork(object):
126eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def __init__(self, inspector_backend):
127eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._inspector_backend = inspector_backend
128a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._http_responses = []
129a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._served_from_cache = set()
130a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._timeline_recorder = None
131eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
132eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def ClearCache(self, timeout=60):
133eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    """Clears the browser's disk and memory cache."""
134eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    res = self._inspector_backend.SyncRequest({
135eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        'method': 'Network.canClearBrowserCache'
136eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        }, timeout)
137a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    assert res['result'], 'Cache clearing is not supported by this browser.'
138eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._inspector_backend.SyncRequest({
139eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        'method': 'Network.clearBrowserCache'
140eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch        }, timeout)
141a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
142a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def StartMonitoringNetwork(self):
143a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Starts monitoring network notifications and recording HTTP responses."""
144a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self.ClearResponseData()
145a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_backend.RegisterDomain(
146a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        'Network',
147a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        self._OnNetworkNotification,
148a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        self._OnClose)
149a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    request = {
150a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        'method': 'Network.enable'
151a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        }
152a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_backend.SyncRequest(request)
153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def StopMonitoringNetwork(self):
155a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Stops monitoring network notifications and recording HTTP responses."""
156a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_backend.UnregisterDomain('Network')
157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    request = {
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        'method': 'Network.disable'
159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        }
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_backend.SyncRequest(request)
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def GetResponseData(self):
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Returns all recorded HTTP responses."""
164a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._http_responses
165a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
166a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def ClearResponseData(self):
167a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    """Clears recorded HTTP responses."""
168a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._http_responses = []
169a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._served_from_cache.clear()
170a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
171a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def _OnNetworkNotification(self, msg):
172a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if msg['method'] == 'Network.responseReceived':
173a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._RecordHTTPResponse(msg['params'])
174a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    elif msg['method'] == 'Network.requestServedFromCache':
175a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._served_from_cache.add(msg['params']['requestId'])
176a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
177a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def _RecordHTTPResponse(self, params):
178a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    required_fields = ['requestId', 'timestamp', 'response']
179a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for field in required_fields:
180a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if field not in params:
181a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        logging.waring('HTTP Response missing required field: %s', field)
182a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        return
183a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._http_responses.append(InspectorNetworkResponseData(self, params))
184a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
185a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def GetHTTPResponseBody(self, request_id, timeout=60):
186a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    try:
187a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      res =  self._inspector_backend.SyncRequest({
188a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          'method': 'Network.getResponseBody',
189a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          'params': {
190a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              'requestId': request_id,
191a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)              }
192a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)          }, timeout)
193a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    except util.TimeoutException:
194a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      logging.warning('Timeout during fetching body for %s' % request_id)
195a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return None, False
196a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if 'error' in res:
197a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return None, False
198a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return res['result']['body'], res['result']['base64Encoded']
199a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
200a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def HTTPResponseServedFromCache(self, request_id):
201a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return request_id and request_id in self._served_from_cache
202a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
203a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def _OnClose(self):
204a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    pass
205a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
206a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  @property
207a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def timeline_recorder (self):
208a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if not self._timeline_recorder:
209a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      self._timeline_recorder = TimelineRecorder(self)
210a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return self._timeline_recorder
211a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
212a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
213a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)class TimelineRecorder(timeline_recorder.TimelineRecorder):
214a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def __init__(self, inspector_network):
215a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    super(TimelineRecorder, self).__init__()
216a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_network = inspector_network
217a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._is_recording = False
218a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
219a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def Start(self):
220a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    assert not self._is_recording, 'Start should only be called once.'
221a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._is_recording = True
222a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_network.StartMonitoringNetwork()
223a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
224a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  def Stop(self):
225a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if not self._is_recording:
226a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return None
227a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    responses = self._inspector_network.GetResponseData()
228a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    events = [r.AsTimelineEvent() for r in list(responses)]
229a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._inspector_network.StopMonitoringNetwork()
230a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    self._is_recording = False
231a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if len(events) == 0:
232a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      return None
233a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return inspector_timeline_data.InspectorTimelineData(events)
234