summaryrefslogtreecommitdiffstats
path: root/b2g/chrome/content/devtools
diff options
context:
space:
mode:
Diffstat (limited to 'b2g/chrome/content/devtools')
-rw-r--r--b2g/chrome/content/devtools/adb.js233
-rw-r--r--b2g/chrome/content/devtools/debugger.js397
-rw-r--r--b2g/chrome/content/devtools/hud.js1017
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);