| 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..1d61a688ff7dde4899923184cbda2eb006263848
|
| --- /dev/null
|
| +++ b/tools/telemetry/telemetry/tab_backend.py
|
| @@ -0,0 +1,298 @@
|
| +# 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, browser_backend, debugger_url):
|
| + assert debugger_url
|
| + self._browser = browser
|
| + self._browser_backend = browser_backend
|
| + 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
|
| +
|
| + def __del__(self):
|
| + self._Disconnect()
|
| +
|
| + 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 = {}
|
| +
|
| + if self._socket:
|
| + self._socket.close()
|
| + self._socket = None
|
| +
|
| + self.__console = None
|
| + self.__page = None
|
| + self.__runtime = None
|
| + self.__timeline = None
|
| +
|
| + @property
|
| + def _console(self):
|
| + self._Connect()
|
| + return self.__console
|
| +
|
| + @property
|
| + def _page(self):
|
| + self._Connect()
|
| + return self.__page
|
| +
|
| + @property
|
| + def _runtime(self):
|
| + self._Connect()
|
| + return self.__runtime
|
| +
|
| + @property
|
| + def _timeline(self):
|
| + self._Connect()
|
| + return self.__timeline
|
| +
|
| + # General public methods.
|
| +
|
| + @property
|
| + def browser(self):
|
| + return self._browser
|
| +
|
| + @property
|
| + def url(self):
|
| + self._Disconnect()
|
| + return self._browser_backend.tabs.GetTabUrl(self._debugger_url)
|
| +
|
| + def Activate(self):
|
| + self._Connect()
|
| + self._browser_backend.tabs.ActivateTab(self._debugger_url)
|
| +
|
| + def Close(self):
|
| + self._Disconnect()
|
| + self._browser_backend.tabs.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)
|
| +
|
| + # Runtime public methods.
|
| +
|
| + def ExecuteJavascript(self, expr, timeout):
|
| + self.__runtime.Execute(expr, timeout)
|
| +
|
| + def EvaluateJavascript(self, expr, timeout):
|
| + return self.__runtime.Evaluate(expr, timeout)
|
| +
|
| + # Timeline public methods.
|
| +
|
| + @property
|
| + def timeline_events(self):
|
| + return self.__timeline.timeline_events
|
| +
|
| + def StartTimelineRecording(self):
|
| + self.__timeline.Start()
|
| +
|
| + def StopTimelineRecording(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._browser_backend.tabs.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._browser_backend.tabs.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)
|
|
|