michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD'; michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, 'devtools', function() { michael@0: const {devtools} = Cu.import('resource://gre/modules/devtools/Loader.jsm', {}); michael@0: return devtools; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() { michael@0: return Cu.import('resource://gre/modules/devtools/dbg-client.jsm', {}).DebuggerClient; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() { michael@0: return devtools.require('devtools/toolkit/webconsole/utils').Utils; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() { michael@0: return devtools.require('devtools/server/actors/eventlooplag').EventLoopLagFront; michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() { michael@0: return devtools.require('devtools/server/actors/memory').MemoryFront; michael@0: }); michael@0: michael@0: michael@0: /** michael@0: * The Developer HUD is an on-device developer tool that displays widgets, michael@0: * showing visual debug information about apps. Each widget corresponds to a michael@0: * metric as tracked by a metric watcher (e.g. consoleWatcher). michael@0: */ michael@0: let developerHUD = { michael@0: michael@0: _targets: new Map(), michael@0: _frames: new Map(), michael@0: _client: null, michael@0: _conn: null, michael@0: _watchers: [], michael@0: _logging: true, michael@0: michael@0: /** michael@0: * This method registers a metric watcher that will watch one or more metrics michael@0: * on app frames that are being tracked. A watcher must implement the michael@0: * `trackTarget(target)` and `untrackTarget(target)` methods, register michael@0: * observed metrics with `target.register(metric)`, and keep them up-to-date michael@0: * with `target.update(metric, message)` when necessary. michael@0: */ michael@0: registerWatcher: function dwp_registerWatcher(watcher) { michael@0: this._watchers.unshift(watcher); michael@0: }, michael@0: michael@0: init: function dwp_init() { michael@0: if (this._client) michael@0: return; michael@0: michael@0: if (!DebuggerServer.initialized) { michael@0: RemoteDebugger.start(); michael@0: } michael@0: michael@0: // We instantiate a local debugger connection so that watchers can use our michael@0: // DebuggerClient to send requests to tab actors (e.g. the consoleActor). michael@0: // Note the special usage of the private _serverConnection, which we need michael@0: // to call connectToChild and set up child process actors on a frame we michael@0: // intend to track. These actors will use the connection to communicate with michael@0: // our DebuggerServer in the parent process. michael@0: let transport = DebuggerServer.connectPipe(); michael@0: this._conn = transport._serverConnection; michael@0: this._client = new DebuggerClient(transport); michael@0: michael@0: for (let w of this._watchers) { michael@0: if (w.init) { michael@0: w.init(this._client); michael@0: } michael@0: } michael@0: michael@0: Services.obs.addObserver(this, 'remote-browser-shown', false); michael@0: Services.obs.addObserver(this, 'inprocess-browser-shown', false); michael@0: Services.obs.addObserver(this, 'message-manager-disconnect', false); michael@0: michael@0: let systemapp = document.querySelector('#systemapp'); michael@0: this.trackFrame(systemapp); michael@0: michael@0: let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]'); michael@0: for (let frame of frames) { michael@0: this.trackFrame(frame); michael@0: } michael@0: michael@0: SettingsListener.observe('hud.logging', this._logging, enabled => { michael@0: this._logging = enabled; michael@0: }); michael@0: }, michael@0: michael@0: uninit: function dwp_uninit() { michael@0: if (!this._client) michael@0: return; michael@0: michael@0: for (let frame of this._targets.keys()) { michael@0: this.untrackFrame(frame); michael@0: } michael@0: michael@0: Services.obs.removeObserver(this, 'remote-browser-shown'); michael@0: Services.obs.removeObserver(this, 'inprocess-browser-shown'); michael@0: Services.obs.removeObserver(this, 'message-manager-disconnect'); michael@0: michael@0: this._client.close(); michael@0: delete this._client; michael@0: }, michael@0: michael@0: /** michael@0: * This method will ask all registered watchers to track and update metrics michael@0: * on an app frame. michael@0: */ michael@0: trackFrame: function dwp_trackFrame(frame) { michael@0: if (this._targets.has(frame)) michael@0: return; michael@0: michael@0: DebuggerServer.connectToChild(this._conn, frame).then(actor => { michael@0: let target = new Target(frame, actor); michael@0: this._targets.set(frame, target); michael@0: michael@0: for (let w of this._watchers) { michael@0: w.trackTarget(target); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: untrackFrame: function dwp_untrackFrame(frame) { michael@0: let target = this._targets.get(frame); michael@0: if (target) { michael@0: for (let w of this._watchers) { michael@0: w.untrackTarget(target); michael@0: } michael@0: michael@0: target.destroy(); michael@0: this._targets.delete(frame); michael@0: } michael@0: }, michael@0: michael@0: observe: function dwp_observe(subject, topic, data) { michael@0: if (!this._client) michael@0: return; michael@0: michael@0: let frame; michael@0: michael@0: switch(topic) { michael@0: michael@0: // listen for frame creation in OOP (device) as well as in parent process (b2g desktop) michael@0: case 'remote-browser-shown': michael@0: case 'inprocess-browser-shown': michael@0: let frameLoader = subject; michael@0: // get a ref to the app