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.
9 from telemetry import decorators
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 inspector_websocket
20 from telemetry.core.backends.chrome import websocket
21 from telemetry.core.heap import model
22 from telemetry.timeline import model as timeline_model
23 from telemetry.timeline import recording_options
26 class InspectorException(Exception):
30 class InspectorBackend(inspector_websocket.InspectorWebsocket):
31 def __init__(self, browser_backend, context, timeout=60):
32 super(InspectorBackend, self).__init__(self._HandleNotification,
35 self._browser_backend = browser_backend
36 self._context = context
37 self._domain_handlers = {}
39 logging.debug('InspectorBackend._Connect() to %s', self.debugger_url)
41 self.Connect(self.debugger_url)
42 except (websocket.WebSocketException, util.TimeoutException):
43 err_msg = sys.exc_info()[1]
44 if not self._browser_backend.IsBrowserRunning():
45 raise exceptions.BrowserGoneException(self.browser, err_msg)
46 elif not self._browser_backend.HasBrowserFinishedLaunching():
47 raise exceptions.BrowserConnectionGoneException(self.browser, err_msg)
49 raise exceptions.TabCrashException(self.browser, err_msg)
51 self._console = inspector_console.InspectorConsole(self)
52 self._memory = inspector_memory.InspectorMemory(self)
53 self._page = inspector_page.InspectorPage(self, timeout=timeout)
54 self._runtime = inspector_runtime.InspectorRuntime(self)
55 self._timeline = inspector_timeline.InspectorTimeline(self)
56 self._network = inspector_network.InspectorNetwork(self)
57 self._timeline_model = None
63 for _, handlers in self._domain_handlers.items():
64 _, will_close_handler = handlers
66 self._domain_handlers = {}
68 super(InspectorBackend, self).Disconnect()
72 return self._browser_backend.browser
76 for c in self._browser_backend.ListInspectableContexts():
77 if c['id'] == self._context['id']:
83 return self.debugger_url
86 def debugger_url(self):
87 return self._context['webSocketDebuggerUrl']
89 # Public methods implemented in JavaScript.
93 def screenshot_supported(self):
94 if (self.browser.platform.GetOSName() == 'linux' and (
95 os.getenv('DISPLAY') not in [':0', ':0.0'])):
96 # Displays other than 0 mean we are likely running in something like
97 # xvfb where screenshotting doesn't work.
99 return not self.EvaluateJavaScript("""
100 window.chrome.gpuBenchmarking === undefined ||
101 window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined
104 def Screenshot(self, timeout):
105 assert self.screenshot_supported, 'Browser does not support screenshotting'
107 self.EvaluateJavaScript("""
108 if(!window.__telemetry) {
109 window.__telemetry = {}
111 window.__telemetry.snapshotComplete = false;
112 window.__telemetry.snapshotData = null;
113 window.chrome.gpuBenchmarking.beginWindowSnapshotPNG(
115 window.__telemetry.snapshotData = snapshot;
116 window.__telemetry.snapshotComplete = true;
121 def IsSnapshotComplete():
122 return self.EvaluateJavaScript(
123 'window.__telemetry.snapshotComplete')
125 util.WaitFor(IsSnapshotComplete, timeout)
127 snap = self.EvaluateJavaScript("""
129 var data = window.__telemetry.snapshotData;
130 delete window.__telemetry.snapshotComplete;
131 delete window.__telemetry.snapshotData;
136 return bitmap.Bitmap.FromBase64Png(snap['data'])
139 # Console public methods.
142 def message_output_stream(self): # pylint: disable=E0202
143 return self._console.message_output_stream
145 @message_output_stream.setter
146 def message_output_stream(self, stream): # pylint: disable=E0202
147 self._console.message_output_stream = stream
149 # Memory public methods.
151 def GetDOMStats(self, timeout):
152 dom_counters = self._memory.GetDOMCounters(timeout)
154 'document_count': dom_counters['documents'],
155 'node_count': dom_counters['nodes'],
156 'event_listener_count': dom_counters['jsEventListeners']
159 # Page public methods.
161 def WaitForNavigate(self, timeout):
162 self._page.WaitForNavigate(timeout)
164 def Navigate(self, url, script_to_evaluate_on_commit, timeout):
165 self._page.Navigate(url, script_to_evaluate_on_commit, timeout)
167 def GetCookieByName(self, name, timeout):
168 return self._page.GetCookieByName(name, timeout)
170 # Runtime public methods.
172 def ExecuteJavaScript(self, expr, context_id=None, timeout=60):
173 self._runtime.Execute(expr, context_id, timeout)
175 def EvaluateJavaScript(self, expr, context_id=None, timeout=60):
176 return self._runtime.Evaluate(expr, context_id, timeout)
178 def EnableAllContexts(self):
179 return self._runtime.EnableAllContexts()
181 # Timeline public methods.
184 def timeline_model(self):
185 return self._timeline_model
187 def StartTimelineRecording(self, options=None):
189 options = recording_options.TimelineRecordingOptions()
190 if options.record_timeline:
191 self._timeline.Start()
192 if options.record_network:
193 self._network.timeline_recorder.Start()
195 def StopTimelineRecording(self):
197 timeline_data = self._timeline.Stop()
199 data.append(timeline_data)
200 network_data = self._network.timeline_recorder.Stop()
202 data.append(network_data)
204 self._timeline_model = timeline_model.TimelineModel(
205 timeline_data=data, shift_world_to_zero=False)
207 self._timeline_model = None
210 def is_timeline_recording_running(self):
211 return self._timeline.is_timeline_recording_running
213 # Network public methods.
215 def ClearCache(self):
216 self._network.ClearCache()
218 # Methods used internally by other backends.
220 def _IsInspectable(self):
221 contexts = self._browser_backend.ListInspectableContexts()
222 return self._context['id'] in [c['id'] for c in contexts]
224 def _HandleNotification(self, res):
225 if (res['method'] == 'Inspector.detached' and
226 res.get('params', {}).get('reason', '') == 'replaced_with_devtools'):
227 self._WaitForInspectorToGoAwayAndReconnect()
229 if res['method'] == 'Inspector.targetCrashed':
230 raise exceptions.TabCrashException(self.browser)
232 mname = res['method']
233 dot_pos = mname.find('.')
234 domain_name = mname[:dot_pos]
235 if domain_name in self._domain_handlers:
237 self._domain_handlers[domain_name][0](res)
240 traceback.print_exc()
242 logging.warn('Unhandled inspector message: %s', res)
244 def _HandleError(self, elapsed_time):
245 if self._IsInspectable():
246 raise util.TimeoutException(
247 'Received a socket error in the browser connection and the tab '
248 'still exists, assuming it timed out. '
249 'Elapsed=%ds Error=%s' % (elapsed_time, sys.exc_info()[1]))
250 raise exceptions.TabCrashException(self.browser,
251 'Received a socket error in the browser connection and the tab no '
252 'longer exists, assuming it crashed. Error=%s' % sys.exc_info()[1])
254 def _WaitForInspectorToGoAwayAndReconnect(self):
255 sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n')
256 sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n')
257 super(InspectorBackend, self).Disconnect()
261 if not self._IsInspectable():
264 self.Connect(self.debugger_url)
265 except exceptions.TabCrashException, ex:
266 if ex.message.message.find('Handshake Status 500') == 0:
270 util.WaitFor(IsBack, 512)
271 sys.stderr.write('\n')
272 sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n')
274 def RegisterDomain(self,
275 domain_name, notification_handler, will_close_handler):
276 """Registers a given domain for handling notification methods.
278 For example, given inspector_backend:
279 def OnConsoleNotification(msg):
280 if msg['method'] == 'Console.messageAdded':
281 print msg['params']['message']
283 def OnConsoleClose(self):
285 inspector_backend.RegisterDomain('Console',
286 OnConsoleNotification, OnConsoleClose)
288 assert domain_name not in self._domain_handlers
289 self._domain_handlers[domain_name] = (notification_handler,
292 def UnregisterDomain(self, domain_name):
293 """Unregisters a previously registered domain."""
294 assert domain_name in self._domain_handlers
295 self._domain_handlers.pop(domain_name)
297 def CollectGarbage(self):
298 self._page.CollectGarbage()
300 def TakeJSHeapSnapshot(self, timeout=120):
303 def OnNotification(res):
304 if res['method'] == 'HeapProfiler.addHeapSnapshotChunk':
305 snapshot.append(res['params']['chunk'])
310 self.RegisterDomain('HeapProfiler', OnNotification, OnClose)
312 self.SyncRequest({'method': 'Page.getResourceTree'}, timeout)
313 self.SyncRequest({'method': 'Debugger.enable'}, timeout)
314 self.SyncRequest({'method': 'HeapProfiler.takeHeapSnapshot'}, timeout)
315 snapshot = ''.join(snapshot)
317 self.UnregisterDomain('HeapProfiler')
318 return model.Model(snapshot)