diff options
Diffstat (limited to 'b2g/chrome/content/devtools')
-rw-r--r-- | b2g/chrome/content/devtools/adb.js | 233 | ||||
-rw-r--r-- | b2g/chrome/content/devtools/debugger.js | 397 | ||||
-rw-r--r-- | b2g/chrome/content/devtools/hud.js | 1017 |
3 files changed, 0 insertions, 1647 deletions
diff --git a/b2g/chrome/content/devtools/adb.js b/b2g/chrome/content/devtools/adb.js deleted file mode 100644 index cebc6696b..000000000 --- a/b2g/chrome/content/devtools/adb.js +++ /dev/null @@ -1,233 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- / -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -// This file is only loaded on Gonk to manage ADB state - -Components.utils.import("resource://gre/modules/FileUtils.jsm"); - -const DEBUG = false; -var debug = function(str) { - dump("AdbController: " + str + "\n"); -} - -var AdbController = { - locked: undefined, - remoteDebuggerEnabled: undefined, - lockEnabled: undefined, - disableAdbTimer: null, - disableAdbTimeoutHours: 12, - umsActive: false, - - setLockscreenEnabled: function(value) { - this.lockEnabled = value; - DEBUG && debug("setLockscreenEnabled = " + this.lockEnabled); - this.updateState(); - }, - - setLockscreenState: function(value) { - this.locked = value; - DEBUG && debug("setLockscreenState = " + this.locked); - this.updateState(); - }, - - setRemoteDebuggerState: function(value) { - this.remoteDebuggerEnabled = value; - DEBUG && debug("setRemoteDebuggerState = " + this.remoteDebuggerEnabled); - this.updateState(); - }, - - startDisableAdbTimer: function() { - if (this.disableAdbTimer) { - this.disableAdbTimer.cancel(); - } else { - this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - try { - this.disableAdbTimeoutHours = - Services.prefs.getIntPref("b2g.adb.timeout-hours"); - } catch (e) { - // This happens if the pref doesn't exist, in which case - // disableAdbTimeoutHours will still be set to the default. - } - } - if (this.disableAdbTimeoutHours <= 0) { - DEBUG && debug("Timer to disable ADB not started due to zero timeout"); - return; - } - - DEBUG && debug("Starting timer to disable ADB in " + - this.disableAdbTimeoutHours + " hours"); - let timeoutMilliseconds = this.disableAdbTimeoutHours * 60 * 60 * 1000; - this.disableAdbTimer.initWithCallback(this, timeoutMilliseconds, - Ci.nsITimer.TYPE_ONE_SHOT); - }, - - stopDisableAdbTimer: function() { - DEBUG && debug("Stopping timer to disable ADB"); - if (this.disableAdbTimer) { - this.disableAdbTimer.cancel(); - this.disableAdbTimer = null; - } - }, - - notify: function(aTimer) { - if (aTimer == this.disableAdbTimer) { - this.disableAdbTimer = null; - // The following dump will be the last thing that shows up in logcat, - // and will at least give the user a clue about why logcat was - // disconnected, if the user happens to be using logcat. - debug("ADB timer expired - disabling ADB\n"); - navigator.mozSettings.createLock().set( - {'debugger.remote-mode': 'disabled'}); - } - }, - - updateState: function() { - this.umsActive = false; - }, - - updateStateInternal: function() { - DEBUG && debug("updateStateInternal: called"); - - if (this.remoteDebuggerEnabled === undefined || - this.lockEnabled === undefined || - this.locked === undefined) { - // Part of initializing the settings database will cause the observers - // to trigger. We want to wait until both have been initialized before - // we start changing ther adb state. Without this then we can wind up - // toggling adb off and back on again (or on and back off again). - // - // For completeness, one scenario which toggles adb is using the unagi. - // The unagi has adb enabled by default (prior to b2g starting). If you - // have the phone lock disabled and remote debugging enabled, then we'll - // receive an unlock event and an rde event. However at the time we - // receive the unlock event we haven't yet received the rde event, so - // we turn adb off momentarily, which disconnects a logcat that might - // be running. Changing the defaults (in AdbController) just moves the - // problem to a different phone, which has adb disabled by default and - // we wind up turning on adb for a short period when we shouldn't. - // - // By waiting until both values are properly initialized, we avoid - // turning adb on or off accidentally. - DEBUG && debug("updateState: Waiting for all vars to be initialized"); - return; - } - - // Check if we have a remote debugging session going on. If so, we won't - // disable adb even if the screen is locked. - let isDebugging = USBRemoteDebugger.isDebugging; - DEBUG && debug("isDebugging=" + isDebugging); - - // If USB Mass Storage, USB tethering, or a debug session is active, - // then we don't want to disable adb in an automatic fashion (i.e. - // when the screen locks or due to timeout). - let sysUsbConfig = libcutils.property_get("sys.usb.config").split(","); - let usbFuncActive = this.umsActive || isDebugging; - usbFuncActive |= (sysUsbConfig.indexOf("rndis") >= 0); - usbFuncActive |= (sysUsbConfig.indexOf("mtp") >= 0); - - let enableAdb = this.remoteDebuggerEnabled && - (!(this.lockEnabled && this.locked) || usbFuncActive); - - let useDisableAdbTimer = true; - try { - if (Services.prefs.getBoolPref("marionette.defaultPrefs.enabled")) { - // Marionette is enabled. Marionette requires that adb be on (and also - // requires that remote debugging be off). The fact that marionette - // is enabled also implies that we're doing a non-production build, so - // we want adb enabled all of the time. - enableAdb = true; - useDisableAdbTimer = false; - } - } catch (e) { - // This means that the pref doesn't exist. Which is fine. We just leave - // enableAdb alone. - } - - // Check wakelock to prevent adb from disconnecting when phone is locked - let lockFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); - lockFile.initWithPath('/sys/power/wake_lock'); - if(lockFile.exists()) { - let foStream = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsIFileInputStream); - let coStream = Cc["@mozilla.org/intl/converter-input-stream;1"] - .createInstance(Ci.nsIConverterInputStream); - let str = {}; - foStream.init(lockFile, FileUtils.MODE_RDONLY, 0, 0); - coStream.init(foStream, "UTF-8", 0, 0); - coStream.readString(-1, str); - coStream.close(); - foStream.close(); - let wakeLockContents = str.value.replace(/\n/, ""); - let wakeLockList = wakeLockContents.split(" "); - if (wakeLockList.indexOf("adb") >= 0) { - enableAdb = true; - useDisableAdbTimer = false; - DEBUG && debug("Keeping ADB enabled as ADB wakelock is present."); - } else { - DEBUG && debug("ADB wakelock not found."); - } - } else { - DEBUG && debug("Wake_lock file not found."); - } - - DEBUG && debug("updateState: enableAdb = " + enableAdb + - " remoteDebuggerEnabled = " + this.remoteDebuggerEnabled + - " lockEnabled = " + this.lockEnabled + - " locked = " + this.locked + - " usbFuncActive = " + usbFuncActive); - - // Configure adb. - let currentConfig = libcutils.property_get("persist.sys.usb.config"); - let configFuncs = currentConfig.split(","); - if (currentConfig == "" || currentConfig == "none") { - // We want to treat none like the empty string. - // "".split(",") yields [""] and not [] - configFuncs = []; - } - let adbIndex = configFuncs.indexOf("adb"); - - if (enableAdb) { - // Add adb to the list of functions, if not already present - if (adbIndex < 0) { - configFuncs.push("adb"); - } - } else { - // Remove adb from the list of functions, if present - if (adbIndex >= 0) { - configFuncs.splice(adbIndex, 1); - } - } - let newConfig = configFuncs.join(","); - if (newConfig == "") { - // Convert the empty string back into none, since that's what init.rc - // needs. - newConfig = "none"; - } - if (newConfig != currentConfig) { - DEBUG && debug("updateState: currentConfig = " + currentConfig); - DEBUG && debug("updateState: newConfig = " + newConfig); - try { - libcutils.property_set("persist.sys.usb.config", newConfig); - } catch(e) { - Cu.reportError("Error configuring adb: " + e); - } - } - if (useDisableAdbTimer) { - if (enableAdb && !usbFuncActive) { - this.startDisableAdbTimer(); - } else { - this.stopDisableAdbTimer(); - } - } - } -}; - -SettingsListener.observe("lockscreen.locked", false, - AdbController.setLockscreenState.bind(AdbController)); -SettingsListener.observe("lockscreen.enabled", false, - AdbController.setLockscreenEnabled.bind(AdbController)); diff --git a/b2g/chrome/content/devtools/debugger.js b/b2g/chrome/content/devtools/debugger.js deleted file mode 100644 index 11987a839..000000000 --- a/b2g/chrome/content/devtools/debugger.js +++ /dev/null @@ -1,397 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- / -/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -XPCOMUtils.defineLazyGetter(this, "devtools", function() { - const { devtools } = - Cu.import("resource://devtools/shared/Loader.jsm", {}); - return devtools; -}); - -XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function() { - const { DebuggerServer } = devtools.require("devtools/server/main"); - return DebuggerServer; -}); - -XPCOMUtils.defineLazyGetter(this, "B2GTabList", function() { - const { B2GTabList } = - devtools.require("resource://gre/modules/DebuggerActors.js"); - return B2GTabList; -}); - -// Load the discovery module eagerly, so that it can set a device name at -// startup. This does not cause discovery to start listening for packets, as -// that only happens once DevTools is enabled. -devtools.require("devtools/shared/discovery/discovery"); - -var RemoteDebugger = { - _listening: false, - - /** - * Prompt the user to accept or decline the incoming connection. - * - * @param session object - * The session object will contain at least the following fields: - * { - * authentication, - * client: { - * host, - * port - * }, - * server: { - * host, - * port - * } - * } - * Specific authentication modes may include additional fields. Check - * the different |allowConnection| methods in - * devtools/shared/security/auth.js. - * @return An AuthenticationResult value. - * A promise that will be resolved to the above is also allowed. - */ - allowConnection(session) { - if (this._promptingForAllow) { - // Don't stack connection prompts if one is already open - return DebuggerServer.AuthenticationResult.DENY; - } - this._listen(); - - this._promptingForAllow = new Promise(resolve => { - this._handleAllowResult = detail => { - this._handleAllowResult = null; - this._promptingForAllow = null; - // Newer Gaia supplies |authResult|, which is one of the - // AuthenticationResult values. - if (detail.authResult) { - resolve(detail.authResult); - } else if (detail.value) { - resolve(DebuggerServer.AuthenticationResult.ALLOW); - } else { - resolve(DebuggerServer.AuthenticationResult.DENY); - } - }; - - shell.sendChromeEvent({ - type: "remote-debugger-prompt", - session - }); - }); - - return this._promptingForAllow; - }, - - /** - * During OOB_CERT authentication, the user must transfer some data through some - * out of band mechanism from the client to the server to authenticate the - * devices. - * - * This implementation instructs Gaia to continually capture images which are - * passed back here and run through a QR decoder. - * - * @return An object containing: - * * sha256: hash(ClientCert) - * * k : K(random 128-bit number) - * A promise that will be resolved to the above is also allowed. - */ - receiveOOB() { - if (this._receivingOOB) { - return this._receivingOOB; - } - this._listen(); - - const QR = devtools.require("devtools/shared/qrcode/index"); - this._receivingOOB = new Promise((resolve, reject) => { - this._handleAuthEvent = detail => { - debug(detail.action); - if (detail.action === "abort") { - this._handleAuthEvent = null; - this._receivingOOB = null; - reject(); - return; - } - - if (detail.action !== "capture") { - return; - } - - let url = detail.url; - QR.decodeFromURI(url).then(data => { - debug("Got auth data: " + data); - let oob = JSON.parse(data); - - shell.sendChromeEvent({ - type: "devtools-auth", - action: "stop" - }); - - this._handleAuthEvent = null; - this._receivingOOB = null; - resolve(oob); - }).catch(() => { - debug("No auth data, requesting new capture"); - shell.sendChromeEvent({ - type: "devtools-auth", - action: "capture" - }); - }); - }; - - // Show QR scanning dialog, get an initial capture - shell.sendChromeEvent({ - type: "devtools-auth", - action: "start" - }); - }); - - return this._receivingOOB; - }, - - _listen: function() { - if (this._listening) { - return; - } - - this.handleEvent = this.handleEvent.bind(this); - let content = shell.contentBrowser.contentWindow; - content.addEventListener("mozContentEvent", this, false, true); - this._listening = true; - }, - - handleEvent: function(event) { - let detail = event.detail; - if (detail.type === "remote-debugger-prompt" && this._handleAllowResult) { - this._handleAllowResult(detail); - } - if (detail.type === "devtools-auth" && this._handleAuthEvent) { - this._handleAuthEvent(detail); - } - }, - - initServer: function() { - if (DebuggerServer.initialized) { - return; - } - - // Ask for remote connections. - DebuggerServer.init(); - - // /!\ Be careful when adding a new actor, especially global actors. - // Any new global actor will be exposed and returned by the root actor. - - // Add Firefox-specific actors, but prevent tab actors to be loaded in - // the parent process, unless we enable certified apps debugging. - let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps"); - DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges); - - // Allow debugging of chrome for any process - if (!restrictPrivileges) { - DebuggerServer.allowChromeProcess = true; - } - - /** - * Construct a root actor appropriate for use in a server running in B2G. - * The returned root actor respects the factories registered with - * DebuggerServer.addGlobalActor only if certified apps debugging is on, - * otherwise we used an explicit limited list of global actors - * - * * @param connection DebuggerServerConnection - * The conection to the client. - */ - DebuggerServer.createRootActor = function createRootActor(connection) - { - let parameters = { - tabList: new B2GTabList(connection), - // Use an explicit global actor list to prevent exposing - // unexpected actors - globalActorFactories: restrictPrivileges ? { - webappsActor: DebuggerServer.globalActorFactories.webappsActor, - deviceActor: DebuggerServer.globalActorFactories.deviceActor, - settingsActor: DebuggerServer.globalActorFactories.settingsActor - } : DebuggerServer.globalActorFactories - }; - let { RootActor } = devtools.require("devtools/server/actors/root"); - let root = new RootActor(connection, parameters); - root.applicationType = "operating-system"; - return root; - }; - - if (isGonk) { - DebuggerServer.on("connectionchange", function() { - AdbController.updateState(); - }); - } - } -}; - -RemoteDebugger.allowConnection = - RemoteDebugger.allowConnection.bind(RemoteDebugger); -RemoteDebugger.receiveOOB = - RemoteDebugger.receiveOOB.bind(RemoteDebugger); - -var USBRemoteDebugger = { - - get isDebugging() { - if (!this._listener) { - return false; - } - - return DebuggerServer._connections && - Object.keys(DebuggerServer._connections).length > 0; - }, - - start: function() { - if (this._listener) { - return; - } - - RemoteDebugger.initServer(); - - let portOrPath = - Services.prefs.getCharPref("devtools.debugger.unix-domain-socket") || - "/data/local/debugger-socket"; - - try { - debug("Starting USB debugger on " + portOrPath); - let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT"); - let authenticator = new AuthenticatorType.Server(); - authenticator.allowConnection = RemoteDebugger.allowConnection; - this._listener = DebuggerServer.createListener(); - this._listener.portOrPath = portOrPath; - this._listener.authenticator = authenticator; - this._listener.open(); - // Temporary event, until bug 942756 lands and offers a way to know - // when the server is up and running. - Services.obs.notifyObservers(null, "debugger-server-started", null); - } catch (e) { - debug("Unable to start USB debugger server: " + e); - } - }, - - stop: function() { - if (!this._listener) { - return; - } - - try { - this._listener.close(); - this._listener = null; - } catch (e) { - debug("Unable to stop USB debugger server: " + e); - } - } - -}; - -var WiFiRemoteDebugger = { - - start: function() { - if (this._listener) { - return; - } - - RemoteDebugger.initServer(); - - try { - debug("Starting WiFi debugger"); - let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT"); - let authenticator = new AuthenticatorType.Server(); - authenticator.allowConnection = RemoteDebugger.allowConnection; - authenticator.receiveOOB = RemoteDebugger.receiveOOB; - this._listener = DebuggerServer.createListener(); - this._listener.portOrPath = -1 /* any available port */; - this._listener.authenticator = authenticator; - this._listener.discoverable = true; - this._listener.encryption = true; - this._listener.open(); - let port = this._listener.port; - debug("Started WiFi debugger on " + port); - } catch (e) { - debug("Unable to start WiFi debugger server: " + e); - } - }, - - stop: function() { - if (!this._listener) { - return; - } - - try { - this._listener.close(); - this._listener = null; - } catch (e) { - debug("Unable to stop WiFi debugger server: " + e); - } - } - -}; - -(function() { - // Track these separately here so we can determine the correct value for the - // pref "devtools.debugger.remote-enabled", which is true when either mode of - // using DevTools is enabled. - let devtoolsUSB = false; - let devtoolsWiFi = false; - - // Keep the old setting to not break people that won't have updated - // gaia and gecko. - SettingsListener.observe("devtools.debugger.remote-enabled", false, - function(value) { - devtoolsUSB = value; - Services.prefs.setBoolPref("devtools.debugger.remote-enabled", - devtoolsUSB || devtoolsWiFi); - // This preference is consulted during startup - Services.prefs.savePrefFile(null); - try { - value ? USBRemoteDebugger.start() : USBRemoteDebugger.stop(); - } catch(e) { - dump("Error while initializing USB devtools: " + - e + "\n" + e.stack + "\n"); - } - }); - - SettingsListener.observe("debugger.remote-mode", "disabled", function(value) { - if (["disabled", "adb-only", "adb-devtools"].indexOf(value) == -1) { - dump("Illegal value for debugger.remote-mode: " + value + "\n"); - return; - } - - devtoolsUSB = value == "adb-devtools"; - Services.prefs.setBoolPref("devtools.debugger.remote-enabled", - devtoolsUSB || devtoolsWiFi); - // This preference is consulted during startup - Services.prefs.savePrefFile(null); - - try { - (value == "adb-devtools") ? USBRemoteDebugger.start() - : USBRemoteDebugger.stop(); - } catch(e) { - dump("Error while initializing USB devtools: " + - e + "\n" + e.stack + "\n"); - } - - isGonk && AdbController.setRemoteDebuggerState(value != "disabled"); - }); - - SettingsListener.observe("devtools.remote.wifi.enabled", false, - function(value) { - devtoolsWiFi = value; - Services.prefs.setBoolPref("devtools.debugger.remote-enabled", - devtoolsUSB || devtoolsWiFi); - // Allow remote debugging on non-local interfaces when WiFi debug is enabled - // TODO: Bug 1034411: Lock down to WiFi interface, instead of all interfaces - Services.prefs.setBoolPref("devtools.debugger.force-local", !value); - // This preference is consulted during startup - Services.prefs.savePrefFile(null); - - try { - value ? WiFiRemoteDebugger.start() : WiFiRemoteDebugger.stop(); - } catch(e) { - dump("Error while initializing WiFi devtools: " + - e + "\n" + e.stack + "\n"); - } - }); -})(); diff --git a/b2g/chrome/content/devtools/hud.js b/b2g/chrome/content/devtools/hud.js deleted file mode 100644 index 64e9d553d..000000000 --- a/b2g/chrome/content/devtools/hud.js +++ /dev/null @@ -1,1017 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -// settings.js loads this file when the HUD setting is enabled. - -const DEVELOPER_HUD_LOG_PREFIX = 'DeveloperHUD'; -const CUSTOM_HISTOGRAM_PREFIX = 'DEVTOOLS_HUD_CUSTOM_'; -const APPNAME_IDX = 3; -const HISTNAME_IDX = 4; - -XPCOMUtils.defineLazyGetter(this, 'devtools', function() { - const {devtools} = Cu.import('resource://devtools/shared/Loader.jsm', {}); - return devtools; -}); - -XPCOMUtils.defineLazyGetter(this, 'DebuggerClient', function() { - return devtools.require('devtools/shared/client/main').DebuggerClient; -}); - -XPCOMUtils.defineLazyGetter(this, 'WebConsoleUtils', function() { - return devtools.require('devtools/shared/webconsole/utils').Utils; -}); - -XPCOMUtils.defineLazyGetter(this, 'EventLoopLagFront', function() { - return devtools.require('devtools/shared/fronts/eventlooplag').EventLoopLagFront; -}); - -XPCOMUtils.defineLazyGetter(this, 'PerformanceEntriesFront', function() { - return devtools.require('devtools/server/actors/performance-entries').PerformanceEntriesFront; -}); - -XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() { - return devtools.require('devtools/server/actors/memory').MemoryFront; -}); - -Cu.import('resource://gre/modules/Frames.jsm'); - -var _telemetryDebug = false; - -function telemetryDebug(...args) { - if (_telemetryDebug) { - args.unshift('[AdvancedTelemetry]'); - console.log(...args); - } -} - -/** - * The Developer HUD is an on-device developer tool that displays widgets, - * showing visual debug information about apps. Each widget corresponds to a - * metric as tracked by a metric watcher (e.g. consoleWatcher). - */ -var developerHUD = { - - _targets: new Map(), - _histograms: new Set(), - _customHistograms: new Set(), - _client: null, - _conn: null, - _watchers: [], - _logging: true, - _telemetry: false, - - /** - * This method registers a metric watcher that will watch one or more metrics - * on app frames that are being tracked. A watcher must implement the - * `trackTarget(target)` and `untrackTarget(target)` methods, register - * observed metrics with `target.register(metric)`, and keep them up-to-date - * with `target.update(metric, message)` when necessary. - */ - registerWatcher(watcher) { - this._watchers.unshift(watcher); - }, - - init() { - if (this._client) { - return; - } - - if (!DebuggerServer.initialized) { - RemoteDebugger.initServer(); - } - - // We instantiate a local debugger connection so that watchers can use our - // DebuggerClient to send requests to tab actors (e.g. the consoleActor). - // Note the special usage of the private _serverConnection, which we need - // to call connectToChild and set up child process actors on a frame we - // intend to track. These actors will use the connection to communicate with - // our DebuggerServer in the parent process. - let transport = DebuggerServer.connectPipe(); - this._conn = transport._serverConnection; - this._client = new DebuggerClient(transport); - - for (let w of this._watchers) { - if (w.init) { - w.init(this._client); - } - } - - Frames.addObserver(this); - - let appFrames = Frames.list().filter(frame => frame.getAttribute('mozapp')); - for (let frame of appFrames) { - this.trackFrame(frame); - } - - SettingsListener.observe('hud.logging', this._logging, enabled => { - this._logging = enabled; - }); - - SettingsListener.observe('hud.telemetry.logging', _telemetryDebug, enabled => { - _telemetryDebug = enabled; - }); - - SettingsListener.observe('metrics.selectedMetrics.level', "", level => { - this._telemetry = (level === 'Enhanced'); - }); - }, - - uninit() { - if (!this._client) { - return; - } - - for (let frame of this._targets.keys()) { - this.untrackFrame(frame); - } - - Frames.removeObserver(this); - - this._client.close(); - delete this._client; - }, - - /** - * This method will ask all registered watchers to track and update metrics - * on an app frame. - */ - trackFrame(frame) { - if (this._targets.has(frame)) { - return; - } - - DebuggerServer.connectToChild(this._conn, frame).then(actor => { - let target = new Target(frame, actor); - this._targets.set(frame, target); - - for (let w of this._watchers) { - w.trackTarget(target); - } - }); - }, - - untrackFrame(frame) { - let target = this._targets.get(frame); - if (target) { - for (let w of this._watchers) { - w.untrackTarget(target); - } - - target.destroy(); - this._targets.delete(frame); - } - }, - - onFrameCreated(frame, isFirstAppFrame) { - let mozapp = frame.getAttribute('mozapp'); - if (!mozapp) { - return; - } - this.trackFrame(frame); - }, - - onFrameDestroyed(frame, isLastAppFrame) { - let mozapp = frame.getAttribute('mozapp'); - if (!mozapp) { - return; - } - this.untrackFrame(frame); - }, - - log(message) { - if (this._logging) { - dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n'); - } - } - -}; - - -/** - * A Target object represents all there is to know about a Firefox OS app frame - * that is being tracked, e.g. a pointer to the frame, current values of watched - * metrics, and how to notify the front-end when metrics have changed. - */ -function Target(frame, actor) { - this.frame = frame; - this.actor = actor; - this.metrics = new Map(); - this._appName = null; -} - -Target.prototype = { - - get manifest() { - return this.frame.appManifestURL; - }, - - get appName() { - - if (this._appName) { - return this._appName; - } - - let manifest = this.manifest; - if (!manifest) { - let msg = DEVELOPER_HUD_LOG_PREFIX + ': Unable to determine app for telemetry metric. src: ' + - this.frame.src; - console.error(msg); - return null; - } - - // "communications" apps are a special case - if (manifest.indexOf('communications') === -1) { - let start = manifest.indexOf('/') + 2; - let end = manifest.indexOf('.', start); - this._appName = manifest.substring(start, end).toLowerCase(); - } else { - let src = this.frame.src; - if (src) { - // e.g., `app://communications.gaiamobile.org/contacts/index.html` - let parts = src.split('/'); - let APP = 3; - let EXPECTED_PARTS_LENGTH = 5; - if (parts.length === EXPECTED_PARTS_LENGTH) { - this._appName = parts[APP]; - } - } - } - - return this._appName; - }, - - /** - * Register a metric that can later be updated. Does not update the front-end. - */ - register(metric) { - this.metrics.set(metric, 0); - }, - - /** - * Modify one of a target's metrics, and send out an event to notify relevant - * parties (e.g. the developer HUD, automated tests, etc). - */ - update(metric, message) { - if (!metric.name) { - throw new Error('Missing metric.name'); - } - - if (!metric.value) { - metric.value = 0; - } - - let metrics = this.metrics; - if (metrics) { - metrics.set(metric.name, metric.value); - } - - let data = { - metrics: [], // FIXME(Bug 982066) Remove this field. - manifest: this.manifest, - metric: metric, - message: message - }; - - // FIXME(Bug 982066) Remove this loop. - if (metrics && metrics.size > 0) { - for (let name of metrics.keys()) { - data.metrics.push({name: name, value: metrics.get(name)}); - } - } - - if (message) { - developerHUD.log('[' + data.manifest + '] ' + data.message); - } - - this._send(data); - }, - - /** - * Nicer way to call update() when the metric value is a number that needs - * to be incremented. - */ - bump(metric, message) { - metric.value = (this.metrics.get(metric.name) || 0) + 1; - this.update(metric, message); - }, - - /** - * Void a metric value and make sure it isn't displayed on the front-end - * anymore. - */ - clear(metric) { - metric.value = 0; - this.update(metric); - }, - - /** - * Tear everything down, including the front-end by sending a message without - * widgets. - */ - destroy() { - delete this.metrics; - this._send({metric: {skipTelemetry: true}}); - }, - - _send(data) { - let frame = this.frame; - - shell.sendEvent(frame, 'developer-hud-update', Cu.cloneInto(data, frame)); - this._logHistogram(data.metric); - }, - - _getAddonHistogram(item) { - let appName = this._getAddonHistogramName(item, APPNAME_IDX); - let histName = this._getAddonHistogramName(item, HISTNAME_IDX); - - return Services.telemetry.getAddonHistogram(appName, CUSTOM_HISTOGRAM_PREFIX - + histName); - }, - - _getAddonHistogramName(item, index) { - let array = item.split('_'); - return array[index].toUpperCase(); - }, - - _clearTelemetryData() { - developerHUD._histograms.forEach(function(item) { - Services.telemetry.getKeyedHistogramById(item).clear(); - }); - - developerHUD._customHistograms.forEach(item => { - this._getAddonHistogram(item).clear(); - }); - }, - - _sendTelemetryData() { - if (!developerHUD._telemetry) { - return; - } - telemetryDebug('calling sendTelemetryData'); - let frame = this.frame; - let payload = { - keyedHistograms: {}, - addonHistograms: {} - }; - // Package the hud histograms. - developerHUD._histograms.forEach(function(item) { - payload.keyedHistograms[item] = - Services.telemetry.getKeyedHistogramById(item).snapshot(); - }); - - // Package the registered hud custom histograms - developerHUD._customHistograms.forEach(item => { - let appName = this._getAddonHistogramName(item, APPNAME_IDX); - let histName = CUSTOM_HISTOGRAM_PREFIX + - this._getAddonHistogramName(item, HISTNAME_IDX); - let addonHist = Services.telemetry.getAddonHistogram(appName, histName).snapshot(); - if (!(appName in payload.addonHistograms)) { - payload.addonHistograms[appName] = {}; - } - // Do not include histograms with sum of 0. - if (addonHist.sum > 0) { - payload.addonHistograms[appName][histName] = addonHist; - } - }); - shell.sendEvent(frame, 'advanced-telemetry-update', Cu.cloneInto(payload, frame)); - }, - - _logHistogram(metric) { - if (!developerHUD._telemetry || metric.skipTelemetry) { - return; - } - - metric.appName = this.appName; - if (!metric.appName) { - return; - } - - let metricName = metric.name.toUpperCase(); - let metricAppName = metric.appName.toUpperCase(); - if (!metric.custom) { - let keyedMetricName = 'DEVTOOLS_HUD_' + metricName; - try { - let keyed = Services.telemetry.getKeyedHistogramById(keyedMetricName); - if (keyed) { - keyed.add(metric.appName, parseInt(metric.value, 10)); - developerHUD._histograms.add(keyedMetricName); - telemetryDebug(keyedMetricName, metric.value, metric.appName); - } - } catch(err) { - console.error('Histogram error is metricname added to histograms.json:' - + keyedMetricName); - } - } else { - let histogramName = CUSTOM_HISTOGRAM_PREFIX + metricAppName + '_' - + metricName; - // This is a call to add a value to an existing histogram. - if (typeof metric.value !== 'undefined') { - Services.telemetry.getAddonHistogram(metricAppName, - CUSTOM_HISTOGRAM_PREFIX + metricName).add(parseInt(metric.value, 10)); - telemetryDebug(histogramName, metric.value); - return; - } - - // The histogram already exists and are not adding data to it. - if (developerHUD._customHistograms.has(histogramName)) { - return; - } - - // This is a call to create a new histogram. - try { - let metricType = parseInt(metric.type, 10); - if (metricType === Services.telemetry.HISTOGRAM_COUNT) { - Services.telemetry.registerAddonHistogram(metricAppName, - CUSTOM_HISTOGRAM_PREFIX + metricName, metricType); - } else { - Services.telemetry.registerAddonHistogram(metricAppName, - CUSTOM_HISTOGRAM_PREFIX + metricName, metricType, metric.min, - metric.max, metric.buckets); - } - developerHUD._customHistograms.add(histogramName); - } catch (err) { - console.error('Histogram error: ' + err); - } - } - } -}; - - -/** - * The Console Watcher tracks the following metrics in apps: reflows, warnings, - * and errors, with security errors reported separately. - */ -var consoleWatcher = { - - _client: null, - _targets: new Map(), - _watching: { - reflows: false, - warnings: false, - errors: false, - security: false - }, - _security: [ - 'Mixed Content Blocker', - 'Mixed Content Message', - 'CSP', - 'Invalid HSTS Headers', - 'Invalid HPKP Headers', - 'Insecure Password Field', - 'SSL', - 'CORS' - ], - _reflowThreshold: 0, - - init(client) { - this._client = client; - this.consoleListener = this.consoleListener.bind(this); - - let watching = this._watching; - - for (let key in watching) { - let metric = key; - SettingsListener.observe('hud.' + metric, watching[metric], watch => { - // Watch or unwatch the metric. - if (watching[metric] = watch) { - return; - } - - // If unwatched, remove any existing widgets for that metric. - for (let target of this._targets.values()) { - target.clear({name: metric}); - } - }); - } - - SettingsListener.observe('hud.reflows.duration', this._reflowThreshold, threshold => { - this._reflowThreshold = threshold; - }); - - client.addListener('logMessage', this.consoleListener); - client.addListener('pageError', this.consoleListener); - client.addListener('consoleAPICall', this.consoleListener); - client.addListener('reflowActivity', this.consoleListener); - }, - - trackTarget(target) { - target.register('reflows'); - target.register('warnings'); - target.register('errors'); - target.register('security'); - - this._client.request({ - to: target.actor.consoleActor, - type: 'startListeners', - listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity'] - }, (res) => { - this._targets.set(target.actor.consoleActor, target); - }); - }, - - untrackTarget(target) { - this._client.request({ - to: target.actor.consoleActor, - type: 'stopListeners', - listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity'] - }, (res) => { }); - - this._targets.delete(target.actor.consoleActor); - }, - - consoleListener(type, packet) { - let target = this._targets.get(packet.from); - let metric = {}; - let output = ''; - - switch (packet.type) { - - case 'pageError': - let pageError = packet.pageError; - - if (pageError.warning || pageError.strict) { - metric.name = 'warnings'; - output += 'Warning ('; - } else { - metric.name = 'errors'; - output += 'Error ('; - } - - if (this._security.indexOf(pageError.category) > -1) { - metric.name = 'security'; - - // Telemetry sends the security error category not the - // count of security errors. - target._logHistogram({ - name: 'security_category', - value: pageError.category - }); - - // Indicate that the 'hud' security metric (the count of security - // errors) should not be sent as a telemetry metric since the - // security error category is being sent instead. - metric.skipTelemetry = true; - } - - let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError; - output += category + '): "' + (errorMessage.initial || errorMessage) + - '" in ' + sourceName + ':' + lineNumber + ':' + columnNumber; - break; - - case 'consoleAPICall': - switch (packet.message.level) { - - case 'error': - metric.name = 'errors'; - output += 'Error (console)'; - break; - - case 'warn': - metric.name = 'warnings'; - output += 'Warning (console)'; - break; - - case 'info': - this.handleTelemetryMessage(target, packet); - - // Currently, informational log entries are tracked only by - // telemetry. Nonetheless, for consistency, we continue here - // and let the function return normally, when it concludes 'info' - // entries are not being watched. - metric.name = 'info'; - break; - - default: - return; - } - break; - - case 'reflowActivity': - metric.name = 'reflows'; - - let {start, end, sourceURL, interruptible} = packet; - metric.interruptible = interruptible; - let duration = Math.round((end - start) * 100) / 100; - - // Record the reflow if the duration exceeds the threshold. - if (duration < this._reflowThreshold) { - return; - } - - output += 'Reflow: ' + duration + 'ms'; - if (sourceURL) { - output += ' ' + this.formatSourceURL(packet); - } - - // Telemetry also records reflow duration. - target._logHistogram({ - name: 'reflow_duration', - value: Math.round(duration) - }); - break; - - default: - return; - } - - if (developerHUD._telemetry) { - // Always record telemetry for these metrics. - if (metric.name === 'errors' || metric.name === 'warnings' || metric.name === 'reflows') { - let value = target.metrics.get(metric.name); - metric.value = (value || 0) + 1; - target._logHistogram(metric); - - // Telemetry has already been recorded. - metric.skipTelemetry = true; - - // If the metric is not being watched, persist the incremented value. - // If the metric is being watched, `target.bump` will increment the value - // of the metric and will persist the incremented value. - if (!this._watching[metric.name]) { - target.metrics.set(metric.name, metric.value); - } - } - } - - if (!this._watching[metric.name]) { - return; - } - - target.bump(metric, output); - }, - - formatSourceURL(packet) { - // Abbreviate source URL - let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL); - - // Add function name and line number - let {functionName, sourceLine} = packet; - source = 'in ' + (functionName || '<anonymousFunction>') + - ', ' + source + ':' + sourceLine; - - return source; - }, - - handleTelemetryMessage(target, packet) { - if (!developerHUD._telemetry) { - return; - } - - // If this is a 'telemetry' log entry, create a telemetry metric from - // the log content. - let separator = '|'; - let logContent = packet.message.arguments.toString(); - - if (logContent.indexOf('telemetry') < 0) { - return; - } - - let telemetryData = logContent.split(separator); - - // Positions of the components of a telemetry log entry. - let TELEMETRY_IDENTIFIER_IDX = 0; - let NAME_IDX = 1; - let VALUE_IDX = 2; - let TYPE_IDX = 2; - let MIN_IDX = 3; - let MAX_IDX = 4; - let BUCKETS_IDX = 5; - let MAX_CUSTOM_ARGS = 6; - let MIN_CUSTOM_ARGS = 3; - - if (telemetryData[TELEMETRY_IDENTIFIER_IDX] != 'telemetry' || - telemetryData.length < MIN_CUSTOM_ARGS || - telemetryData.length > MAX_CUSTOM_ARGS) { - return; - } - - let metric = { - name: telemetryData[NAME_IDX] - }; - - if (metric.name === 'MGMT') { - metric.value = telemetryData[VALUE_IDX]; - if (metric.value === 'TIMETOSHIP') { - telemetryDebug('Received a Ship event'); - target._sendTelemetryData(); - } else if (metric.value === 'CLEARMETRICS') { - target._clearTelemetryData(); - } - } else { - if (telemetryData.length === MIN_CUSTOM_ARGS) { - metric.value = telemetryData[VALUE_IDX]; - } else if (telemetryData.length === MAX_CUSTOM_ARGS) { - metric.type = telemetryData[TYPE_IDX]; - metric.min = telemetryData[MIN_IDX]; - metric.max = telemetryData[MAX_IDX]; - metric.buckets = telemetryData[BUCKETS_IDX]; - } - metric.custom = true; - target._logHistogram(metric); - } - } -}; -developerHUD.registerWatcher(consoleWatcher); - - -var eventLoopLagWatcher = { - _client: null, - _fronts: new Map(), - _active: false, - - init(client) { - this._client = client; - - SettingsListener.observe('hud.jank', false, this.settingsListener.bind(this)); - }, - - settingsListener(value) { - if (this._active == value) { - return; - } - - this._active = value; - - // Toggle the state of existing fronts. - let fronts = this._fronts; - for (let target of fronts.keys()) { - if (value) { - fronts.get(target).start(); - } else { - fronts.get(target).stop(); - target.clear({name: 'jank'}); - } - } - }, - - trackTarget(target) { - target.register('jank'); - - let front = new EventLoopLagFront(this._client, target.actor); - this._fronts.set(target, front); - - front.on('event-loop-lag', time => { - target.update({name: 'jank', value: time}, 'Jank: ' + time + 'ms'); - }); - - if (this._active) { - front.start(); - } - }, - - untrackTarget(target) { - let fronts = this._fronts; - if (fronts.has(target)) { - fronts.get(target).destroy(); - fronts.delete(target); - } - } -}; -developerHUD.registerWatcher(eventLoopLagWatcher); - -/* - * The performanceEntriesWatcher determines the delta between the epoch - * of an app's launch time and the epoch of the app's performance entry marks. - * When it receives an "appLaunch" performance entry mark it records the - * name of the app being launched and the epoch of when the launch ocurred. - * When it receives subsequent performance entry events for the app being - * launched, it records the delta of the performance entry opoch compared - * to the app-launch epoch and emits an "app-start-time-<performance mark name>" - * event containing the delta. - * - * Additionally, while recording the "app-start-time" for a performance mark, - * USS memory at the time of the performance mark is also recorded. - */ -var performanceEntriesWatcher = { - _client: null, - _fronts: new Map(), - _appLaunch: new Map(), - _supported: [ - 'contentInteractive', - 'navigationInteractive', - 'navigationLoaded', - 'visuallyLoaded', - 'fullyLoaded', - 'mediaEnumerated', - 'scanEnd' - ], - - init(client) { - this._client = client; - let setting = 'devtools.telemetry.supported_performance_marks'; - let defaultValue = this._supported.join(','); - - SettingsListener.observe(setting, defaultValue, supported => { - this._supported = supported.split(','); - }); - }, - - trackTarget(target) { - // The performanceEntries watcher doesn't register a metric because - // currently the metrics generated are not displayed in - // in the front-end. - - let front = new PerformanceEntriesFront(this._client, target.actor); - this._fronts.set(target, front); - - // User timings are always gathered; there is no setting to enable/ - // disable. - front.start(); - - front.on('entry', detail => { - - // Only process performance marks. - if (detail.type !== 'mark') { - return; - } - - let name = detail.name; - let epoch = detail.epoch; - - // If this is an "app launch" mark, record the app that was - // launched and the epoch of when it was launched. - if (name.indexOf('appLaunch') !== -1) { - let CHARS_UNTIL_APP_NAME = 7; // '@app://' - let startPos = name.indexOf('@app') + CHARS_UNTIL_APP_NAME; - let endPos = name.indexOf('.'); - let appName = name.slice(startPos, endPos); - this._appLaunch.set(appName, epoch); - return; - } - - // Only process supported performance marks - if (this._supported.indexOf(name) === -1) { - return; - } - - let origin = detail.origin; - origin = origin.slice(0, origin.indexOf('.')); - - let appLaunchTime = this._appLaunch.get(origin); - - // Sanity check: ensure we have an app launch time for the app - // corresponding to this performance mark. - if (!appLaunchTime) { - return; - } - - let time = epoch - appLaunchTime; - let eventName = 'app_startup_time_' + name; - - // Events based on performance marks are for telemetry only, they are - // not displayed in the HUD front end. - target._logHistogram({name: eventName, value: time}); - - memoryWatcher.front(target).residentUnique().then(value => { - // bug 1215277, need 'v2' for app-memory histograms - eventName = 'app_memory_' + name + '_v2'; - target._logHistogram({name: eventName, value: value}); - }, err => { - console.error(err); - }); - }); - }, - - untrackTarget(target) { - let fronts = this._fronts; - if (fronts.has(target)) { - fronts.get(target).destroy(); - fronts.delete(target); - } - } -}; -developerHUD.registerWatcher(performanceEntriesWatcher); - -/** - * The Memory Watcher uses devtools actors to track memory usage. - */ -var memoryWatcher = { - - _client: null, - _fronts: new Map(), - _timers: new Map(), - _watching: { - uss: false, - appmemory: false, - jsobjects: false, - jsstrings: false, - jsother: false, - dom: false, - style: false, - other: false - }, - _active: false, - - init(client) { - this._client = client; - let watching = this._watching; - - for (let key in watching) { - let category = key; - SettingsListener.observe('hud.' + category, false, watch => { - watching[category] = watch; - this.update(); - }); - } - }, - - update() { - let watching = this._watching; - let active = watching.appmemory || watching.uss; - - if (this._active) { - for (let target of this._fronts.keys()) { - if (!watching.appmemory) target.clear({name: 'memory'}); - if (!watching.uss) target.clear({name: 'uss'}); - if (!active) clearTimeout(this._timers.get(target)); - } - } else if (active) { - for (let target of this._fronts.keys()) { - this.measure(target); - } - } - this._active = active; - }, - - measure(target) { - let watch = this._watching; - let format = this.formatMemory; - - if (watch.uss) { - this.front(target).residentUnique().then(value => { - target.update({name: 'uss', value: value}, 'USS: ' + format(value)); - }, err => { - console.error(err); - }); - } - - if (watch.appmemory) { - front.measure().then(data => { - let total = 0; - let details = []; - - function item(name, condition, value) { - if (!condition) { - return; - } - - let v = parseInt(value); - total += v; - details.push(name + ': ' + format(v)); - } - - item('JS objects', watch.jsobjects, data.jsObjectsSize); - item('JS strings', watch.jsstrings, data.jsStringsSize); - item('JS other', watch.jsother, data.jsOtherSize); - item('DOM', watch.dom, data.domSize); - item('Style', watch.style, data.styleSize); - item('Other', watch.other, data.otherSize); - // TODO Also count images size (bug #976007). - - target.update({name: 'memory', value: total}, - 'App Memory: ' + format(total) + ' (' + details.join(', ') + ')'); - }, err => { - console.error(err); - }); - } - - let timer = setTimeout(() => this.measure(target), 2000); - this._timers.set(target, timer); - }, - - formatMemory(bytes) { - var prefix = ['','K','M','G','T','P','E','Z','Y']; - var i = 0; - for (; bytes > 1024 && i < prefix.length; ++i) { - bytes /= 1024; - } - return (Math.round(bytes * 100) / 100) + ' ' + prefix[i] + 'B'; - }, - - trackTarget(target) { - target.register('uss'); - target.register('memory'); - this._fronts.set(target, MemoryFront(this._client, target.actor)); - if (this._active) { - this.measure(target); - } - }, - - untrackTarget(target) { - let front = this._fronts.get(target); - if (front) { - front.destroy(); - clearTimeout(this._timers.get(target)); - this._fronts.delete(target); - this._timers.delete(target); - } - }, - - front(target) { - return this._fronts.get(target); - } -}; -developerHUD.registerWatcher(memoryWatcher); |