OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 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. | |
4 import json | |
5 import logging | |
6 import socket | |
7 import sys | |
8 | |
9 from telemetry import inspector_console | |
10 from telemetry import inspector_page | |
11 from telemetry import inspector_runtime | |
12 from telemetry import inspector_timeline | |
13 from telemetry import png_bitmap | |
14 from telemetry import tab_crash_exception | |
15 from telemetry import util | |
16 from telemetry import websocket | |
17 | |
18 class InspectorException(Exception): | |
19 pass | |
20 | |
21 class InspectorBackend(object): | |
22 def __init__(self, browser, browser_backend, debugger_url): | |
23 assert debugger_url | |
24 self._browser = browser | |
25 self._browser_backend = browser_backend | |
26 self._debugger_url = debugger_url | |
27 self._socket = None | |
28 self._domain_handlers = {} | |
29 self._cur_socket_timeout = 0 | |
30 self._next_request_id = 0 | |
31 | |
32 self._console = inspector_console.InspectorConsole(self) | |
33 self._page = inspector_page.InspectorPage(self) | |
34 self._runtime = inspector_runtime.InspectorRuntime(self) | |
35 self._timeline = inspector_timeline.InspectorTimeline(self) | |
36 | |
37 def __del__(self): | |
38 self.Disconnect() | |
39 | |
40 def _Connect(self): | |
41 if self._socket: | |
42 return | |
43 self._socket = websocket.create_connection(self._debugger_url) | |
44 self._cur_socket_timeout = 0 | |
45 self._next_request_id = 0 | |
46 | |
47 def Disconnect(self): | |
48 for _, handlers in self._domain_handlers.items(): | |
49 _, will_close_handler = handlers | |
50 will_close_handler() | |
51 self._domain_handlers = {} | |
52 | |
53 if self._socket: | |
54 self._socket.close() | |
55 self._socket = None | |
56 | |
57 # General public methods. | |
58 | |
59 @property | |
60 def browser(self): | |
61 return self._browser | |
62 | |
63 @property | |
64 def url(self): | |
65 self.Disconnect() | |
66 return self._browser_backend.tab_list_backend.GetTabUrl(self._debugger_url) | |
67 | |
68 def Activate(self): | |
69 self._Connect() | |
70 self._browser_backend.tab_list_backend.ActivateTab(self._debugger_url) | |
71 | |
72 def Close(self): | |
73 self.Disconnect() | |
74 self._browser_backend.tab_list_backend.CloseTab(self._debugger_url) | |
75 | |
76 # Public methods implemented in JavaScript. | |
77 | |
78 def WaitForDocumentReadyStateToBeComplete(self, timeout): | |
79 util.WaitFor( | |
80 lambda: self._runtime.Evaluate('document.readyState') == 'complete', | |
81 timeout) | |
82 | |
83 def WaitForDocumentReadyStateToBeInteractiveOrBetter( | |
84 self, timeout): | |
85 def IsReadyStateInteractiveOrBetter(): | |
86 rs = self._runtime.Evaluate('document.readyState') | |
87 return rs == 'complete' or rs == 'interactive' | |
88 util.WaitFor(IsReadyStateInteractiveOrBetter, timeout) | |
89 | |
90 @property | |
91 def screenshot_supported(self): | |
92 if self._runtime.Evaluate( | |
93 'window.chrome.gpuBenchmarking === undefined'): | |
94 return False | |
95 | |
96 if self._runtime.Evaluate( | |
97 'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'): | |
98 return False | |
99 | |
100 # TODO(dtu): Also check for Chrome branch number, because of a bug in | |
101 # beginWindowSnapshotPNG in older versions. crbug.com/171592 | |
102 | |
103 return True | |
104 | |
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") | |
109 | |
110 if self._runtime.Evaluate( | |
111 'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'): | |
112 raise Exception("Browser does not support window snapshot API.") | |
113 | |
114 self._runtime.Evaluate(""" | |
115 if(!window.__telemetry) { | |
116 window.__telemetry = {} | |
117 } | |
118 window.__telemetry.snapshotComplete = false; | |
119 window.__telemetry.snapshotData = null; | |
120 window.chrome.gpuBenchmarking.beginWindowSnapshotPNG( | |
121 function(snapshot) { | |
122 window.__telemetry.snapshotData = snapshot; | |
123 window.__telemetry.snapshotComplete = true; | |
124 } | |
125 ); | |
126 """) | |
127 | |
128 def IsSnapshotComplete(): | |
129 return self._runtime.Evaluate('window.__telemetry.snapshotComplete') | |
130 | |
131 util.WaitFor(IsSnapshotComplete, timeout) | |
132 | |
133 snap = self._runtime.Evaluate(""" | |
134 (function() { | |
135 var data = window.__telemetry.snapshotData; | |
136 delete window.__telemetry.snapshotComplete; | |
137 delete window.__telemetry.snapshotData; | |
138 return data; | |
139 })() | |
140 """) | |
141 if snap: | |
142 return png_bitmap.PngBitmap(snap['data']) | |
143 return None | |
144 | |
145 # Console public methods. | |
146 | |
147 @property | |
148 def message_output_stream(self): # pylint: disable=E0202 | |
149 return self._console.message_output_stream | |
150 | |
151 @message_output_stream.setter | |
152 def message_output_stream(self, stream): # pylint: disable=E0202 | |
153 self._console.message_output_stream = stream | |
154 | |
155 # Page public methods. | |
156 | |
157 def PerformActionAndWaitForNavigate(self, action_function, timeout): | |
158 self._page.PerformActionAndWaitForNavigate(action_function, timeout) | |
159 | |
160 def Navigate(self, url, timeout): | |
161 self._page.Navigate(url, timeout) | |
162 | |
163 def GetCookieByName(self, name, timeout): | |
164 return self._page.GetCookieByName(name, timeout) | |
165 | |
166 # Runtime public methods. | |
167 | |
168 def ExecuteJavaScript(self, expr, timeout): | |
169 self._runtime.Execute(expr, timeout) | |
170 | |
171 def EvaluateJavaScript(self, expr, timeout): | |
172 return self._runtime.Evaluate(expr, timeout) | |
173 | |
174 # Timeline public methods. | |
175 | |
176 @property | |
177 def timeline_model(self): | |
178 return self._timeline.timeline_model | |
179 | |
180 def StartTimelineRecording(self): | |
181 self._timeline.Start() | |
182 | |
183 def StopTimelineRecording(self): | |
184 self._timeline.Stop() | |
185 | |
186 # Methods used internally by other backends. | |
187 | |
188 def DispatchNotifications(self, timeout=10): | |
189 self._Connect() | |
190 self._SetTimeout(timeout) | |
191 | |
192 try: | |
193 data = self._socket.recv() | |
194 except (socket.error, websocket.WebSocketException): | |
195 if self._browser_backend.tab_list_backend.DoesDebuggerUrlExist( | |
196 self._debugger_url): | |
197 return | |
198 raise tab_crash_exception.TabCrashException() | |
199 | |
200 res = json.loads(data) | |
201 logging.debug('got [%s]', data) | |
202 if 'method' in res: | |
203 self._HandleNotification(res) | |
204 | |
205 def _HandleNotification(self, res): | |
206 if (res['method'] == 'Inspector.detached' and | |
207 res.get('params', {}).get('reason','') == 'replaced_with_devtools'): | |
208 self._WaitForInspectorToGoAwayAndReconnect() | |
209 return | |
210 | |
211 mname = res['method'] | |
212 dot_pos = mname.find('.') | |
213 domain_name = mname[:dot_pos] | |
214 if domain_name in self._domain_handlers: | |
215 try: | |
216 self._domain_handlers[domain_name][0](res) | |
217 except Exception: | |
218 import traceback | |
219 traceback.print_exc() | |
220 else: | |
221 logging.debug('Unhandled inspector message: %s', res) | |
222 | |
223 def SendAndIgnoreResponse(self, req): | |
224 self._Connect() | |
225 req['id'] = self._next_request_id | |
226 self._next_request_id += 1 | |
227 data = json.dumps(req) | |
228 self._socket.send(data) | |
229 logging.debug('sent [%s]', data) | |
230 | |
231 def _SetTimeout(self, timeout): | |
232 if self._cur_socket_timeout != timeout: | |
233 self._socket.settimeout(timeout) | |
234 self._cur_socket_timeout = timeout | |
235 | |
236 def _WaitForInspectorToGoAwayAndReconnect(self): | |
237 sys.stderr.write('The connection to Chrome was lost to the Inspector UI.\n') | |
238 sys.stderr.write('Telemetry is waiting for the inspector to be closed...\n') | |
239 self._socket.close() | |
240 self._socket = None | |
241 def IsBack(): | |
242 return self._browser_backend.tab_list_backend.DoesDebuggerUrlExist( | |
243 self._debugger_url) | |
244 util.WaitFor(IsBack, 512, 0.5) | |
245 sys.stderr.write('\n') | |
246 sys.stderr.write('Inspector\'s UI closed. Telemetry will now resume.\n') | |
247 self._Connect() | |
248 | |
249 def SyncRequest(self, req, timeout=10): | |
250 self._Connect() | |
251 # TODO(nduca): Listen to the timeout argument | |
252 # pylint: disable=W0613 | |
253 self._SetTimeout(timeout) | |
254 self.SendAndIgnoreResponse(req) | |
255 | |
256 while True: | |
257 try: | |
258 data = self._socket.recv() | |
259 except (socket.error, websocket.WebSocketException): | |
260 if self._browser_backend.tab_list_backend.DoesDebuggerUrlExist( | |
261 self._debugger_url): | |
262 raise util.TimeoutException( | |
263 'Timed out waiting for reply. This is unusual.') | |
264 raise tab_crash_exception.TabCrashException() | |
265 | |
266 res = json.loads(data) | |
267 logging.debug('got [%s]', data) | |
268 if 'method' in res: | |
269 self._HandleNotification(res) | |
270 continue | |
271 | |
272 if res['id'] != req['id']: | |
273 logging.debug('Dropped reply: %s', json.dumps(res)) | |
274 continue | |
275 return res | |
276 | |
277 def RegisterDomain(self, | |
278 domain_name, notification_handler, will_close_handler): | |
279 """Registers a given domain for handling notification methods. | |
280 | |
281 For example, given inspector_backend: | |
282 def OnConsoleNotification(msg): | |
283 if msg['method'] == 'Console.messageAdded': | |
284 print msg['params']['message'] | |
285 return | |
286 def OnConsoleClose(self): | |
287 pass | |
288 inspector_backend.RegisterDomain('Console', | |
289 OnConsoleNotification, OnConsoleClose) | |
290 """ | |
291 assert domain_name not in self._domain_handlers | |
292 self._domain_handlers[domain_name] = (notification_handler, | |
293 will_close_handler) | |
294 | |
295 def UnregisterDomain(self, domain_name): | |
296 """Unregisters a previously registered domain.""" | |
297 assert domain_name in self._domain_handlers | |
298 self._domain_handlers.pop(domain_name) | |
OLD | NEW |