1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
10 from telemetry.core import bitmap
11 from telemetry.core import exceptions
12 from telemetry.core import util
13 from telemetry.core.backends.chrome import inspector_console
14 from telemetry.core.backends.chrome import inspector_memory
15 from telemetry.core.backends.chrome import inspector_network
16 from telemetry.core.backends.chrome import inspector_page
17 from telemetry.core.backends.chrome import inspector_runtime
18 from telemetry.core.backends.chrome import inspector_timeline
19 from telemetry.core.backends.chrome import websocket
20 from telemetry.core.heap import model
22 class InspectorException(Exception):
25 class InspectorBackend(object):
26 def __init__(self, browser, browser_backend, debugger_url, timeout=60):
28 self._browser = browser
29 self._browser_backend = browser_backend
30 self._debugger_url = debugger_url
32 self._domain_handlers = {}
33 self._cur_socket_timeout = 0
34 self._next_request_id = 0
36 self._console = inspector_console.InspectorConsole(self)
37 self._memory = inspector_memory.InspectorMemory(self)
38 self._page = inspector_page.InspectorPage(self, timeout=timeout)
39 self._runtime = inspector_runtime.InspectorRuntime(self)
40 self._timeline = inspector_timeline.InspectorTimeline(self)
41 self._network = inspector_network.InspectorNetwork(self)
46 def _Connect(self, timeout=10):
50 self._socket = websocket.create_connection(self._debugger_url,
52 except (websocket.WebSocketException):
53 if self._browser_backend.IsBrowserRunning():
54 raise exceptions.TabCrashException(sys.exc_info()[1])
56 raise exceptions.BrowserGoneException()
58 self._cur_socket_timeout = 0
59 self._next_request_id = 0
62 for _, handlers in self._domain_handlers.items():
63 _, will_close_handler = handlers
65 self._domain_handlers = {}
71 # General public methods.
80 return self._browser_backend.tab_list_backend.GetTabUrl(self._debugger_url)
84 self._browser_backend.tab_list_backend.ActivateTab(self._debugger_url)
88 self._browser_backend.tab_list_backend.CloseTab(self._debugger_url)
90 # Public methods implemented in JavaScript.
93 def screenshot_supported(self):
94 if self._runtime.Evaluate(
95 'window.chrome.gpuBenchmarking === undefined'):
98 if self._runtime.Evaluate(
99 'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'):
102 return (self._browser_backend.chrome_branch_number >= 1391 or
103 self._browser_backend.is_content_shell)
105 def Screenshot(self, timeout):
106 if self._runtime.Evaluate(
107 'window.chrome.gpuBenchmarking === undefined'):
108 raise Exception("Browser was not started with --enable-gpu-benchmarking")
110 if self._runtime.Evaluate(
111 'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'):
112 raise Exception("Browser does not support window snapshot API.")
114 self._runtime.Evaluate("""
115 if(!window.__telemetry) {
116 window.__telemetry = {}
118 window.__telemetry.snapshotComplete = false;
119 window.__telemetry.snapshotData = null;
120 window.chrome.gpuBenchmarking.beginWindowSnapshotPNG(
122 window.__telemetry.snapshotData = snapshot;
123 window.__telemetry.snapshotComplete = true;
128 def IsSnapshotComplete():
129 return self._runtime.Evaluate('window.__telemetry.snapshotComplete')
131 util.WaitFor(IsSnapshotComplete, timeout)
133 snap = self._runtime.Evaluate("""
135 var data = window.__telemetry.snapshotData;
136 delete window.__telemetry.snapshotComplete;
137 delete window.__telemetry.snapshotData;
142 return bitmap.Bitmap.FromBase64Png(snap['data'])
145 # Console public methods.
148 def message_output_stream(self): # pylint: disable=E0202
149 return self._console.message_output_stream
151 @message_output_stream.setter
152 def message_output_stream(self, stream): # pylint: disable=E0202
153 self._console.message_output_stream = stream
155 # Memory public methods.
157 def GetDOMStats(self, timeout):
158 dom_counters = self._memory.GetDOMCounters(timeout)
160 'document_count': dom_counters['documents'],
161 'node_count': dom_counters['nodes'],
162 'event_listener_count': dom_counters['jsEventListeners']
165 # Page public methods.
167 def PerformActionAndWaitForNavigate(self, action_function, timeout):
168 self._page.PerformActionAndWaitForNavigate(action_function, timeout)
170 def Navigate(self, url, script_to_evaluate_on_commit, timeout):
171 self._page.Navigate(url, script_to_evaluate_on_commit, timeout)
173 def GetCookieByName(self, name, timeout):
174 return self._page.GetCookieByName(name, timeout)
176 # Runtime public methods.
178 def ExecuteJavaScript(self, expr, timeout):
179 self._runtime.Execute(expr, timeout)
181 def EvaluateJavaScript(self, expr, timeout):
182 return self._runtime.Evaluate(expr, timeout)
184 # Timeline public methods.
187 def timeline_model(self):
188 return self._timeline.timeline_model
190 def StartTimelineRecording(self):
191 self._timeline.Start()
193 def StopTimelineRecording(self):
194 self._timeline.Stop()
196 # Network public methods.
198 def ClearCache(self):
199 self._network.ClearCache()
201 # Methods used internally by other backends.
203 def DispatchNotifications(self, timeout=10):
204 self._Connect(timeout)
205 self._SetTimeout(timeout)
206 res = self._ReceiveJsonData(timeout)
208 self._HandleNotification(res)
210 def _ReceiveJsonData(self, timeout):
212 start_time = time.time()
213 data = self._socket.recv()
214 except (socket.error, websocket.WebSocketException):
215 if self._browser_backend.tab_list_backend.DoesDebuggerUrlExist(
217 elapsed_time = time.time() - start_time
218 raise util.TimeoutException(
219 'Received a socket error in the browser connection and the tab '
220 'still exists, assuming it timed out. '
221 'Timeout=%ds Elapsed=%ds Error=%s' % (
222 timeout, elapsed_time, sys.exc_info()[1]))
223 raise exceptions.TabCrashException(
224 'Received a socket error in the browser connection and the tab no '
225 'longer exists, assuming it crashed. Error=%s' % sys.exc_info()[1])
226 res = json.loads(data)
227 logging.debug('got [%s]', data)
230 def _HandleNotification(self, res):
231 if (res['method'] == 'Inspector.detached' and
232 res.get('params', {}).get('reason','') == 'replaced_with_devtools'):
233 self._WaitForInspectorToGoAwayAndReconnect()
235 if res['method'] == 'Inspector.targetCrashed':
236 raise exceptions.TabCrashException()
238 mname = res['method']
239 dot_pos = mname.find('.')
240 domain_name = mname[:dot_pos]
241 if domain_name in self._domain_handlers:
243 self._domain_handlers[domain_name][0](res)
246 traceback.print_exc()
248 logging.debug('Unhandled inspector message: %s', res)
250 def SendAndIgnoreResponse(self, req):
252 req['id'] = self._next_request_id
253 self._next_request_id += 1
254 data = json.dumps(req)
255 self._socket.send(data)
256 logging.debug('sent [%s]', data)
258 def _SetTimeout(self, timeout):
259 if self._cur_socket_timeout != timeout:
260 self._socket.settimeout(timeout)
261 self._cur_socket_timeout = timeout
263 def _WaitForInspectorToGoAwayAndReconnect(self):
264 sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n')
265 sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n')
269 if not self._browser_backend.tab_list_backend.DoesDebuggerUrlExist(
274 except exceptions.TabCrashException, ex:
275 if ex.message.message.find('Handshake Status 500') == 0:
279 util.WaitFor(IsBack, 512)
280 sys.stderr.write('\n')
281 sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n')
283 def SyncRequest(self, req, timeout=10):
284 self._Connect(timeout)
285 self._SetTimeout(timeout)
286 self.SendAndIgnoreResponse(req)
289 res = self._ReceiveJsonData(timeout)
291 self._HandleNotification(res)
293 if 'id' not in res or res['id'] != req['id']:
294 logging.debug('Dropped reply: %s', json.dumps(res))
298 def RegisterDomain(self,
299 domain_name, notification_handler, will_close_handler):
300 """Registers a given domain for handling notification methods.
302 For example, given inspector_backend:
303 def OnConsoleNotification(msg):
304 if msg['method'] == 'Console.messageAdded':
305 print msg['params']['message']
307 def OnConsoleClose(self):
309 inspector_backend.RegisterDomain('Console',
310 OnConsoleNotification, OnConsoleClose)
312 assert domain_name not in self._domain_handlers
313 self._domain_handlers[domain_name] = (notification_handler,
316 def UnregisterDomain(self, domain_name):
317 """Unregisters a previously registered domain."""
318 assert domain_name in self._domain_handlers
319 self._domain_handlers.pop(domain_name)
321 def CollectGarbage(self):
322 self._page.CollectGarbage()
324 def TakeJSHeapSnapshot(self, timeout=120):
327 def OnNotification(res):
328 if res['method'] == 'HeapProfiler.addHeapSnapshotChunk':
329 snapshot.append(res['params']['chunk'])
334 self.RegisterDomain('HeapProfiler', OnNotification, OnClose)
336 self.SyncRequest({'method': 'Page.getResourceTree'}, timeout)
337 self.SyncRequest({'method': 'Debugger.enable'}, timeout)
338 self.SyncRequest({'method': 'HeapProfiler.takeHeapSnapshot'}, timeout)
339 snapshot = ''.join(snapshot)
341 self.UnregisterDomain('HeapProfiler')
342 return model.Model(snapshot)