Index: tools/telemetry/telemetry/tab_backend.py |
diff --git a/tools/telemetry/telemetry/tab_backend.py b/tools/telemetry/telemetry/tab_backend.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..265edd0f5b74803be5e7c3a0ac2ed81fabdfa8ef |
--- /dev/null |
+++ b/tools/telemetry/telemetry/tab_backend.py |
@@ -0,0 +1,279 @@ |
+# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+import json |
+import logging |
+import socket |
+ |
+from telemetry import inspector_console |
+from telemetry import inspector_page |
+from telemetry import inspector_runtime |
+from telemetry import inspector_timeline |
+from telemetry import png_bitmap |
+from telemetry import tab_crash_exception |
+from telemetry import util |
+from telemetry import websocket |
+ |
+class InspectorException(Exception): |
+ pass |
+ |
+class TabBackend(object): |
+ def __init__(self, browser, tab_controller, debugger_url): |
+ self._browser = browser |
+ self._tab_controller = tab_controller |
+ self._debugger_url = debugger_url |
+ self._socket = None |
+ self._next_request_id = 0 |
+ self._domain_handlers = {} |
+ self._cur_socket_timeout = 0 |
+ |
+ self._console = None |
+ self._page = None |
+ self._runtime = None |
+ self._timeline = None |
+ |
+ self._Connect() |
+ |
+ def _Connect(self): |
+ if self._socket: |
+ return |
+ self._socket = websocket.create_connection(self._debugger_url) |
+ |
+ self._console = inspector_console.InspectorConsole(self) |
+ self._page = inspector_page.InspectorPage(self) |
+ self._runtime = inspector_runtime.InspectorRuntime(self) |
+ self._timeline = inspector_timeline.InspectorTimeline(self) |
+ |
+ def _Disconnect(self): |
+ for _, handlers in self._domain_handlers.items(): |
+ _, will_close_handler = handlers |
+ will_close_handler() |
+ self._domain_handlers = {} |
+ |
+ self._socket.close() |
+ self._socket = None |
+ |
+ self._browser = None |
+ self._tab_controller = None |
+ self._debugger_url = None |
+ |
+ self._console = None |
+ self._page = None |
+ self._runtime = None |
+ self._timeline = None |
+ |
+ # General public methods. |
+ |
+ @property |
+ def browser(self): |
+ return self._browser |
+ |
+ @property |
+ def url(self): |
+ self._Disconnect() |
+ return self._tab_controller.GetTabUrl(self._debugger_url) |
+ |
+ def Activate(self): |
+ self._Connect() |
+ self._tab_controller.ActivateTab(self._debugger_url) |
+ |
+ def Close(self): |
+ self._Disconnect() |
+ self._tab_controller.CloseTab(self._debugger_url) |
+ |
+ # Public methods implemented in JavaScript. |
+ |
+ def WaitForDocumentReadyStateToBeComplete(self, timeout): |
+ util.WaitFor( |
+ lambda: self._runtime.Evaluate('document.readyState') == 'complete', |
+ timeout) |
+ |
+ def WaitForDocumentReadyStateToBeInteractiveOrBetter( |
+ self, timeout): |
+ def IsReadyStateInteractiveOrBetter(): |
+ rs = self._runtime.Evaluate('document.readyState') |
+ return rs == 'complete' or rs == 'interactive' |
+ util.WaitFor(IsReadyStateInteractiveOrBetter, timeout) |
+ |
+ @property |
+ def screenshot_supported(self): |
+ if self._runtime.Evaluate( |
+ 'window.chrome.gpuBenchmarking === undefined'): |
+ return False |
+ |
+ if self._runtime.Evaluate( |
+ 'window.chrome.gpuBenchmarking.windowSnapshotPNG === undefined'): |
+ return False |
+ |
+ return True |
+ |
+ def Screenshot(self, timeout): |
+ if self._runtime.Evaluate( |
+ 'window.chrome.gpuBenchmarking === undefined'): |
+ raise Exception("Browser was not started with --enable-gpu-benchmarking") |
+ |
+ if self._runtime.Evaluate( |
+ 'window.chrome.gpuBenchmarking.beginWindowSnapshotPNG === undefined'): |
+ raise Exception("Browser does not support window snapshot API.") |
+ |
+ self._runtime.Evaluate(""" |
+ if(!window.__telemetry) { |
+ window.__telemetry = {} |
+ } |
+ window.__telemetry.snapshotComplete = false; |
+ window.__telemetry.snapshotData = null; |
+ window.chrome.gpuBenchmarking.beginWindowSnapshotPNG( |
+ function(snapshot) { |
+ window.__telemetry.snapshotData = snapshot; |
+ window.__telemetry.snapshotComplete = true; |
+ } |
+ ); |
+ """) |
+ |
+ def IsSnapshotComplete(): |
+ return self._runtime.Evaluate('window.__telemetry.snapshotComplete') |
+ |
+ util.WaitFor(IsSnapshotComplete, timeout) |
+ |
+ snap = self._runtime.Evaluate(""" |
+ (function() { |
+ var data = window.__telemetry.snapshotData; |
+ delete window.__telemetry.snapshotComplete; |
+ delete window.__telemetry.snapshotData; |
+ return data; |
+ })() |
+ """) |
+ if snap: |
+ return png_bitmap.PngBitmap(snap['data']) |
+ return None |
+ |
+ # Console public methods. |
+ |
+ @property |
+ def message_output_stream(self): # pylint: disable=E0202 |
+ return self._console.message_output_stream |
+ |
+ @message_output_stream.setter |
+ def message_output_stream(self, stream): # pylint: disable=E0202 |
+ self._console.message_output_stream = stream |
+ |
+ # Page public methods. |
+ |
+ def PerformActionAndWaitForNavigate(self, action_function, timeout): |
+ self._page.PerformActionAndWaitForNavigate(action_function, timeout) |
+ |
+ def Navigate(self, url, timeout): |
+ self._page.Navigate(url, timeout) |
+ |
+ def GetCookieByName(self, name, timeout): |
+ return self._page.GetCookieByName(name, timeout) |
+ |
+ # Console public methods. |
+ |
+ def Execute(self, expr, timeout): |
+ self._console.Execute(expr, timeout) |
+ |
+ def Evaluate(self, expr, timeout): |
+ return self._console.Evaluate(expr, timeout) |
+ |
+ # Timeline public methods. |
+ |
+ @property |
+ def timeline_events(self): |
+ return self._timeline.timeline_events |
+ |
+ def StartRecording(self): |
+ self._timeline.Start() |
+ |
+ def StopRecording(self): |
+ self._timeline.Stop() |
+ |
+ # Methods used internally by other backends. |
+ |
+ def DispatchNotifications(self, timeout=10): |
+ self._SetTimeout(timeout) |
+ try: |
+ data = self._socket.recv() |
+ except (socket.error, websocket.WebSocketException): |
+ if self._tab_controller.DoesDebuggerUrlExist(self._debugger_url): |
+ return |
+ raise tab_crash_exception.TabCrashException() |
+ |
+ res = json.loads(data) |
+ logging.debug('got [%s]', data) |
+ if 'method' in res: |
+ self._HandleNotification(res) |
+ |
+ def _HandleNotification(self, res): |
+ mname = res['method'] |
+ dot_pos = mname.find('.') |
+ domain_name = mname[:dot_pos] |
+ if domain_name in self._domain_handlers: |
+ try: |
+ self._domain_handlers[domain_name][0](res) |
+ except Exception: |
+ import traceback |
+ traceback.print_exc() |
+ else: |
+ logging.debug('Unhandled inspector message: %s', res) |
+ |
+ def SendAndIgnoreResponse(self, req): |
+ req['id'] = self._next_request_id |
+ self._next_request_id += 1 |
+ data = json.dumps(req) |
+ self._socket.send(data) |
+ logging.debug('sent [%s]', data) |
+ |
+ def _SetTimeout(self, timeout): |
+ if self._cur_socket_timeout != timeout: |
+ self._socket.settimeout(timeout) |
+ self._cur_socket_timeout = timeout |
+ |
+ def SyncRequest(self, req, timeout=10): |
+ # TODO(nduca): Listen to the timeout argument |
+ # pylint: disable=W0613 |
+ self._SetTimeout(timeout) |
+ self.SendAndIgnoreResponse(req) |
+ |
+ while True: |
+ try: |
+ data = self._socket.recv() |
+ except (socket.error, websocket.WebSocketException): |
+ if self._tab_controller.DoesDebuggerUrlExist(self._debugger_url): |
+ raise util.TimeoutException( |
+ 'Timed out waiting for reply. This is unusual.') |
+ raise tab_crash_exception.TabCrashException() |
+ |
+ res = json.loads(data) |
+ logging.debug('got [%s]', data) |
+ if 'method' in res: |
+ self._HandleNotification(res) |
+ continue |
+ |
+ if res['id'] != req['id']: |
+ logging.debug('Dropped reply: %s', json.dumps(res)) |
+ continue |
+ return res |
+ |
+ def RegisterDomain(self, |
+ domain_name, notification_handler, will_close_handler): |
+ """Registers a given domain for handling notification methods. |
+ |
+ For example, given tab_backend: |
+ def OnConsoleNotification(msg): |
+ if msg['method'] == 'Console.messageAdded': |
+ print msg['params']['message'] |
+ return |
+ def OnConsoleClose(self): |
+ pass |
+ tab_backend.RegisterDomain('Console', |
+ OnConsoleNotification, OnConsoleClose) |
+ """ |
+ assert domain_name not in self._domain_handlers |
+ self._domain_handlers[domain_name] = (notification_handler, |
+ will_close_handler) |
+ |
+ def UnregisterDomain(self, domain_name): |
+ """Unregisters a previously registered domain.""" |
+ assert domain_name in self._domain_handlers |
+ self._domain_handlers.pop(domain_name) |