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.core.timeline import model as timeline_model
23 from telemetry.core.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(err_msg)
46 elif not self._browser_backend.HasBrowserFinishedLaunching():
47 raise exceptions.BrowserConnectionGoneException(err_msg)
49 raise exceptions.TabCrashException(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.id:
83 return self._context['id']
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 PerformActionAndWaitForNavigate(self, action_function, timeout):
162 self._page.PerformActionAndWaitForNavigate(action_function, 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 # Timeline public methods.
181 def timeline_model(self):
182 return self._timeline_model
184 def StartTimelineRecording(self, options=None):
186 options = recording_options.TimelineRecordingOptions()
187 if options.record_timeline:
188 self._timeline.Start()
189 if options.record_network:
190 self._network.timeline_recorder.Start()
192 def StopTimelineRecording(self):
194 timeline_data = self._timeline.Stop()
196 data.append(timeline_data)
197 network_data = self._network.timeline_recorder.Stop()
199 data.append(network_data)
201 self._timeline_model = timeline_model.TimelineModel(
202 timeline_data=data, shift_world_to_zero=False)
204 self._timeline_model = None
207 def is_timeline_recording_running(self):
208 return self._timeline.is_timeline_recording_running
210 # Network public methods.
212 def ClearCache(self):
213 self._network.ClearCache()
215 # Methods used internally by other backends.
217 def _IsInspectable(self):
218 contexts = self._browser_backend.ListInspectableContexts()
219 return self.id in [c['id'] for c in contexts]
221 def _HandleNotification(self, res):
222 if (res['method'] == 'Inspector.detached' and
223 res.get('params', {}).get('reason','') == 'replaced_with_devtools'):
224 self._WaitForInspectorToGoAwayAndReconnect()
226 if res['method'] == 'Inspector.targetCrashed':
227 raise exceptions.TabCrashException()
229 mname = res['method']
230 dot_pos = mname.find('.')
231 domain_name = mname[:dot_pos]
232 if domain_name in self._domain_handlers:
234 self._domain_handlers[domain_name][0](res)
237 traceback.print_exc()
239 logging.debug('Unhandled inspector message: %s', res)
241 def _HandleError(self, elapsed_time):
242 if self._IsInspectable():
243 raise util.TimeoutException(
244 'Received a socket error in the browser connection and the tab '
245 'still exists, assuming it timed out. '
246 'Elapsed=%ds Error=%s' % (elapsed_time, sys.exc_info()[1]))
247 raise exceptions.TabCrashException(
248 'Received a socket error in the browser connection and the tab no '
249 'longer exists, assuming it crashed. Error=%s' % sys.exc_info()[1])
251 def _WaitForInspectorToGoAwayAndReconnect(self):
252 sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n')
253 sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n')
254 super(InspectorBackend, self).Disconnect()
258 if not self._IsInspectable():
261 self.Connect(self.debugger_url)
262 except exceptions.TabCrashException, ex:
263 if ex.message.message.find('Handshake Status 500') == 0:
267 util.WaitFor(IsBack, 512)
268 sys.stderr.write('\n')
269 sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n')
271 def RegisterDomain(self,
272 domain_name, notification_handler, will_close_handler):
273 """Registers a given domain for handling notification methods.
275 For example, given inspector_backend:
276 def OnConsoleNotification(msg):
277 if msg['method'] == 'Console.messageAdded':
278 print msg['params']['message']
280 def OnConsoleClose(self):
282 inspector_backend.RegisterDomain('Console',
283 OnConsoleNotification, OnConsoleClose)
285 assert domain_name not in self._domain_handlers
286 self._domain_handlers[domain_name] = (notification_handler,
289 def UnregisterDomain(self, domain_name):
290 """Unregisters a previously registered domain."""
291 assert domain_name in self._domain_handlers
292 self._domain_handlers.pop(domain_name)
294 def CollectGarbage(self):
295 self._page.CollectGarbage()
297 def TakeJSHeapSnapshot(self, timeout=120):
300 def OnNotification(res):
301 if res['method'] == 'HeapProfiler.addHeapSnapshotChunk':
302 snapshot.append(res['params']['chunk'])
307 self.RegisterDomain('HeapProfiler', OnNotification, OnClose)
309 self.SyncRequest({'method': 'Page.getResourceTree'}, timeout)
310 self.SyncRequest({'method': 'Debugger.enable'}, timeout)
311 self.SyncRequest({'method': 'HeapProfiler.takeHeapSnapshot'}, timeout)
312 snapshot = ''.join(snapshot)
314 self.UnregisterDomain('HeapProfiler')
315 return model.Model(snapshot)