Merge autoland to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Tue, 10 Apr 2018 19:53:31 +0300
changeset 466105 b74bbdb12c63137c85215e287f75bddf77c71caf
parent 466080 50b945170f334bb5c7c3dad5a8e975251a369b3b (current diff)
parent 466104 6ced908a1f35ac4544e6a01bdc8edbc98c6db1bf (diff)
child 466127 0a2dae2d8cf9f628c55668514c54a23da446d5de
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
testing/web-platform/meta/css/css-scoping/css-scoping-shadow-host-functional-rule.html.ini
testing/web-platform/meta/mediacapture-streams/GUM-optional-constraint.https.html.ini
testing/web-platform/meta/mediacapture-streams/GUM-trivial-constraint.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-add-audio-track.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-audio-only.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-finished-add.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-gettrackid.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-idl.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-removetrack.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStream-video-only.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html.ini
testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-id.https.html.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -101,30 +101,20 @@ browser/extensions/mortar/**
 browser/extensions/formautofill/phonenumberutils/PhoneNumberMetaData.jsm
 
 # devtools/ exclusions
 devtools/client/inspector/markup/test/doc_markup_events_*.html
 devtools/client/inspector/rules/test/doc_media_queries.html
 devtools/client/memory/test/chrome/*.html
 devtools/client/performance/components/test/test_jit_optimizations_01.html
 devtools/client/responsive.html/test/browser/touch.html
-devtools/client/shared/*.jsm
-devtools/client/shared/components/reps/reps.js
-devtools/client/shared/components/reps/test/mochitest/*.html
-!devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
-!devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
-!devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
-!devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
-!devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
 devtools/client/shared/components/test/mochitest/*.html
 !devtools/client/shared/components/test/mochitest/test_stack-trace.html
 devtools/client/shared/shim/test/test_*.html
 devtools/client/shared/test/browser_toolbar_webconsole_errors_count.html
-devtools/client/shared/webgl-utils.js
-devtools/client/shared/widgets/*.jsm
 devtools/client/storage/test/*.html
 !devtools/client/storage/test/storage-cookies.html
 !devtools/client/storage/test/storage-overflow.html
 !devtools/client/storage/test/storage-search.html
 !devtools/client/storage/test/storage-unsecured-iframe.html
 !devtools/client/storage/test/storage-unsecured-iframe-usercontextid.html
 devtools/client/webaudioeditor/**
 devtools/client/webconsole/old/net/**
@@ -145,16 +135,17 @@ devtools/shared/webconsole/test/test_*.h
 # Soon to be removed
 devtools/client/commandline/**
 # Soon to be removed, the new/ directory is explicitly excluded below due to
 # also being an imported repository.
 devtools/client/debugger/**
 
 # Ignore devtools imported repositories
 devtools/client/debugger/new/**
+devtools/client/shared/components/reps/**
 
 # Ignore devtools preferences files
 devtools/client/preferences/**
 devtools/shared/preferences/**
 devtools/startup/preferences/devtools-startup.js
 
 # Ignore devtools third-party libs
 devtools/shared/jsbeautify/*
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -31,72 +31,82 @@ module.exports = {
     "rules": {
       "no-return-assign": "off",
       "no-unused-vars": "off",
     }
   }, {
     "files": [
       "client/scratchpad/scratchpad-manager.jsm",
       "client/scratchpad/scratchpad.js",
+      "client/shared/*.jsm",
     ],
     "rules": {
       "camelcase": "off",
     }
   }, {
     "files": [
       "client/framework/**",
       "client/scratchpad/**",
+      "client/shared/*.jsm",
+      "client/shared/widgets/*.jsm",
     ],
     "rules": {
       "consistent-return": "off",
     }
   }, {
     "files": [
       "client/framework/**",
       "client/scratchpad/**",
+      "client/shared/AppCacheUtils.jsm",
     ],
     "rules": {
       "max-nested-callbacks": "off",
     }
   }, {
     "files": [
       "client/framework/**",
       "client/scratchpad/**",
+      "client/shared/*.jsm",
+      "client/shared/widgets/*.jsm",
     ],
     "rules": {
       "max-len": "off",
     }
   }, {
     "files": [
       "client/scratchpad/test/browser_scratchpad_inspect.js",
       "client/scratchpad/test/browser_scratchpad_inspect_primitives.js",
     ],
     "rules": {
       "no-labels": "off",
     }
   }, {
     "files": [
       "client/framework/**",
       "client/scratchpad/**",
+      "client/shared/*.jsm",
+      "client/shared/widgets/*.jsm",
     ],
     "rules": {
       "mozilla/no-aArgs": "off",
     }
   }, {
     "files": [
       "client/framework/test/**",
       "client/scratchpad/**",
     ],
     "rules": {
       "mozilla/var-only-at-top-level": "off",
     }
   }, {
     "files": [
       "client/framework/**",
       "client/scratchpad/**",
+      "client/shared/AppCacheUtils.jsm",
+      "client/shared/widgets/*.jsm",
     ],
     "rules": {
       "no-shadow": "off",
     }
   }, {
     "files": [
       "client/framework/**",
       "client/scratchpad/**",
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -4,23 +4,21 @@
 
 "use strict";
 
 const {Cu} = require("chrome");
 const Services = require("Services");
 
 const {DevToolsShim} = require("chrome://devtools-startup/content/DevToolsShim.jsm");
 
-// Load gDevToolsBrowser toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "TabTarget", "devtools/client/framework/target", true);
-loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "ToolboxHostManager", "devtools/client/framework/toolbox-host-manager", true);
-loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
 loader.lazyRequireGetter(this, "HUDService", "devtools/client/webconsole/hudservice", true);
+loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
 
 loader.lazyRequireGetter(this, "WebExtensionInspectedWindowFront",
       "devtools/shared/fronts/webextension-inspected-window", true);
 
 const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
   require("devtools/client/definitions");
@@ -38,16 +36,18 @@ const MAX_ORDINAL = 99;
 function DevTools() {
   this._tools = new Map(); // Map<toolId, tool>
   this._themes = new Map(); // Map<themeId, theme>
   this._toolboxes = new Map(); // Map<target, toolbox>
   // List of toolboxes that are still in process of creation
   this._creatingToolboxes = new Map(); // Map<target, toolbox Promise>
 
   EventEmitter.decorate(this);
+  this._telemetry = new Telemetry();
+  this._telemetry.setEventRecordingEnabled("devtools.main", true);
 
   // Listen for changes to the theme pref.
   this._onThemeChanged = this._onThemeChanged.bind(this);
   addThemeObserver(this._onThemeChanged);
 
   // This is important step in initialization codepath where we are going to
   // start registering all default tools and themes: create menuitems, keys, emit
   // related events.
@@ -476,16 +476,21 @@ DevTools.prototype = {
       toolbox = await toolboxPromise;
       this._creatingToolboxes.delete(target);
 
       if (startTime) {
         this.logToolboxOpenTime(toolbox.currentToolId, startTime);
       }
       this._firstShowToolbox = false;
     }
+
+    let width = Math.ceil(toolbox.win.outerWidth / 50) * 50;
+    this._telemetry.addEventProperty(
+      "devtools.main", "open", "tools", null, "width", width);
+
     return toolbox;
   },
 
   /**
    * Log telemetry related to toolbox opening.
    * Two distinct probes are logged. One for cold startup, when we open the very first
    * toolbox. This one includes devtools framework loading. And a second one for all
    * subsequent toolbox opening, which should all be faster.
@@ -495,20 +500,44 @@ DevTools.prototype = {
    *        The id of the opened tool.
    * @param {Number} startTime
    *        Indicates the time at which the user event related to the toolbox
    *        opening started. This is a `performance.now()` timing.
    */
   logToolboxOpenTime(toolId, startTime) {
     let { performance } = Services.appShell.hiddenDOMWindow;
     let delay = performance.now() - startTime;
+
     let telemetryKey = this._firstShowToolbox ?
       "DEVTOOLS_COLD_TOOLBOX_OPEN_DELAY_MS" : "DEVTOOLS_WARM_TOOLBOX_OPEN_DELAY_MS";
-    let histogram = Services.telemetry.getKeyedHistogramById(telemetryKey);
-    histogram.add(toolId, delay);
+    this._telemetry.logKeyed(telemetryKey, toolId, delay);
+
+    this._telemetry.addEventProperty(
+      "devtools.main", "open", "tools", null, "first_panel",
+      this.makeToolIdHumanReadable(toolId));
+  },
+
+  makeToolIdHumanReadable(toolId) {
+    if (/^[0-9a-fA-F]{40}_temporary-addon/.test(toolId)) {
+      return "temporary-addon";
+    }
+
+    let matches = toolId.match(
+      /^_([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})_/
+    );
+    if (matches && matches.length === 2) {
+      return matches[1];
+    }
+
+    matches = toolId.match(/^_?(.*)-\d+-\d+-devtools-panel$/);
+    if (matches && matches.length === 2) {
+      return matches[1];
+    }
+
+    return toolId;
   },
 
   async createToolbox(target, toolId, hostType, hostOptions) {
     let manager = new ToolboxHostManager(target, hostType, hostOptions);
 
     let toolbox = await manager.create(toolId);
 
     this._toolboxes.set(target, toolbox);
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -539,16 +539,21 @@ Toolbox.prototype = {
 
       await this.selectTool(this._defaultToolId);
 
       // Wait until the original tool is selected so that the split
       // console input will receive focus.
       let splitConsolePromise = promise.resolve();
       if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
         splitConsolePromise = this.openSplitConsole();
+        this._telemetry.addEventProperty(
+          "devtools.main", "open", "tools", null, "splitconsole", true);
+      } else {
+        this._telemetry.addEventProperty(
+          "devtools.main", "open", "tools", null, "splitconsole", false);
       }
 
       await promise.all([
         splitConsolePromise,
         framesPromise
       ]);
 
       // Lazily connect to the profiler here and don't wait for it to complete,
@@ -693,27 +698,46 @@ Toolbox.prototype = {
       case Toolbox.HostType.BOTTOM: return 0;
       case Toolbox.HostType.SIDE: return 1;
       case Toolbox.HostType.WINDOW: return 2;
       case Toolbox.HostType.CUSTOM: return 3;
       default: return 9;
     }
   },
 
+  // Return HostType string for telemetry
+  _getTelemetryHostString: function() {
+    switch (this.hostType) {
+      case Toolbox.HostType.BOTTOM: return "bottom";
+      case Toolbox.HostType.SIDE: return "side";
+      case Toolbox.HostType.WINDOW: return "window";
+      case Toolbox.HostType.CUSTOM: return "other";
+      default: return "bottom";
+    }
+  },
+
   _pingTelemetry: function() {
     this._telemetry.toolOpened("toolbox");
 
     this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM,
                                              system.getScreenDimensions());
     this._telemetry.log(HOST_HISTOGRAM, this._getTelemetryHostId());
 
     // Log current theme. The question we want to answer is:
     // "What proportion of users use which themes?"
     let currentTheme = Services.prefs.getCharPref("devtools.theme");
     this._telemetry.logKeyedScalar(CURRENT_THEME_SCALAR, currentTheme, 1);
+
+    this._telemetry.preparePendingEvent(
+      "devtools.main", "open", "tools", null,
+      ["entrypoint", "first_panel", "host", "splitconsole", "width"]
+    );
+    this._telemetry.addEventProperty(
+      "devtools.main", "open", "tools", null, "host", this._getTelemetryHostString()
+    );
   },
 
   /**
    * Create a simple object to store the state of a toolbox button. The checked state of
    * a button can be updated arbitrarily outside of the scope of the toolbar and its
    * controllers. In order to simplify this interaction this object emits an
    * "updatechecked" event any time the isChecked value is updated, allowing any consuming
    * components to listen and respond to updates.
--- a/devtools/client/shared/AppCacheUtils.jsm
+++ b/devtools/client/shared/AppCacheUtils.jsm
@@ -26,17 +26,16 @@
 "use strict";
 
 var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
 var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm", {});
 var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
 var { gDevTools } = require("devtools/client/framework/devtools");
 var Services = require("Services");
-var promise = require("promise");
 
 this.EXPORTED_SYMBOLS = ["AppCacheUtils"];
 
 function AppCacheUtils(documentOrUri) {
   this._parseManifest = this._parseManifest.bind(this);
 
   if (documentOrUri) {
     if (typeof documentOrUri == "string") {
@@ -63,17 +62,17 @@ AppCacheUtils.prototype = {
         if (!this.manifestURI) {
           this._addError(0, "noManifest");
           resolve(this.errors);
         }
 
         this._getURIInfo(this.manifestURI).then(uriInfo => {
           this._parseManifest(uriInfo).then(() => {
             // Sort errors by line number.
-            this.errors.sort(function (a, b) {
+            this.errors.sort(function(a, b) {
               return a.line - b.line;
             });
             resolve(this.errors);
           });
         });
       });
     });
   },
@@ -179,33 +178,33 @@ AppCacheUtils.prototype = {
     });
   },
 
   _getURIInfo: function ACU__getURIInfo(uri) {
     return new Promise((resolve, reject) => {
       let inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
                           .createInstance(Ci.nsIScriptableInputStream);
       let buffer = "";
-      var channel = NetUtil.newChannel({
+      let channel = NetUtil.newChannel({
         uri: uri,
         loadUsingSystemPrincipal: true,
         securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
       });
 
       // Avoid the cache:
       channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
       channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
 
       channel.asyncOpen2({
-        onStartRequest: function (request, context) {
+        onStartRequest: function(request, context) {
           // This empty method is needed in order for onDataAvailable to be
           // called.
         },
 
-        onDataAvailable: function (request, context, stream, offset, count) {
+        onDataAvailable: function(request, context, stream, offset, count) {
           request.QueryInterface(Ci.nsIHttpChannel);
           inputStream.init(stream);
           buffer = buffer.concat(inputStream.read(count));
         },
 
         onStopRequest: function onStartRequest(request, context, statusCode) {
           if (statusCode === 0) {
             request.QueryInterface(Ci.nsIHttpChannel);
@@ -218,22 +217,22 @@ AppCacheUtils.prototype = {
               mimeType: request.contentType,
               contentLength: request.contentLength,
               nocache: request.isNoCacheResponse() || request.isNoStoreResponse(),
               prePath: request.URI.prePath + "/",
               text: buffer
             };
 
             result.requestHeaders = {};
-            request.visitRequestHeaders(function (header, value) {
+            request.visitRequestHeaders(function(header, value) {
               result.requestHeaders[header.toLowerCase()] = value;
             });
 
             result.responseHeaders = {};
-            request.visitResponseHeaders(function (header, value) {
+            request.visitResponseHeaders(function(header, value) {
               result.responseHeaders[header.toLowerCase()] = value;
             });
 
             resolve(result);
           } else {
             resolve({
               name: request.name,
               success: false
@@ -248,19 +247,19 @@ AppCacheUtils.prototype = {
     if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
       throw new Error(l10n.GetStringFromName("cacheDisabled"));
     }
 
     let entries = [];
 
     let appCacheStorage = Services.cache2.appCacheStorage(Services.loadContextInfo.default, null);
     appCacheStorage.asyncVisitStorage({
-      onCacheStorageInfo: function () {},
+      onCacheStorageInfo: function() {},
 
-      onCacheEntryInfo: function (aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime) {
+      onCacheEntryInfo: function(aURI, aIdEnhance, aDataSize, aFetchCount, aLastModifiedTime, aExpirationTime) {
         let lowerKey = aURI.asciiSpec.toLowerCase();
 
         if (searchTerm && !lowerKey.includes(searchTerm.toLowerCase())) {
           return;
         }
 
         if (aIdEnhance) {
           aIdEnhance += ":";
@@ -283,31 +282,29 @@ AppCacheUtils.prototype = {
 
     if (entries.length === 0) {
       throw new Error(l10n.GetStringFromName("noResults"));
     }
     return entries;
   },
 
   viewEntry: function ACU_viewEntry(key) {
-    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
-               .getService(Ci.nsIWindowMediator);
-    let win = wm.getMostRecentWindow(gDevTools.chromeWindowType);
+    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     let url = "about:cache-entry?storage=appcache&context=&eid=&uri=" + key;
     win.openUILinkIn(url, "tab");
   },
 
   clearAll: function ACU_clearAll() {
     if (!Services.prefs.getBoolPref("browser.cache.disk.enable")) {
       throw new Error(l10n.GetStringFromName("cacheDisabled"));
     }
 
     let appCacheStorage = Services.cache2.appCacheStorage(Services.loadContextInfo.default, null);
     appCacheStorage.asyncEvictStorage({
-      onCacheEntryDoomed: function (result) {}
+      onCacheEntryDoomed: function(result) {}
     });
   },
 
   _getManifestURI: function ACU__getManifestURI() {
     return new Promise((resolve, reject) => {
       let getURI = () => {
         let htmlNode = this.doc.querySelector("html[manifest]");
         if (htmlNode) {
@@ -323,32 +320,31 @@ AppCacheUtils.prototype = {
 
           return pageUri.substring(0, pageUri.lastIndexOf("/") + 1) + manifestURI;
         }
       };
 
       if (this.doc) {
         let uri = getURI();
         return resolve(uri);
-      } else {
-        this._getURIInfo(this.uri).then(uriInfo => {
-          if (uriInfo.success) {
-            let html = uriInfo.text;
-            let parser = _DOMParser;
-            this.doc = parser.parseFromString(html, "text/html");
-            let uri = getURI();
-            resolve(uri);
-          } else {
-            this.errors.push({
+      }
+      this._getURIInfo(this.uri).then(uriInfo => {
+        if (uriInfo.success) {
+          let html = uriInfo.text;
+          let parser = _DOMParser;
+          this.doc = parser.parseFromString(html, "text/html");
+          let uri = getURI();
+          resolve(uri);
+        } else {
+          this.errors.push({
               line: 0,
               msg: l10n.GetStringFromName("invalidURI")
-            });
-          }
-        });
-      }
+          });
+        }
+      });
     });
   },
 
   _addError: function ACU__addError(line, l10nString, ...params) {
     let msg;
 
     if (params) {
       msg = l10n.formatStringFromName(l10nString, params, params.length);
@@ -610,17 +606,16 @@ ManifestParser.prototype = {
       }
     }
   },
 };
 
 XPCOMUtils.defineLazyGetter(this, "l10n", () => Services.strings
   .createBundle("chrome://devtools/locale/appcacheutils.properties"));
 
-XPCOMUtils.defineLazyGetter(this, "appcacheservice", function () {
+XPCOMUtils.defineLazyGetter(this, "appcacheservice", function() {
   return Cc["@mozilla.org/network/application-cache-service;1"]
            .getService(Ci.nsIApplicationCacheService);
-
 });
 
-XPCOMUtils.defineLazyGetter(this, "_DOMParser", function () {
+XPCOMUtils.defineLazyGetter(this, "_DOMParser", function() {
   return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
 });
--- a/devtools/client/shared/DOMHelpers.jsm
+++ b/devtools/client/shared/DOMHelpers.jsm
@@ -21,117 +21,119 @@ this.EXPORTED_SYMBOLS = ["DOMHelpers"];
 this.DOMHelpers = function DOMHelpers(aWindow) {
   if (!aWindow) {
     throw new Error("window can't be null or undefined");
   }
   this.window = aWindow;
 };
 
 DOMHelpers.prototype = {
-  getParentObject: function Helpers_getParentObject(node)
-  {
+  getParentObject: function Helpers_getParentObject(node) {
     let parentNode = node ? node.parentNode : null;
 
     if (!parentNode) {
       // Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
       // and Notation. top level windows have no parentNode
       if (node && node == this.window.Node.DOCUMENT_NODE) {
         // document type
         if (node.defaultView) {
           let embeddingFrame = node.defaultView.frameElement;
-          if (embeddingFrame)
+          if (embeddingFrame) {
             return embeddingFrame.parentNode;
+          }
         }
       }
       // a Document object without a parentNode or window
-      return null;  // top level has no parent
+      return null; // top level has no parent
     }
 
     if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
       if (parentNode.defaultView) {
         return parentNode.defaultView.frameElement;
       }
       // parent is document element, but no window at defaultView.
       return null;
     }
 
-    if (!parentNode.localName)
+    if (!parentNode.localName) {
       return null;
+    }
 
     return parentNode;
   },
 
   getChildObject: function Helpers_getChildObject(node, index, previousSibling,
-                                                showTextNodesWithWhitespace)
-  {
-    if (!node)
+                                                showTextNodesWithWhitespace) {
+    if (!node) {
       return null;
+    }
 
     if (node.contentDocument) {
       // then the node is a frame
       if (index == 0) {
-        return node.contentDocument.documentElement;  // the node's HTMLElement
+        return node.contentDocument.documentElement; // the node's HTMLElement
       }
       return null;
     }
 
     if (node.getSVGDocument) {
       let svgDocument = node.getSVGDocument();
       if (svgDocument) {
         // then the node is a frame
         if (index == 0) {
-          return svgDocument.documentElement;  // the node's SVGElement
+          return svgDocument.documentElement; // the node's SVGElement
         }
         return null;
       }
     }
 
     let child = null;
-    if (previousSibling)  // then we are walking
+    if (previousSibling) {
+      // then we are walking
       child = this.getNextSibling(previousSibling);
-    else
+    } else {
       child = this.getFirstChild(node);
+    }
 
-    if (showTextNodesWithWhitespace)
+    if (showTextNodesWithWhitespace) {
       return child;
+    }
 
     for (; child; child = this.getNextSibling(child)) {
-      if (!this.isWhitespaceText(child))
+      if (!this.isWhitespaceText(child)) {
         return child;
+      }
     }
 
-    return null;  // we have no children worth showing.
+    return null; // we have no children worth showing.
   },
 
-  getFirstChild: function Helpers_getFirstChild(node)
-  {
+  getFirstChild: function Helpers_getFirstChild(node) {
     let SHOW_ALL = nodeFilterConstants.SHOW_ALL;
     this.treeWalker = node.ownerDocument.createTreeWalker(node,
       SHOW_ALL, null);
     return this.treeWalker.firstChild();
   },
 
-  getNextSibling: function Helpers_getNextSibling(node)
-  {
+  getNextSibling: function Helpers_getNextSibling(node) {
     let next = this.treeWalker.nextSibling();
 
-    if (!next)
+    if (!next) {
       delete this.treeWalker;
+    }
 
     return next;
   },
 
-  isWhitespaceText: function Helpers_isWhitespaceText(node)
-  {
+  isWhitespaceText: function Helpers_isWhitespaceText(node) {
     return node.nodeType == this.window.Node.TEXT_NODE &&
                             !/[^\s]/.exec(node.nodeValue);
   },
 
-  destroy: function Helpers_destroy()
-  {
+  destroy: function Helpers_destroy() {
     delete this.window;
     delete this.treeWalker;
   },
 
   /**
    * A simple way to be notified (once) when a window becomes
    * interactive (DOMContentLoaded).
    *
@@ -139,17 +141,17 @@ DOMHelpers.prototype = {
    * chrome iframes are loaded in content docshells (in Firefox
    * tabs for example).
    */
   onceDOMReady: function Helpers_onLocationChange(callback, targetURL) {
     let window = this.window;
     let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIWebNavigation)
                          .QueryInterface(Ci.nsIDocShell);
-    let onReady = function (event) {
+    let onReady = function(event) {
       if (event.target == window.document) {
         docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady);
         // If in `callback` the URL of the window is changed and a listener to DOMContentLoaded
         // is attached, the event we just received will be also be caught by the new listener.
         // We want to avoid that so we execute the callback in the next queue.
         Services.tm.dispatchToMainThread(callback);
       }
     };
--- a/devtools/client/shared/SplitView.jsm
+++ b/devtools/client/shared/SplitView.jsm
@@ -23,18 +23,17 @@ var bindings = new WeakMap();
  * A split view contains items, each of those having one summary and one details
  * elements.
  * It is adaptive as it behaves similarly to a richlistbox when there the aspect
  * ratio is narrow or as a pair listbox-box otherwise.
  *
  * @param DOMElement aRoot
  * @see appendItem
  */
-this.SplitView = function SplitView(aRoot)
-{
+this.SplitView = function SplitView(aRoot) {
   this._root = aRoot;
   this._controller = aRoot.querySelector(".splitview-controller");
   this._nav = aRoot.querySelector(".splitview-nav");
   this._side = aRoot.querySelector(".splitview-side-details");
   this._activeSummary = null;
 
   this._mql = aRoot.ownerDocument.defaultView.matchMedia(LANDSCAPE_MEDIA_QUERY);
 
@@ -84,48 +83,44 @@ this.SplitView = function SplitView(aRoo
 };
 
 SplitView.prototype = {
   /**
     * Retrieve whether the UI currently has a landscape orientation.
     *
     * @return boolean
     */
-  get isLandscape()
-  {
+  get isLandscape() {
     return this._mql.matches;
   },
 
   /**
     * Retrieve the root element.
     *
     * @return DOMElement
     */
-  get rootElement()
-  {
+  get rootElement() {
     return this._root;
   },
 
   /**
     * Retrieve the active item's summary element or null if there is none.
     *
     * @return DOMElement
     */
-  get activeSummary()
-  {
+  get activeSummary() {
     return this._activeSummary;
   },
 
   /**
     * Set the active item's summary element.
     *
     * @param DOMElement aSummary
     */
-  set activeSummary(aSummary)
-  {
+  set activeSummary(aSummary) {
     if (aSummary == this._activeSummary) {
       return;
     }
 
     if (this._activeSummary) {
       let binding = bindings.get(this._activeSummary);
 
       if (binding.onHide) {
@@ -150,32 +145,30 @@ SplitView.prototype = {
       binding.onShow(aSummary, binding._details, binding.data);
     }
   },
 
   /**
     * Retrieve the active item's details element or null if there is none.
     * @return DOMElement
     */
-  get activeDetails()
-  {
+  get activeDetails() {
     let summary = this.activeSummary;
     return summary ? bindings.get(summary)._details : null;
   },
 
   /**
    * Retrieve the summary element for a given ordinal.
    *
    * @param number aOrdinal
    * @return DOMElement
    *         Summary element with given ordinal or null if not found.
    * @see appendItem
    */
-  getSummaryElementByOrdinal: function SEC_getSummaryElementByOrdinal(aOrdinal)
-  {
+  getSummaryElementByOrdinal: function SEC_getSummaryElementByOrdinal(aOrdinal) {
     return this._nav.querySelector("* > li[data-ordinal='" + aOrdinal + "']");
   },
 
   /**
    * Append an item to the split view.
    *
    * @param DOMElement aSummary
    *        The summary element for the item.
@@ -193,18 +186,17 @@ SplitView.prototype = {
    *     - function(summary, details, data) onDestroy
    *         Called when the item has been removed.
    *     - object data
    *         Object to pass to the callbacks above.
    *     - number ordinal
    *         Items with a lower ordinal are displayed before those with a
    *         higher ordinal.
    */
-  appendItem: function ASV_appendItem(aSummary, aDetails, aOptions)
-  {
+  appendItem: function ASV_appendItem(aSummary, aDetails, aOptions) {
     let binding = aOptions || {};
 
     binding._summary = aSummary;
     binding._details = aDetails;
     bindings.set(aSummary, binding);
 
     this._nav.appendChild(aSummary);
 
@@ -230,18 +222,17 @@ SplitView.prototype = {
    *        and "splitview-tpl-details-" suffixed with aName.
    * @param object aOptions
    *        Optional object that defines custom behavior and data for the item.
    *        See appendItem for full description.
    * @return object{summary:,details:}
    *         Object with the new DOM elements created for summary and details.
    * @see appendItem
    */
-  appendTemplatedItem: function ASV_appendTemplatedItem(aName, aOptions)
-  {
+  appendTemplatedItem: function ASV_appendTemplatedItem(aName, aOptions) {
     aOptions = aOptions || {};
     let summary = this._root.querySelector("#splitview-tpl-summary-" + aName);
     let details = this._root.querySelector("#splitview-tpl-details-" + aName);
 
     summary = summary.cloneNode(true);
     summary.id = "";
     if (aOptions.ordinal !== undefined) { // can be zero
       summary.style.MozBoxOrdinalGroup = aOptions.ordinal;
@@ -255,53 +246,50 @@ SplitView.prototype = {
   },
 
   /**
     * Remove an item from the split view.
     *
     * @param DOMElement aSummary
     *        Summary element of the item to remove.
     */
-  removeItem: function ASV_removeItem(aSummary)
-  {
+  removeItem: function ASV_removeItem(aSummary) {
     if (aSummary == this._activeSummary) {
       this.activeSummary = null;
     }
 
     let binding = bindings.get(aSummary);
     aSummary.remove();
     binding._details.remove();
 
     if (binding.onDestroy) {
       binding.onDestroy(aSummary, binding._details, binding.data);
     }
   },
 
   /**
    * Remove all items from the split view.
    */
-  removeAll: function ASV_removeAll()
-  {
+  removeAll: function ASV_removeAll() {
     while (this._nav.hasChildNodes()) {
       this.removeItem(this._nav.firstChild);
     }
   },
 
   /**
    * Set the item's CSS class name.
    * This sets the class on both the summary and details elements, retaining
    * any SplitView-specific classes.
    *
    * @param DOMElement aSummary
    *        Summary element of the item to set.
    * @param string aClassName
    *        One or more space-separated CSS classes.
    */
-  setItemClassName: function ASV_setItemClassName(aSummary, aClassName)
-  {
+  setItemClassName: function ASV_setItemClassName(aSummary, aClassName) {
     let binding = bindings.get(aSummary);
     let viewSpecific;
 
     viewSpecific = aSummary.className.match(/(splitview\-[\w-]+)/g);
     viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
     aSummary.className = viewSpecific + " " + aClassName;
 
     viewSpecific = binding._details.className.match(/(splitview\-[\w-]+)/g);
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -8,25 +8,33 @@
  * Comprehensive documentation is in docs/frontend/telemetry.md
  */
 
 "use strict";
 
 const Services = require("Services");
 const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
 
+// Object to be shared among all instances.
+const PENDING_EVENTS = new Map();
+const PENDING_EVENT_PROPERTIES = new Map();
+
 class Telemetry {
   constructor() {
     // Bind pretty much all functions so that callers do not need to.
     this.toolOpened = this.toolOpened.bind(this);
     this.toolClosed = this.toolClosed.bind(this);
     this.log = this.log.bind(this);
     this.logScalar = this.logScalar.bind(this);
     this.logKeyedScalar = this.logKeyedScalar.bind(this);
     this.logOncePerBrowserVersion = this.logOncePerBrowserVersion.bind(this);
+    this.recordEvent = this.recordEvent.bind(this);
+    this.setEventRecordingEnabled = this.setEventRecordingEnabled.bind(this);
+    this.preparePendingEvent = this.preparePendingEvent.bind(this);
+    this.addEventProperty = this.addEventProperty.bind(this);
     this.destroy = this.destroy.bind(this);
 
     this._timers = new Map();
   }
 
   get histograms() {
     return {
       toolbox: {
@@ -285,20 +293,20 @@ class Telemetry {
    *         Value to store.
    */
   logScalar(scalarId, value) {
     if (!scalarId) {
       return;
     }
 
     try {
-      if (isNaN(value)) {
-        dump(`Warning: An attempt was made to write a non-numeric value ` +
-             `${value} to the ${scalarId} scalar. Only numeric values are ` +
-             `allowed.`);
+      if (isNaN(value) && typeof value !== "boolean") {
+        dump(`Warning: An attempt was made to write a non-numeric and ` +
+             `non-boolean value ${value} to the ${scalarId} scalar. Only ` +
+             `numeric and boolean values are allowed.`);
 
         return;
       }
       Services.telemetry.scalarSet(scalarId, value);
     } catch (e) {
       dump(`Warning: An attempt was made to write to the ${scalarId} ` +
            `scalar, which is not defined in Scalars.yaml\n`);
     }
@@ -379,16 +387,199 @@ class Telemetry {
         lastVersionHistogramUpdated !== currentVersion) {
       latestObj[perUserHistogram] = currentVersion;
       latest = JSON.stringify(latestObj);
       Services.prefs.setCharPref(TOOLS_OPENED_PREF, latest);
       this.log(perUserHistogram, value);
     }
   }
 
+  /**
+   * Event telemetry is disabled by default. Use this method to enable it for
+   * a particular category.
+   *
+   * @param {String} category
+   *        The telemetry event category e.g. "devtools.main"
+   * @param {Boolean} enabled
+   *        Enabled: true or false.
+   */
+  setEventRecordingEnabled(category, enabled) {
+    return Services.telemetry.setEventRecordingEnabled(category, enabled);
+  }
+
+  /**
+   * Telemetry events often need to make use of a number of properties from
+   * completely different codepaths. To make this possible we create a
+   * "pending event" along with an array of property names that we need to wait
+   * for before sending the event.
+   *
+   * As each property is received via addEventProperty() we check if all
+   * properties have been received. Once they have all been received we send the
+   * telemetry event.
+   *
+   * @param {String} category
+   *        The telemetry event category (a group name for events and helps to
+   *        avoid name conflicts) e.g. "devtools.main"
+   * @param {String} method
+   *        The telemetry event method (describes the type of event that
+   *        occurred e.g. "open")
+   * @param {String} object
+   *        The telemetry event object name (the name of the object the event
+   *        occurred on) e.g. "tools" or "setting"
+   * @param {String|null} value
+   *        The telemetry event value (a user defined value, providing context
+   *        for the event) e.g. "console"
+   * @param {Array} expected
+   *        An array of the properties needed before sending the telemetry
+   *        event e.g.
+   *        [
+   *          "host",
+   *          "width"
+   *        ]
+   */
+  preparePendingEvent(category, method, object, value, expected = []) {
+    const sig = `${category},${method},${object},${value}`;
+
+    if (expected.length === 0) {
+      throw new Error(`preparePendingEvent() was called without any expected ` +
+                      `properties.`);
+    }
+
+    PENDING_EVENTS.set(sig, {
+      extra: {},
+      expected: new Set(expected)
+    });
+
+    const props = PENDING_EVENT_PROPERTIES.get(sig);
+    if (props) {
+      for (let [name, val] of Object.entries(props)) {
+        this.addEventProperty(category, method, object, value, name, val);
+      }
+      PENDING_EVENT_PROPERTIES.delete(sig);
+    }
+  }
+
+  /**
+   * Adds an expected property for either a current or future pending event.
+   * This means that if preparePendingEvent() is called before or after sending
+   * the event properties they will automatically added to the event.
+   *
+   * @param {String} category
+   *        The telemetry event category (a group name for events and helps to
+   *        avoid name conflicts) e.g. "devtools.main"
+   * @param {String} method
+   *        The telemetry event method (describes the type of event that
+   *        occurred e.g. "open")
+   * @param {String} object
+   *        The telemetry event object name (the name of the object the event
+   *        occurred on) e.g. "tools" or "setting"
+   * @param {String|null} value
+   *        The telemetry event value (a user defined value, providing context
+   *        for the event) e.g. "console"
+   * @param {String} pendingPropName
+   *        The pending property name
+   * @param {String} pendingPropValue
+   *        The pending property value
+   */
+  addEventProperty(category, method, object, value, pendingPropName, pendingPropValue) {
+    const sig = `${category},${method},${object},${value}`;
+
+    // If the pending event has not been created add the property to the pending
+    // list.
+    if (!PENDING_EVENTS.has(sig)) {
+      PENDING_EVENT_PROPERTIES.set(sig, {
+        [pendingPropName]: pendingPropValue
+      });
+      return;
+    }
+
+    const { expected, extra } = PENDING_EVENTS.get(sig);
+
+    if (expected.has(pendingPropName)) {
+      extra[pendingPropName] = pendingPropValue;
+
+      if (expected.size === Object.keys(extra).length) {
+        this._sendPendingEvent(category, method, object, value);
+      }
+    } else {
+      // The property was not expected, warn and bail.
+      throw new Error(`An attempt was made to add the unexpected property ` +
+                      `"${pendingPropName}" to a telemetry event with the ` +
+                      `signature "${sig}"\n`);
+    }
+  }
+
+  /**
+   * Send a telemetry event.
+   *
+   * @param {String} category
+   *        The telemetry event category (a group name for events and helps to
+   *        avoid name conflicts) e.g. "devtools.main"
+   * @param {String} method
+   *        The telemetry event method (describes the type of event that
+   *        occurred e.g. "open")
+   * @param {String} object
+   *        The telemetry event object name (the name of the object the event
+   *        occurred on) e.g. "tools" or "setting"
+   * @param {String|null} value
+   *        The telemetry event value (a user defined value, providing context
+   *        for the event) e.g. "console"
+   * @param {Object} extra
+   *        The telemetry event extra object containing the properties that will
+   *        be sent with the event e.g.
+   *        {
+   *          host: "bottom",
+   *          width: "1024"
+   *        }
+   */
+  recordEvent(category, method, object, value, extra) {
+    // Only string values are allowed so cast all values to strings.
+    for (let [name, val] of Object.entries(extra)) {
+      val = val + "";
+      extra[name] = val;
+
+      if (val.length > 80) {
+        const sig = `${category},${method},${object},${value}`;
+
+        throw new Error(`The property "${name}" was added to a telemetry ` +
+                        `event with the signature ${sig} but it's value ` +
+                        `"${val}" is longer than the maximum allowed length ` +
+                        `of 80 characters\n`);
+      }
+    }
+    Services.telemetry.recordEvent(category, method, object, value, extra);
+  }
+
+  /**
+   * A private method that is not to be used externally. This method is used to
+   * prepare a pending telemetry event for sending and then send it via
+   * recordEvent().
+   *
+   * @param {String} category
+   *        The telemetry event category (a group name for events and helps to
+   *        avoid name conflicts) e.g. "devtools.main"
+   * @param {String} method
+   *        The telemetry event method (describes the type of event that
+   *        occurred e.g. "open")
+   * @param {String} object
+   *        The telemetry event object name (the name of the object the event
+   *        occurred on) e.g. "tools" or "setting"
+   * @param {String|null} value
+   *        The telemetry event value (a user defined value, providing context
+   *        for the event) e.g. "console"
+   */
+  _sendPendingEvent(category, method, object, value) {
+    const sig = `${category},${method},${object},${value}`;
+    const { extra } = PENDING_EVENTS.get(sig);
+
+    PENDING_EVENTS.delete(sig);
+    PENDING_EVENT_PROPERTIES.delete(sig);
+    this.recordEvent(category, method, object, value, extra);
+  }
+
   destroy() {
     for (let histogramId of this._timers.keys()) {
       this.stopTimer(histogramId);
     }
   }
 }
 
 module.exports = Telemetry;
--- a/devtools/client/shared/webpack/shims/platform-stack-stub.js
+++ b/devtools/client/shared/webpack/shims/platform-stack-stub.js
@@ -13,37 +13,41 @@
 /**
  * Looks like Cu.callFunctionWithAsyncStack, but just calls the callee.
  */
 function callFunctionWithAsyncStack(callee, stack, id) {
   return callee();
 }
 
 /**
- * Return a description of the Nth caller, suitable for logging.
+ * Return the Nth path from the stack excluding substr.
  *
- * @param {Number} n the caller to describe
- * @return {String} a description of the nth caller.
+ * @param {Number}
+ *        n the Nth path from the stack to describe.
+ * @param {String} substr
+ *        A segment of the path that should be excluded.
  */
-function describeNthCaller(n) {
+function getNthPathExcluding(n, substr) {
   if (isWorker) {
     return "";
   }
 
   let stack = new Error().stack.split("\n");
-  // Add one here to skip this function.
+  stack = stack.filter(line => {
+    return line && !line.includes(substr);
+  });
   return stack[n + 1];
 }
 
 /**
  * Return a stack object that can be serialized and, when
  * deserialized, passed to callFunctionWithAsyncStack.
  */
 function getStack() {
   // There's no reason for this to do anything fancy, since it's only
   // used to pass back into callFunctionWithAsyncStack, which we can't
   // implement.
   return null;
 }
 
 exports.callFunctionWithAsyncStack = callFunctionWithAsyncStack;
-exports.describeNthCaller = describeNthCaller;
+exports.getNthPathExcluding = getNthPathExcluding;
 exports.getStack = getStack;
--- a/devtools/client/shared/webpack/shims/test/test_stack.js
+++ b/devtools/client/shared/webpack/shims/test/test_stack.js
@@ -6,21 +6,21 @@
 
 "use strict";
 
 const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
 const {
   callFunctionWithAsyncStack,
   getStack,
-  describeNthCaller
+  getNthPathExcluding
 } = require("devtools/client/shared/webpack/shims/platform-stack-stub");
 
 function f3() {
-  return describeNthCaller(2);
+  return getNthPathExcluding(2);
 }
 
 function f2() {
   return f3();
 }
 
 function f1() {
   return f2();
--- a/devtools/client/shared/widgets/AbstractTreeItem.jsm
+++ b/devtools/client/shared/widgets/AbstractTreeItem.jsm
@@ -1,17 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 const { require, loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
-const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 this.EXPORTED_SYMBOLS = ["AbstractTreeItem"];
 
 /**
@@ -144,30 +143,30 @@ AbstractTreeItem.prototype = {
    * Creates the view for this tree item. Implement this method in the
    * inheriting classes to create the child node displayed in the tree.
    * Use `this.level` and the provided `arrowNode` as you see fit.
    *
    * @param nsIDOMNode document
    * @param nsIDOMNode arrowNode
    * @return nsIDOMNode
    */
-  _displaySelf: function (document, arrowNode) {
+  _displaySelf: function(document, arrowNode) {
     throw new Error(
       "The `_displaySelf` method needs to be implemented by inheriting classes.");
   },
 
   /**
    * Populates this tree item with child items, whenever it's expanded.
    * Implement this method in the inheriting classes to fill the provided
    * `children` array with AbstractTreeItem instances, which will then be
    * magically handled by this tree item.
    *
    * @param array:AbstractTreeItem children
    */
-  _populateSelf: function (children) {
+  _populateSelf: function(children) {
     throw new Error(
       "The `_populateSelf` method needs to be implemented by inheriting classes.");
   },
 
   /**
    * Gets the this tree's owner document.
    * @return Document
    */
@@ -247,17 +246,17 @@ AbstractTreeItem.prototype = {
    * @param nsIDOMNode containerNode
    *        The parent element for this tree item (and every other tree item).
    * @param nsIDOMNode fragmentNode [optional]
    *        An optional document fragment temporarily holding this tree item in
    *        the current batch. Defaults to the `containerNode`.
    * @param nsIDOMNode beforeNode [optional]
    *        An optional child element which should succeed this tree item.
    */
-  attachTo: function (containerNode, fragmentNode = containerNode, beforeNode = null) {
+  attachTo: function(containerNode, fragmentNode = containerNode, beforeNode = null) {
     this._containerNode = containerNode;
     this._constructTargetNode();
 
     if (beforeNode) {
       fragmentNode.insertBefore(this._targetNode, beforeNode);
     } else {
       fragmentNode.appendChild(this._targetNode);
     }
@@ -266,178 +265,177 @@ AbstractTreeItem.prototype = {
       this.expand();
     }
   },
 
   /**
    * Permanently removes this tree item (and all subsequent children) from the
    * parent container.
    */
-  remove: function () {
+  remove: function() {
     this._targetNode.remove();
     this._hideChildren();
     this._childTreeItems.length = 0;
   },
 
   /**
    * Focuses this item in the tree.
    */
-  focus: function () {
+  focus: function() {
     this._targetNode.focus();
   },
 
   /**
    * Expands this item in the tree.
    */
-  expand: function () {
+  expand: function() {
     if (this._expanded) {
       return;
     }
     this._expanded = true;
     this._arrowNode.setAttribute("open", "");
     this._targetNode.setAttribute("expanded", "");
     this._toggleChildren(true);
     this._rootItem.emit("expand", this);
   },
 
   /**
    * Collapses this item in the tree.
    */
-  collapse: function () {
+  collapse: function() {
     if (!this._expanded) {
       return;
     }
     this._expanded = false;
     this._arrowNode.removeAttribute("open");
     this._targetNode.removeAttribute("expanded", "");
     this._toggleChildren(false);
     this._rootItem.emit("collapse", this);
   },
 
   /**
    * Returns the child item at the specified index.
    *
    * @param number index
    * @return AbstractTreeItem
    */
-  getChild: function (index = 0) {
+  getChild: function(index = 0) {
     return this._childTreeItems[index];
   },
 
   /**
    * Calls the provided function on all the descendants of this item.
    * If this item was never expanded, then no descendents exist yet.
    * @param function cb
    */
-  traverse: function (cb) {
+  traverse: function(cb) {
     for (let child of this._childTreeItems) {
       cb(child);
       child.bfs();
     }
   },
 
   /**
    * Calls the provided function on all descendants of this item until
    * a truthy value is returned by the predicate.
    * @param function predicate
    * @return AbstractTreeItem
    */
-  find: function (predicate) {
+  find: function(predicate) {
     for (let child of this._childTreeItems) {
       if (predicate(child) || child.find(predicate)) {
         return child;
       }
     }
     return null;
   },
 
   /**
    * Shows or hides all the children of this item in the tree. If neessary,
    * populates this item with children.
    *
    * @param boolean visible
    *        True if the children should be visible, false otherwise.
    */
-  _toggleChildren: function (visible) {
+  _toggleChildren: function(visible) {
     if (visible) {
       if (!this._populated) {
         this._populateSelf(this._childTreeItems);
         this._populated = this._childTreeItems.length > 0;
       }
       this._showChildren();
     } else {
       this._hideChildren();
     }
   },
 
   /**
    * Shows all children of this item in the tree.
    */
-  _showChildren: function () {
+  _showChildren: function() {
     // If this is the root item and we're not expanding any child nodes,
     // it is safe to append everything at once.
     if (this == this._rootItem && this.autoExpandDepth == 0) {
       this._appendChildrenBatch();
-    }
-    // Otherwise, append the child items and their descendants successively;
-    // if not, the tree will become garbled and nodes will intertwine,
-    // since all the tree items are sharing a single container node.
-    else {
+    } else {
+      // Otherwise, append the child items and their descendants successively;
+      // if not, the tree will become garbled and nodes will intertwine,
+      // since all the tree items are sharing a single container node.
       this._appendChildrenSuccessive();
     }
   },
 
   /**
    * Hides all children of this item in the tree.
    */
-  _hideChildren: function () {
+  _hideChildren: function() {
     for (let item of this._childTreeItems) {
       item._targetNode.remove();
       item._hideChildren();
     }
   },
 
   /**
    * Appends all children in a single batch.
    * This only works properly for root nodes when no child nodes will expand.
    */
-  _appendChildrenBatch: function () {
+  _appendChildrenBatch: function() {
     if (this._fragment === undefined) {
       this._fragment = this.document.createDocumentFragment();
     }
 
     let childTreeItems = this._childTreeItems;
 
     for (let i = 0, len = childTreeItems.length; i < len; i++) {
       childTreeItems[i].attachTo(this._containerNode, this._fragment);
     }
 
     this._containerNode.appendChild(this._fragment);
   },
 
   /**
    * Appends all children successively.
    */
-  _appendChildrenSuccessive: function () {
+  _appendChildrenSuccessive: function() {
     let childTreeItems = this._childTreeItems;
     let expandedChildTreeItems = childTreeItems.filter(e => e._expanded);
     let nextNode = this._getSiblingAtDelta(1);
 
     for (let i = 0, len = childTreeItems.length; i < len; i++) {
       childTreeItems[i].attachTo(this._containerNode, undefined, nextNode);
     }
     for (let i = 0, len = expandedChildTreeItems.length; i < len; i++) {
       expandedChildTreeItems[i]._showChildren();
     }
   },
 
   /**
    * Constructs and stores the target node displaying this tree item.
    */
-  _constructTargetNode: function () {
+  _constructTargetNode: function() {
     if (this._constructed) {
       return;
     }
     this._onArrowClick = this._onArrowClick.bind(this);
     this._onClick = this._onClick.bind(this);
     this._onDoubleClick = this._onDoubleClick.bind(this);
     this._onKeyDown = this._onKeyDown.bind(this);
     this._onFocus = this._onFocus.bind(this);
@@ -465,17 +463,17 @@ AbstractTreeItem.prototype = {
    * Gets the element displaying an item in the tree at the specified offset
    * relative to this item.
    *
    * @param number delta
    *        The offset from this item to the target item.
    * @return nsIDOMNode
    *         The element displaying the target item at the specified offset.
    */
-  _getSiblingAtDelta: function (delta) {
+  _getSiblingAtDelta: function(delta) {
     let childNodes = this._containerNode.childNodes;
     let indexOfSelf = Array.indexOf(childNodes, this._targetNode);
     if (indexOfSelf + delta >= 0) {
       return childNodes[indexOfSelf + delta];
     }
     return undefined;
   },
 
@@ -491,99 +489,105 @@ AbstractTreeItem.prototype = {
     let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils);
     return utils.getBoundsWithoutFlushing(elem).height;
   },
 
   /**
    * Focuses the first item in this tree.
    */
-  _focusFirstNode: function () {
+  _focusFirstNode: function() {
     let childNodes = this._containerNode.childNodes;
     // The root node of the tree may be hidden in practice, so uses for-loop
     // here to find the next visible node.
     for (let i = 0; i < childNodes.length; i++) {
       // The height will be 0 if an element is invisible.
       if (this._getHeight(childNodes[i])) {
         childNodes[i].focus();
         return;
       }
     }
   },
 
   /**
    * Focuses the last item in this tree.
    */
-  _focusLastNode: function () {
+  _focusLastNode: function() {
     let childNodes = this._containerNode.childNodes;
     childNodes[childNodes.length - 1].focus();
   },
 
   /**
    * Focuses the next item in this tree.
    */
-  _focusNextNode: function () {
+  _focusNextNode: function() {
     let nextElement = this._getSiblingAtDelta(1);
-    if (nextElement) nextElement.focus(); // nsIDOMNode
+    if (nextElement) {
+      nextElement.focus();
+    } // nsIDOMNode
   },
 
   /**
    * Focuses the previous item in this tree.
    */
-  _focusPrevNode: function () {
+  _focusPrevNode: function() {
     let prevElement = this._getSiblingAtDelta(-1);
-    if (prevElement) prevElement.focus(); // nsIDOMNode
+    if (prevElement) {
+      prevElement.focus();
+    } // nsIDOMNode
   },
 
   /**
    * Focuses the parent item in this tree.
    *
    * The parent item is not always the previous item, because any tree item
    * may have multiple children.
    */
-  _focusParentNode: function () {
+  _focusParentNode: function() {
     let parentItem = this._parentItem;
-    if (parentItem) parentItem.focus(); // AbstractTreeItem
+    if (parentItem) {
+      parentItem.focus();
+    } // AbstractTreeItem
   },
 
   /**
    * Handler for the "click" event on the arrow node of this tree item.
    */
-  _onArrowClick: function (e) {
+  _onArrowClick: function(e) {
     if (!this._expanded) {
       this.expand();
     } else {
       this.collapse();
     }
   },
 
   /**
    * Handler for the "click" event on the element displaying this tree item.
    */
-  _onClick: function (e) {
+  _onClick: function(e) {
     e.stopPropagation();
     this.focus();
   },
 
   /**
    * Handler for the "dblclick" event on the element displaying this tree item.
    */
-  _onDoubleClick: function (e) {
+  _onDoubleClick: function(e) {
     // Ignore dblclick on the arrow as it has already recived and handled two
     // click events.
     if (!e.target.classList.contains("arrow")) {
       this._onArrowClick(e);
     }
     this.focus();
   },
 
   /**
    * Handler for the "keydown" event on the element displaying this tree item.
    */
-  _onKeyDown: function (e) {
+  _onKeyDown: function(e) {
     // Prevent scrolling when pressing navigation keys.
     ViewHelpers.preventScrolling(e);
 
     switch (e.keyCode) {
       case KeyCodes.DOM_VK_UP:
         this._focusPrevNode();
         return;
 
@@ -630,26 +634,25 @@ AbstractTreeItem.prototype = {
         return;
 
       case KeyCodes.DOM_VK_HOME:
         this._focusFirstNode();
         return;
 
       case KeyCodes.DOM_VK_END:
         this._focusLastNode();
-        return;
     }
   },
 
   /**
    * Handler for the "focus" event on the element displaying this tree item.
    */
-  _onFocus: function (e) {
+  _onFocus: function(e) {
     this._rootItem.emit("focus", this);
   },
 
   /**
    * Handler for the "blur" event on the element displaying this tree item.
    */
-  _onBlur: function (e) {
+  _onBlur: function(e) {
     this._rootItem.emit("blur", this);
   }
 };
--- a/devtools/client/shared/widgets/BreadcrumbsWidget.jsm
+++ b/devtools/client/shared/widgets/BreadcrumbsWidget.jsm
@@ -62,52 +62,52 @@ BreadcrumbsWidget.prototype = {
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @param nsIDOMNode aContents
    *        The node displayed in the container.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  insertItemAt: function (aIndex, aContents) {
+  insertItemAt: function(aIndex, aContents) {
     let list = this._list;
     let breadcrumb = new Breadcrumb(this, aContents);
     return list.insertBefore(breadcrumb._target, list.childNodes[aIndex]);
   },
 
   /**
    * Returns the child node in this container situated at the specified index.
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  getItemAtIndex: function (aIndex) {
+  getItemAtIndex: function(aIndex) {
     return this._list.childNodes[aIndex];
   },
 
   /**
    * Removes the specified child node from this container.
    *
    * @param nsIDOMNode aChild
    *        The element associated with the displayed item.
    */
-  removeChild: function (aChild) {
+  removeChild: function(aChild) {
     this._list.removeChild(aChild);
 
     if (this._selectedItem == aChild) {
       this._selectedItem = null;
     }
   },
 
   /**
    * Removes all of the child nodes from this container.
    */
-  removeAllItems: function () {
+  removeAllItems: function() {
     let list = this._list;
 
     while (list.hasChildNodes()) {
       list.firstChild.remove();
     }
 
     this._selectedItem = null;
   },
@@ -143,58 +143,62 @@ BreadcrumbsWidget.prototype = {
   /**
    * Returns the value of the named attribute on this container.
    *
    * @param string aName
    *        The name of the attribute.
    * @return string
    *         The current attribute value.
    */
-  getAttribute: function (aName) {
-    if (aName == "scrollPosition") return this._list.scrollPosition;
-    if (aName == "scrollWidth") return this._list.scrollWidth;
+  getAttribute: function(aName) {
+    if (aName == "scrollPosition") {
+      return this._list.scrollPosition;
+    }
+    if (aName == "scrollWidth") {
+      return this._list.scrollWidth;
+    }
     return this._parent.getAttribute(aName);
   },
 
   /**
    * Ensures the specified element is visible.
    *
    * @param nsIDOMNode aElement
    *        The element to make visible.
    */
-  ensureElementIsVisible: function (aElement) {
+  ensureElementIsVisible: function(aElement) {
     if (!aElement) {
       return;
     }
 
     // Repeated calls to ensureElementIsVisible would interfere with each other
     // and may sometimes result in incorrect scroll positions.
     setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => {
       if (this._list.ensureElementIsVisible) {
         this._list.ensureElementIsVisible(aElement);
       }
     });
   },
 
   /**
    * The underflow and overflow listener for the arrowscrollbox container.
    */
-  _onUnderflow: function ({ target }) {
+  _onUnderflow: function({ target }) {
     if (target != this._list) {
       return;
     }
     target._scrollButtonUp.collapsed = true;
     target._scrollButtonDown.collapsed = true;
     target.removeAttribute("overflows");
   },
 
   /**
    * The underflow and overflow listener for the arrowscrollbox container.
    */
-  _onOverflow: function ({ target }) {
+  _onOverflow: function({ target }) {
     if (target != this._list) {
       return;
     }
     target._scrollButtonUp.collapsed = false;
     target._scrollButtonDown.collapsed = false;
     target.setAttribute("overflows", "");
   },
 
--- a/devtools/client/shared/widgets/SideMenuWidget.jsm
+++ b/devtools/client/shared/widgets/SideMenuWidget.jsm
@@ -95,32 +95,32 @@ SideMenuWidget.prototype = {
    * @param object aAttachment [optional]
    *        Some attached primitive/object. Custom options supported:
    *          - group: a string specifying the group to place this item into
    *          - checkboxState: the checked state of the checkbox, if shown
    *          - checkboxTooltip: the tooltip text for the checkbox, if shown
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  insertItemAt: function (aIndex, aContents, aAttachment = {}) {
+  insertItemAt: function(aIndex, aContents, aAttachment = {}) {
     let group = this._getMenuGroupForName(aAttachment.group);
     let item = this._getMenuItemForGroup(group, aContents, aAttachment);
     let element = item.insertSelfAt(aIndex);
 
     return element;
   },
 
   /**
    * Checks to see if the list is scrolled all the way to the bottom.
    * Uses getBoundsWithoutFlushing to limit the performance impact
    * of this function.
    *
    * @return bool
    */
-  isScrolledToBottom: function () {
+  isScrolledToBottom: function() {
     if (this._list.lastElementChild) {
       let utils = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
       let childRect = utils.getBoundsWithoutFlushing(this._list.lastElementChild);
       let listRect = utils.getBoundsWithoutFlushing(this._list);
 
       // Cheap way to check if it's scrolled all the way to the bottom.
       return (childRect.height + childRect.top) <= listRect.bottom;
@@ -128,57 +128,56 @@ SideMenuWidget.prototype = {
 
     return false;
   },
 
   /**
    * Scroll the list to the bottom after a timeout.
    * If the user scrolls in the meantime, cancel this operation.
    */
-  scrollToBottom: function () {
+  scrollToBottom: function() {
     this._list.scrollTop = this._list.scrollHeight;
     this.emit("scroll-to-bottom");
   },
 
   /**
    * Returns the child node in this container situated at the specified index.
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  getItemAtIndex: function (aIndex) {
+  getItemAtIndex: function(aIndex) {
     return this._orderedMenuElementsArray[aIndex];
   },
 
   /**
    * Removes the specified child node from this container.
    *
    * @param nsIDOMNode aChild
    *        The element associated with the displayed item.
    */
-  removeChild: function (aChild) {
+  removeChild: function(aChild) {
     this._getNodeForContents(aChild).remove();
 
     this._orderedMenuElementsArray.splice(
       this._orderedMenuElementsArray.indexOf(aChild), 1);
 
     this._itemsByElement.delete(aChild);
 
     if (this._selectedItem == aChild) {
       this._selectedItem = null;
     }
   },
 
   /**
    * Removes all of the child nodes from this container.
    */
-  removeAllItems: function () {
-    let parent = this._parent;
+  removeAllItems: function() {
     let list = this._list;
 
     while (list.hasChildNodes()) {
       list.firstChild.remove();
     }
 
     this._selectedItem = null;
 
@@ -217,40 +216,40 @@ SideMenuWidget.prototype = {
   },
 
   /**
    * Ensures the specified element is visible.
    *
    * @param nsIDOMNode aElement
    *        The element to make visible.
    */
-  ensureElementIsVisible: function (aElement) {
+  ensureElementIsVisible: function(aElement) {
     if (!aElement) {
       return;
     }
 
     // Ensure the element is visible but not scrolled horizontally.
     let boxObject = this._list.boxObject;
     boxObject.ensureElementIsVisible(aElement);
     boxObject.scrollBy(-this._list.clientWidth, 0);
   },
 
   /**
    * Shows all the groups, even the ones with no visible children.
    */
-  showEmptyGroups: function () {
+  showEmptyGroups: function() {
     for (let group of this._orderedGroupElementsArray) {
       group.hidden = false;
     }
   },
 
   /**
    * Hides all the groups which have no visible children.
    */
-  hideEmptyGroups: function () {
+  hideEmptyGroups: function() {
     let visibleChildNodes = ".side-menu-widget-item-contents:not([hidden=true])";
 
     for (let group of this._orderedGroupElementsArray) {
       group.hidden = group.querySelectorAll(visibleChildNodes).length == 0;
     }
     for (let menuItem of this._orderedMenuElementsArray) {
       menuItem.parentNode.hidden = menuItem.hidden;
     }
@@ -259,47 +258,47 @@ SideMenuWidget.prototype = {
   /**
    * Adds a new attribute or changes an existing attribute on this container.
    *
    * @param string aName
    *        The name of the attribute.
    * @param string aValue
    *        The desired attribute value.
    */
-  setAttribute: function (aName, aValue) {
+  setAttribute: function(aName, aValue) {
     this._parent.setAttribute(aName, aValue);
 
     if (aName == "emptyText") {
       this._textWhenEmpty = aValue;
     }
   },
 
   /**
    * Removes an attribute on this container.
    *
    * @param string aName
    *        The name of the attribute.
    */
-  removeAttribute: function (aName) {
+  removeAttribute: function(aName) {
     this._parent.removeAttribute(aName);
 
     if (aName == "emptyText") {
       this._removeEmptyText();
     }
   },
 
   /**
    * Set the checkbox state for the item associated with the given node.
    *
    * @param nsIDOMNode aNode
    *        The dom node for an item we want to check.
    * @param boolean aCheckState
    *        True to check, false to uncheck.
    */
-  checkItem: function (aNode, aCheckState) {
+  checkItem: function(aNode, aCheckState) {
     const widgetItem = this._itemsByElement.get(aNode);
     if (!widgetItem) {
       throw new Error("No item for " + aNode);
     }
     widgetItem.check(aCheckState);
   },
 
   /**
@@ -312,32 +311,32 @@ SideMenuWidget.prototype = {
     }
     this._emptyTextValue = aValue;
     this._showEmptyText();
   },
 
   /**
    * Creates and appends a label signaling that this container is empty.
    */
-  _showEmptyText: function () {
+  _showEmptyText: function() {
     if (this._emptyTextNode || !this._emptyTextValue) {
       return;
     }
     let label = this.document.createElement("label");
     label.className = "plain side-menu-widget-empty-text";
     label.setAttribute("value", this._emptyTextValue);
 
     this._parent.insertBefore(label, this._list);
     this._emptyTextNode = label;
   },
 
   /**
    * Removes the label representing a notice in this container.
    */
-  _removeEmptyText: function () {
+  _removeEmptyText: function() {
     if (!this._emptyTextNode) {
       return;
     }
 
     this._parent.removeChild(this._emptyTextNode);
     this._emptyTextNode = null;
   },
 
@@ -345,17 +344,17 @@ SideMenuWidget.prototype = {
    * Gets a container representing a group for menu items. If the container
    * is not available yet, it is immediately created.
    *
    * @param string aName
    *        The required group name.
    * @return SideMenuGroup
    *         The newly created group.
    */
-  _getMenuGroupForName: function (aName) {
+  _getMenuGroupForName: function(aName) {
     let cachedGroup = this._groupsByName.get(aName);
     if (cachedGroup) {
       return cachedGroup;
     }
 
     let group = new SideMenuGroup(this, aName, {
       showCheckbox: this._showGroupCheckboxes
     });
@@ -372,17 +371,17 @@ SideMenuWidget.prototype = {
    *
    * @param SideMenuGroup aGroup
    *        The group to contain the menu item.
    * @param nsIDOMNode aContents
    *        The node displayed in the container.
    * @param object aAttachment [optional]
    *        Some attached primitive/object.
    */
-  _getMenuItemForGroup: function (aGroup, aContents, aAttachment) {
+  _getMenuItemForGroup: function(aGroup, aContents, aAttachment) {
     return new SideMenuItem(aGroup, aContents, aAttachment, {
       showArrow: this._showArrows,
       showCheckbox: this._showItemCheckboxes
     });
   },
 
   /**
    * Returns the .side-menu-widget-item node corresponding to a SideMenuItem.
@@ -390,28 +389,27 @@ SideMenuWidget.prototype = {
    * these child items, in which case we need to be careful on which nodes
    * .selected class names are added, or which nodes are removed.
    *
    * @param nsIDOMNode aChild
    *        An element which is the target node of a SideMenuItem.
    * @return nsIDOMNode
    *         The wrapper node if there is one, or the same child otherwise.
    */
-  _getNodeForContents: function (aChild) {
+  _getNodeForContents: function(aChild) {
     if (aChild.hasAttribute("merged-item-contents")) {
       return aChild;
-    } else {
-      return aChild.parentNode;
     }
+    return aChild.parentNode;
   },
 
   /**
    * Shows the contextMenu element.
    */
-  _showContextMenu: function (e) {
+  _showContextMenu: function(e) {
     if (!this._contextMenu) {
       return;
     }
 
     // Don't show the menu if a descendant node is going to be visible also.
     let node = e.originalTarget;
     while (node && node !== this._list) {
       if (node.hasAttribute("contextmenu")) {
@@ -482,41 +480,42 @@ function SideMenuGroup(aWidget, aName, a
         checkboxTooltip: L10N.getStr("sideMenu.groupCheckbox.tooltip")
       });
       checkbox.className = "side-menu-widget-group-checkbox";
     }
 
     title.appendChild(name);
     target.appendChild(title);
     target.appendChild(list);
-  }
-  // Skip a few redundant nodes when no title is shown.
-  else {
+  } else {
+    // Skip a few redundant nodes when no title is shown.
     let target = this._target = this._list = this.document.createElement("vbox");
     target.className = "side-menu-widget-group side-menu-widget-group-list";
     target.setAttribute("merged-group-contents", "");
   }
 }
 
 SideMenuGroup.prototype = {
   get _orderedGroupElementsArray() {
     return this.ownerView._orderedGroupElementsArray;
   },
   get _orderedMenuElementsArray() {
     return this.ownerView._orderedMenuElementsArray;
   },
-  get _itemsByElement() { return this.ownerView._itemsByElement; },
+  get _itemsByElement() {
+    return this.ownerView._itemsByElement;
+  },
 
   /**
    * Inserts this group in the parent container at the specified index.
    *
    * @param number aIndex
    *        The position in the container intended for this group.
    */
-  insertSelfAt: function (aIndex) {
+  insertSelfAt: function(aIndex) {
     let ownerList = this.ownerView._list;
     let groupsArray = this._orderedGroupElementsArray;
 
     if (aIndex >= 0) {
       ownerList.insertBefore(this._target, groupsArray[aIndex]);
       groupsArray.splice(aIndex, 0, this._target);
     } else {
       ownerList.appendChild(this._target);
@@ -525,17 +524,17 @@ SideMenuGroup.prototype = {
   },
 
   /**
    * Finds the expected index of this group based on its name.
    *
    * @return number
    *         The expected index.
    */
-  findExpectedIndexForSelf: function (sortPredicate) {
+  findExpectedIndexForSelf: function(sortPredicate) {
     let identifier = this.identifier;
     let groupsArray = this._orderedGroupElementsArray;
 
     for (let group of groupsArray) {
       let name = group.getAttribute("name");
       if (sortPredicate(name, identifier) > 0 && // Insertion sort at its best :)
           !name.includes(identifier)) { // Least significant group should be last.
         return groupsArray.indexOf(group);
@@ -590,46 +589,47 @@ function SideMenuItem(aGroup, aContents,
     container.appendChild(target);
 
     // Show a horizontal arrow towards the content.
     if (aOptions.showArrow) {
       let arrow = this._arrow = this.document.createElement("hbox");
       arrow.className = "side-menu-widget-item-arrow";
       container.appendChild(arrow);
     }
-  }
-  // Skip a few redundant nodes when no horizontal arrow or checkbox is shown.
-  else {
+  } else {
+    // Skip a few redundant nodes when no horizontal arrow or checkbox is shown.
     let target = this._target = this._container = this.document.createElement("hbox");
     target.className = "side-menu-widget-item side-menu-widget-item-contents";
     target.setAttribute("merged-item-contents", "");
   }
 
   this._target.setAttribute("flex", "1");
   this.contents = aContents;
 }
 
 SideMenuItem.prototype = {
   get _orderedGroupElementsArray() {
     return this.ownerView._orderedGroupElementsArray;
   },
   get _orderedMenuElementsArray() {
     return this.ownerView._orderedMenuElementsArray;
   },
-  get _itemsByElement() { return this.ownerView._itemsByElement; },
+  get _itemsByElement() {
+    return this.ownerView._itemsByElement;
+  },
 
   /**
    * Inserts this item in the parent group at the specified index.
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  insertSelfAt: function (aIndex) {
+  insertSelfAt: function(aIndex) {
     let ownerList = this.ownerView._list;
     let menuArray = this._orderedMenuElementsArray;
 
     if (aIndex >= 0) {
       ownerList.insertBefore(this._container, ownerList.childNodes[aIndex]);
       menuArray.splice(aIndex, 0, this._target);
     } else {
       ownerList.appendChild(this._container);
@@ -641,17 +641,17 @@ SideMenuItem.prototype = {
   },
 
   /**
    * Check or uncheck the checkbox associated with this item.
    *
    * @param boolean aCheckState
    *        True to check, false to uncheck.
    */
-  check: function (aCheckState) {
+  check: function(aCheckState) {
     if (!this._checkbox) {
       throw new Error("Cannot check items that do not have checkboxes.");
     }
     // Don't set or remove the "checked" attribute, assign the property instead.
     // Otherwise, the "CheckboxStateChange" event will not be fired. XUL!!
     this._checkbox.checked = !!aCheckState;
   },
 
--- a/devtools/client/shared/widgets/SimpleListWidget.jsm
+++ b/devtools/client/shared/widgets/SimpleListWidget.jsm
@@ -44,53 +44,53 @@ SimpleListWidget.prototype = {
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @param nsIDOMNode aContents
    *        The node displayed in the container.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  insertItemAt: function (aIndex, aContents) {
+  insertItemAt: function(aIndex, aContents) {
     aContents.classList.add("simple-list-widget-item");
 
     let list = this._list;
     return list.insertBefore(aContents, list.childNodes[aIndex]);
   },
 
   /**
    * Returns the child node in this container situated at the specified index.
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @return nsIDOMNode
    *         The element associated with the displayed item.
    */
-  getItemAtIndex: function (aIndex) {
+  getItemAtIndex: function(aIndex) {
     return this._list.childNodes[aIndex];
   },
 
   /**
    * Immediately removes the specified child node from this container.
    *
    * @param nsIDOMNode aChild
    *        The element associated with the displayed item.
    */
-  removeChild: function (aChild) {
+  removeChild: function(aChild) {
     this._list.removeChild(aChild);
 
     if (this._selectedItem == aChild) {
       this._selectedItem = null;
     }
   },
 
   /**
    * Removes all of the child nodes from this container.
    */
-  removeAllItems: function () {
+  removeAllItems: function() {
     let list = this._list;
     let parent = this._parent;
 
     while (list.hasChildNodes()) {
       list.firstChild.remove();
     }
 
     parent.scrollTop = 0;
@@ -129,47 +129,47 @@ SimpleListWidget.prototype = {
   /**
    * Adds a new attribute or changes an existing attribute on this container.
    *
    * @param string aName
    *        The name of the attribute.
    * @param string aValue
    *        The desired attribute value.
    */
-  setAttribute: function (aName, aValue) {
+  setAttribute: function(aName, aValue) {
     this._parent.setAttribute(aName, aValue);
 
     if (aName == "emptyText") {
       this._textWhenEmpty = aValue;
     } else if (aName == "headerText") {
       this._textAsHeader = aValue;
     }
   },
 
   /**
    * Removes an attribute on this container.
    *
    * @param string aName
    *        The name of the attribute.
    */
-  removeAttribute: function (aName) {
+  removeAttribute: function(aName) {
     this._parent.removeAttribute(aName);
 
     if (aName == "emptyText") {
       this._removeEmptyText();
     }
   },
 
   /**
    * Ensures the specified element is visible.
    *
    * @param nsIDOMNode aElement
    *        The element to make visible.
    */
-  ensureElementIsVisible: function (aElement) {
+  ensureElementIsVisible: function(aElement) {
     if (!aElement) {
       return;
     }
 
     // Ensure the element is visible but not scrolled horizontally.
     let boxObject = this._list.boxObject;
     boxObject.ensureElementIsVisible(aElement);
     boxObject.scrollBy(-this._list.clientWidth, 0);
@@ -197,47 +197,47 @@ SimpleListWidget.prototype = {
     }
     this._emptyTextValue = aValue;
     this._showEmptyText();
   },
 
   /**
    * Creates and appends a label displayed as this container's header.
    */
-  _showHeaderText: function () {
+  _showHeaderText: function() {
     if (this._headerTextNode || !this._headerTextValue) {
       return;
     }
     let label = this.document.createElement("label");
     label.className = "plain simple-list-widget-perma-text";
     label.setAttribute("value", this._headerTextValue);
 
     this._parent.insertBefore(label, this._list);
     this._headerTextNode = label;
   },
 
   /**
    * Creates and appends a label signaling that this container is empty.
    */
-  _showEmptyText: function () {
+  _showEmptyText: function() {
     if (this._emptyTextNode || !this._emptyTextValue) {
       return;
     }
     let label = this.document.createElement("label");
     label.className = "plain simple-list-widget-empty-text";
     label.setAttribute("value", this._emptyTextValue);
 
     this._parent.appendChild(label);
     this._emptyTextNode = label;
   },
 
   /**
    * Removes the label signaling that this container is empty.
    */
-  _removeEmptyText: function () {
+  _removeEmptyText: function() {
     if (!this._emptyTextNode) {
       return;
     }
     this._parent.removeChild(this._emptyTextNode);
     this._emptyTextNode = null;
   },
 
   window: null,
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -30,25 +30,25 @@ const {PluralForm} = require("devtools/s
 const {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper(DBG_STRINGS_URI);
 
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1",
   "nsIClipboardHelper");
 
 Object.defineProperty(this, "WebConsoleUtils", {
-  get: function () {
+  get: function() {
     return require("devtools/client/webconsole/utils").Utils;
   },
   configurable: true,
   enumerable: true
 });
 
 Object.defineProperty(this, "NetworkHelper", {
-  get: function () {
+  get: function() {
     return require("devtools/shared/webconsole/network-helper");
   },
   configurable: true,
   enumerable: true
 });
 
 this.EXPORTED_SYMBOLS = ["VariablesView", "escapeHTML"];
 
@@ -119,17 +119,17 @@ VariablesView.prototype = {
    *
    * @param string aName
    *        The scope's name (e.g. "Local", "Global" etc.).
    * @param string aCustomClass
    *        An additional class name for the containing element.
    * @return Scope
    *         The newly created Scope instance.
    */
-  addScope: function (aName = "", aCustomClass = "") {
+  addScope: function(aName = "", aCustomClass = "") {
     this._removeEmptyNotice();
     this._toggleSearchVisibility(true);
 
     let scope = new Scope(this, aName, { customClass: aCustomClass });
     this._store.push(scope);
     this._itemsByElement.set(scope._target, scope);
     this._currHierarchy.set(aName, scope);
     scope.header = !!aName;
@@ -139,17 +139,17 @@ VariablesView.prototype = {
 
   /**
    * Removes all items from this container.
    *
    * @param number aTimeout [optional]
    *        The number of milliseconds to delay the operation if
    *        lazy emptying of this container is enabled.
    */
-  empty: function (aTimeout = this.lazyEmptyDelay) {
+  empty: function(aTimeout = this.lazyEmptyDelay) {
     // If there are no items in this container, emptying is useless.
     if (!this._store.length) {
       return;
     }
 
     this._store.length = 0;
     this._itemsByElement = new WeakMap();
     this._prevHierarchy = this._currHierarchy;
@@ -179,17 +179,17 @@ VariablesView.prototype = {
    * immediately attached to the parent container. The old container list
    * is kept around for a short period of time, hopefully accounting for the
    * data fetching delay. In the meantime, any operations can be executed
    * normally.
    *
    * @see VariablesView.empty
    * @see VariablesView.commitHierarchy
    */
-  _emptySoon: function (aTimeout) {
+  _emptySoon: function(aTimeout) {
     let prevList = this._list;
     let currList = this._list = this.document.createElement("scrollbox");
 
     this.window.setTimeout(() => {
       prevList.removeEventListener("keydown", this._onViewKeyDown);
       currList.addEventListener("keydown", this._onViewKeyDown);
       currList.setAttribute("orient", "vertical");
 
@@ -426,17 +426,17 @@ VariablesView.prototype = {
   get searchPlaceholder() {
     return this._searchboxPlaceholder;
   },
 
   /**
    * Enables variable and property searching in this view.
    * Use the "searchEnabled" setter to enable searching.
    */
-  _enableSearch: function () {
+  _enableSearch: function() {
     // If searching was already enabled, no need to re-enable it again.
     if (this._searchboxContainer) {
       return;
     }
     let document = this.document;
     let ownerNode = this._parent.parentNode;
 
     let container = this._searchboxContainer = document.createElement("hbox");
@@ -457,17 +457,17 @@ VariablesView.prototype = {
     container.appendChild(searchbox);
     ownerNode.insertBefore(container, this._parent);
   },
 
   /**
    * Disables variable and property searching in this view.
    * Use the "searchEnabled" setter to disable searching.
    */
-  _disableSearch: function () {
+  _disableSearch: function() {
     // If searching was already disabled, no need to re-disable it again.
     if (!this._searchboxContainer) {
       return;
     }
     this._searchboxContainer.remove();
     this._searchboxNode.removeEventListener("command", this._onSearchboxInput);
     this._searchboxNode.removeEventListener("keydown", this._onSearchboxKeyDown);
 
@@ -477,55 +477,54 @@ VariablesView.prototype = {
 
   /**
    * Sets the variables searchbox container hidden or visible.
    * It's hidden by default.
    *
    * @param boolean aVisibleFlag
    *        Specifies the intended visibility.
    */
-  _toggleSearchVisibility: function (aVisibleFlag) {
+  _toggleSearchVisibility: function(aVisibleFlag) {
     // If searching was already disabled, there's no need to hide it.
     if (!this._searchboxContainer) {
       return;
     }
     this._searchboxContainer.hidden = !aVisibleFlag;
   },
 
   /**
    * Listener handling the searchbox input event.
    */
-  _onSearchboxInput: function () {
+  _onSearchboxInput: function() {
     this.scheduleSearch(this._searchboxNode.value);
   },
 
   /**
    * Listener handling the searchbox keydown event.
    */
-  _onSearchboxKeyDown: function (e) {
+  _onSearchboxKeyDown: function(e) {
     switch (e.keyCode) {
       case KeyCodes.DOM_VK_RETURN:
         this._onSearchboxInput();
         return;
       case KeyCodes.DOM_VK_ESCAPE:
         this._searchboxNode.value = "";
         this._onSearchboxInput();
-        return;
     }
   },
 
   /**
    * Schedules searching for variables or properties matching the query.
    *
    * @param string aToken
    *        The variable or property to search for.
    * @param number aWait
    *        The amount of milliseconds to wait until draining.
    */
-  scheduleSearch: function (aToken, aWait) {
+  scheduleSearch: function(aToken, aWait) {
     // Check if this search operation may not be executed lazily.
     if (!this.lazySearch) {
       this._doSearch(aToken);
       return;
     }
 
     // The amount of time to wait for the requests to settle.
     let maxDelay = SEARCH_ACTION_MAX_DELAY;
@@ -541,17 +540,17 @@ VariablesView.prototype = {
    *
    * If aToken is falsy, then all the scopes are unhidden and expanded,
    * while the available variables and properties inside those scopes are
    * just unhidden.
    *
    * @param string aToken
    *        The variable or property to search for.
    */
-  _doSearch: function (aToken) {
+  _doSearch: function(aToken) {
     if (this.controller && this.controller.supportsSearch()) {
       // Retrieve the main Scope in which we add attributes
       let scope = this._store[0]._store.get(undefined);
       if (!aToken) {
         // Prune the view from old previous content
         // so that we delete the intermediate search results
         // we created in previous searches
         for (let property of scope._store.values()) {
@@ -587,17 +586,17 @@ VariablesView.prototype = {
    * user). Descends into each scope to check the scope and its children.
    *
    * @param function aPredicate
    *        A function that returns true when a match is found.
    * @return Scope | Variable | Property
    *         The first visible scope, variable or property, or null if nothing
    *         is found.
    */
-  _findInVisibleItems: function (aPredicate) {
+  _findInVisibleItems: function(aPredicate) {
     for (let scope of this._store) {
       let result = scope._findInVisibleItems(aPredicate);
       if (result) {
         return result;
       }
     }
     return null;
   },
@@ -609,17 +608,17 @@ VariablesView.prototype = {
    * its children.
    *
    * @param function aPredicate
    *        A function that returns true when a match is found.
    * @return Scope | Variable | Property
    *         The last visible scope, variable or property, or null if nothing
    *         is found.
    */
-  _findInVisibleItemsReverse: function (aPredicate) {
+  _findInVisibleItemsReverse: function(aPredicate) {
     for (let i = this._store.length - 1; i >= 0; i--) {
       let scope = this._store[i];
       let result = scope._findInVisibleItemsReverse(aPredicate);
       if (result) {
         return result;
       }
     }
     return null;
@@ -628,42 +627,42 @@ VariablesView.prototype = {
   /**
    * Gets the scope at the specified index.
    *
    * @param number aIndex
    *        The scope's index.
    * @return Scope
    *         The scope if found, undefined if not.
    */
-  getScopeAtIndex: function (aIndex) {
+  getScopeAtIndex: function(aIndex) {
     return this._store[aIndex];
   },
 
   /**
    * Recursively searches this container for the scope, variable or property
    * displayed by the specified node.
    *
    * @param nsIDOMNode aNode
    *        The node to search for.
    * @return Scope | Variable | Property
    *         The matched scope, variable or property, or null if nothing is found.
    */
-  getItemForNode: function (aNode) {
+  getItemForNode: function(aNode) {
     return this._itemsByElement.get(aNode);
   },
 
   /**
    * Gets the scope owning a Variable or Property.
    *
    * @param Variable | Property
    *        The variable or property to retrieven the owner scope for.
    * @return Scope
    *         The owner scope.
    */
-  getOwnerScopeForVariableOrProperty: function (aItem) {
+  getOwnerScopeForVariableOrProperty: function(aItem) {
     if (!aItem) {
       return null;
     }
     // If this is a Scope, return it.
     if (!(aItem instanceof Variable)) {
       return aItem;
     }
     // If this is a Variable or Property, find its owner scope.
@@ -677,78 +676,78 @@ VariablesView.prototype = {
    * Gets the parent scopes for a specified Variable or Property.
    * The returned list will not include the owner scope.
    *
    * @param Variable | Property
    *        The variable or property for which to find the parent scopes.
    * @return array
    *         A list of parent Scopes.
    */
-  getParentScopesForVariableOrProperty: function (aItem) {
+  getParentScopesForVariableOrProperty: function(aItem) {
     let scope = this.getOwnerScopeForVariableOrProperty(aItem);
     return this._store.slice(0, Math.max(this._store.indexOf(scope), 0));
   },
 
   /**
    * Gets the currently focused scope, variable or property in this view.
    *
    * @return Scope | Variable | Property
    *         The focused scope, variable or property, or null if nothing is found.
    */
-  getFocusedItem: function () {
+  getFocusedItem: function() {
     let focused = this.document.commandDispatcher.focusedElement;
     return this.getItemForNode(focused);
   },
 
   /**
    * Focuses the first visible scope, variable, or property in this container.
    */
-  focusFirstVisibleItem: function () {
+  focusFirstVisibleItem: function() {
     let focusableItem = this._findInVisibleItems(item => item.focusable);
     if (focusableItem) {
       this._focusItem(focusableItem);
     }
     this._parent.scrollTop = 0;
     this._parent.scrollLeft = 0;
   },
 
   /**
    * Focuses the last visible scope, variable, or property in this container.
    */
-  focusLastVisibleItem: function () {
+  focusLastVisibleItem: function() {
     let focusableItem = this._findInVisibleItemsReverse(item => item.focusable);
     if (focusableItem) {
       this._focusItem(focusableItem);
     }
     this._parent.scrollTop = this._parent.scrollHeight;
     this._parent.scrollLeft = 0;
   },
 
   /**
    * Focuses the next scope, variable or property in this view.
    */
-  focusNextItem: function () {
+  focusNextItem: function() {
     this.focusItemAtDelta(+1);
   },
 
   /**
    * Focuses the previous scope, variable or property in this view.
    */
-  focusPrevItem: function () {
+  focusPrevItem: function() {
     this.focusItemAtDelta(-1);
   },
 
   /**
    * Focuses another scope, variable or property in this view, based on
    * the index distance from the currently focused item.
    *
    * @param number aDelta
    *        A scalar specifying by how many items should the selection change.
    */
-  focusItemAtDelta: function (aDelta) {
+  focusItemAtDelta: function(aDelta) {
     let direction = aDelta > 0 ? "advanceFocus" : "rewindFocus";
     let distance = Math.abs(Math[aDelta > 0 ? "ceil" : "floor"](aDelta));
     while (distance--) {
       if (!this._focusChange(direction)) {
         break; // Out of bounds.
       }
     }
   },
@@ -757,17 +756,17 @@ VariablesView.prototype = {
    * Focuses the next or previous scope, variable or property in this view.
    *
    * @param string aDirection
    *        Either "advanceFocus" or "rewindFocus".
    * @return boolean
    *         False if the focus went out of bounds and the first or last element
    *         in this view was focused instead.
    */
-  _focusChange: function (aDirection) {
+  _focusChange: function(aDirection) {
     let commandDispatcher = this.document.commandDispatcher;
     let prevFocusedElement = commandDispatcher.focusedElement;
     let currFocusedItem = null;
 
     do {
       commandDispatcher.suppressFocusScroll = true;
       commandDispatcher[aDirection]();
 
@@ -788,32 +787,32 @@ VariablesView.prototype = {
    *
    * @param aItem Scope | Variable | Property
    *        The item to focus.
    * @param boolean aCollapseFlag
    *        True if the focused item should also be collapsed.
    * @return boolean
    *         True if the item was successfully focused.
    */
-  _focusItem: function (aItem, aCollapseFlag) {
+  _focusItem: function(aItem, aCollapseFlag) {
     if (!aItem.focusable) {
       return false;
     }
     if (aCollapseFlag) {
       aItem.collapse();
     }
     aItem._target.focus();
     this.boxObject.ensureElementIsVisible(aItem._arrow);
     return true;
   },
 
   /**
    * Listener handling a key down event on the view.
    */
-  _onViewKeyDown: function (e) {
+  _onViewKeyDown: function(e) {
     let item = this.getFocusedItem();
 
     // Prevent scrolling when pressing navigation keys.
     ViewHelpers.preventScrolling(e);
 
     switch (e.keyCode) {
       case KeyCodes.DOM_VK_C:
         // Copy current selection to clipboard.
@@ -895,17 +894,16 @@ VariablesView.prototype = {
         // Delete the Variable or Property if allowed.
         if (item instanceof Variable) {
           item._onDelete(e);
         }
         return;
 
       case KeyCodes.DOM_VK_INSERT:
         item._onAddProperty(e);
-        return;
     }
   },
 
   /**
    * Sets the text displayed in this container when there are no available items.
    * @param string aValue
    */
   set emptyText(aValue) {
@@ -914,33 +912,33 @@ VariablesView.prototype = {
     }
     this._emptyTextValue = aValue;
     this._appendEmptyNotice();
   },
 
   /**
    * Creates and appends a label signaling that this container is empty.
    */
-  _appendEmptyNotice: function () {
+  _appendEmptyNotice: function() {
     if (this._emptyTextNode || !this._emptyTextValue) {
       return;
     }
 
     let label = this.document.createElement("label");
     label.className = "variables-view-empty-notice";
     label.setAttribute("value", this._emptyTextValue);
 
     this._parent.appendChild(label);
     this._emptyTextNode = label;
   },
 
   /**
    * Removes the label signaling that this container is empty.
    */
-  _removeEmptyNotice: function () {
+  _removeEmptyNotice: function() {
     if (!this._emptyTextNode) {
       return;
     }
 
     this._parent.removeChild(this._emptyTextNode);
     this._emptyTextNode = null;
   },
 
@@ -1057,50 +1055,50 @@ VariablesView.NON_SORTABLE_CLASSES = [
 ];
 
 /**
  * Determine whether an object's properties should be sorted based on its class.
  *
  * @param string aClassName
  *        The class of the object.
  */
-VariablesView.isSortable = function (aClassName) {
+VariablesView.isSortable = function(aClassName) {
   return !VariablesView.NON_SORTABLE_CLASSES.includes(aClassName);
 };
 
 /**
  * Generates the string evaluated when performing simple value changes.
  *
  * @param Variable | Property aItem
  *        The current variable or property.
  * @param string aCurrentString
  *        The trimmed user inputted string.
  * @param string aPrefix [optional]
  *        Prefix for the symbolic name.
  * @return string
  *         The string to be evaluated.
  */
-VariablesView.simpleValueEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
+VariablesView.simpleValueEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
   return aPrefix + aItem.symbolicName + "=" + aCurrentString;
 };
 
 /**
  * Generates the string evaluated when overriding getters and setters with
  * plain values.
  *
  * @param Property aItem
  *        The current getter or setter property.
  * @param string aCurrentString
  *        The trimmed user inputted string.
  * @param string aPrefix [optional]
  *        Prefix for the symbolic name.
  * @return string
  *         The string to be evaluated.
  */
-VariablesView.overrideValueEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
+VariablesView.overrideValueEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
   let property = escapeString(aItem._nameString);
   let parent = aPrefix + aItem.ownerView.symbolicName || "this";
 
   return "Object.defineProperty(" + parent + "," + property + "," +
     "{ value: " + aCurrentString +
     ", enumerable: " + parent + ".propertyIsEnumerable(" + property + ")" +
     ", configurable: true" +
     ", writable: true" +
@@ -1114,17 +1112,17 @@ VariablesView.overrideValueEvalMacro = f
  *        The current getter or setter property.
  * @param string aCurrentString
  *        The trimmed user inputted string.
  * @param string aPrefix [optional]
  *        Prefix for the symbolic name.
  * @return string
  *         The string to be evaluated.
  */
-VariablesView.getterOrSetterEvalMacro = function (aItem, aCurrentString, aPrefix = "") {
+VariablesView.getterOrSetterEvalMacro = function(aItem, aCurrentString, aPrefix = "") {
   let type = aItem._nameString;
   let propertyObject = aItem.ownerView;
   let parentObject = propertyObject.ownerView;
   let property = escapeString(propertyObject._nameString);
   let parent = aPrefix + parentObject.symbolicName || "this";
 
   switch (aCurrentString) {
     case "":
@@ -1160,23 +1158,21 @@ VariablesView.getterOrSetterEvalMacro = 
       // Wrap statements inside a function declaration if not already wrapped.
       if (!aCurrentString.startsWith("function")) {
         let header = "function(" + (type == "set" ? "value" : "") + ")";
         let body = "";
         // If there's a return statement explicitly written, always use the
         // standard function definition syntax
         if (aCurrentString.includes("return ")) {
           body = "{" + aCurrentString + "}";
-        }
-        // If block syntax is used, use the whole string as the function body.
-        else if (aCurrentString.startsWith("{")) {
+        } else if (aCurrentString.startsWith("{")) {
+          // If block syntax is used, use the whole string as the function body.
           body = aCurrentString;
-        }
-        // Prefer an expression closure.
-        else {
+        } else {
+          // Prefer an expression closure.
           body = "(" + aCurrentString + ")";
         }
         aCurrentString = header + body;
       }
 
       // Determine if a new getter or setter should be defined.
       let defineType = type == "get" ? "__defineGetter__" : "__defineSetter__";
 
@@ -1190,27 +1186,26 @@ VariablesView.getterOrSetterEvalMacro = 
 };
 
 /**
  * Function invoked when a getter or setter is deleted.
  *
  * @param Property aItem
  *        The current getter or setter property.
  */
-VariablesView.getterOrSetterDeleteCallback = function (aItem) {
+VariablesView.getterOrSetterDeleteCallback = function(aItem) {
   aItem._disable();
 
   // Make sure the right getter/setter to value override macro is applied
   // to the target object.
   aItem.ownerView.eval(aItem, "");
 
   return true; // Don't hide the element.
 };
 
-
 /**
  * A Scope is an object holding Variable instances.
  * Iterable via "for (let [name, variable] of instance) { }".
  *
  * @param VariablesView aView
  *        The view to contain this scope.
  * @param string aName
  *        The scope's name.
@@ -1267,17 +1262,17 @@ Scope.prototype = {
    *        The name of the new Property.
    * @param object aDescriptor
    *        The variable's descriptor.
    * @param object aOptions
    *        Options of the form accepted by addItem.
    * @return Variable
    *         The newly created child Variable.
    */
-  _createChild: function (aName, aDescriptor, aOptions) {
+  _createChild: function(aName, aDescriptor, aOptions) {
     return new Variable(this, aName, aDescriptor, aOptions);
   },
 
   /**
    * Adds a child to contain any inspected properties.
    *
    * @param string aName
    *        The child's name.
@@ -1303,17 +1298,17 @@ Scope.prototype = {
    *        * boolean internalItem  true if the item is internally generated.
    *                           This is used for special variables
    *                           like <return> or <exception> and distinguishes
    *                           them from ordinary properties that happen
    *                           to have the same name
    * @return Variable
    *         The newly created Variable instance, null if it already exists.
    */
-  addItem: function (aName, aDescriptor = {}, aOptions = {}) {
+  addItem: function(aName, aDescriptor = {}, aOptions = {}) {
     let {relaxed} = aOptions;
     if (this._store.has(aName) && !relaxed) {
       return this._store.get(aName);
     }
 
     let child = this._createChild(aName, aDescriptor, aOptions);
     this._store.set(aName, child);
     this._variablesView._itemsByElement.set(child._target, child);
@@ -1339,17 +1334,17 @@ Scope.prototype = {
    *                 someProp5: { value: { type: "object", class: "Object" } },
    *                 someProp6: { get: { type: "object", class: "Function" },
    *                              set: { type: "undefined" } } }
    * @param object aOptions [optional]
    *        Additional options for adding the properties. Supported options:
    *        - sorted: true to sort all the properties before adding them
    *        - callback: function invoked after each item is added
    */
-  addItems: function (aItems, aOptions = {}) {
+  addItems: function(aItems, aOptions = {}) {
     let names = Object.keys(aItems);
 
     // Sort all of the properties before adding them, if preferred.
     if (aOptions.sorted) {
       names.sort(this._naturalSort);
     }
 
     // Add the properties to the current scope.
@@ -1361,17 +1356,17 @@ Scope.prototype = {
         aOptions.callback(item, descriptor && descriptor.value);
       }
     }
   },
 
   /**
    * Remove this Scope from its parent and remove all children recursively.
    */
-  remove: function () {
+  remove: function() {
     let view = this._variablesView;
     view._store.splice(view._store.indexOf(this), 1);
     view._itemsByElement.delete(this._target);
     view._currHierarchy.delete(this._nameString);
 
     this._target.remove();
 
     for (let variable of this._store.values()) {
@@ -1382,30 +1377,30 @@ Scope.prototype = {
   /**
    * Gets the variable in this container having the specified name.
    *
    * @param string aName
    *        The name of the variable to get.
    * @return Variable
    *         The matched variable, or null if nothing is found.
    */
-  get: function (aName) {
+  get: function(aName) {
     return this._store.get(aName);
   },
 
   /**
    * Recursively searches for the variable or property in this container
    * displayed by the specified node.
    *
    * @param nsIDOMNode aNode
    *        The node to search for.
    * @return Variable | Property
    *         The matched variable or property, or null if nothing is found.
    */
-  find: function (aNode) {
+  find: function(aNode) {
     for (let [, variable] of this._store) {
       let match;
       if (variable._target == aNode) {
         match = variable;
       } else {
         match = variable.find(aNode);
       }
       if (match) {
@@ -1419,70 +1414,70 @@ Scope.prototype = {
    * Determines if this scope is a direct child of a parent variables view,
    * scope, variable or property.
    *
    * @param VariablesView | Scope | Variable | Property
    *        The parent to check.
    * @return boolean
    *         True if the specified item is a direct child, false otherwise.
    */
-  isChildOf: function (aParent) {
+  isChildOf: function(aParent) {
     return this.ownerView == aParent;
   },
 
   /**
    * Determines if this scope is a descendant of a parent variables view,
    * scope, variable or property.
    *
    * @param VariablesView | Scope | Variable | Property
    *        The parent to check.
    * @return boolean
    *         True if the specified item is a descendant, false otherwise.
    */
-  isDescendantOf: function (aParent) {
+  isDescendantOf: function(aParent) {
     if (this.isChildOf(aParent)) {
       return true;
     }
 
     // Recurse to parent if it is a Scope, Variable, or Property.
     if (this.ownerView instanceof Scope) {
       return this.ownerView.isDescendantOf(aParent);
     }
 
     return false;
   },
 
   /**
    * Shows the scope.
    */
-  show: function () {
+  show: function() {
     this._target.hidden = false;
     this._isContentVisible = true;
 
     if (this.onshow) {
       this.onshow(this);
     }
   },
 
   /**
    * Hides the scope.
    */
-  hide: function () {
+  hide: function() {
     this._target.hidden = true;
     this._isContentVisible = false;
 
     if (this.onhide) {
       this.onhide(this);
     }
   },
 
   /**
    * Expands the scope, showing all the added details.
    */
-  expand: function () {
+  expand: function() {
     if (this._isExpanded || this._isLocked) {
       return;
     }
     if (this._variablesView._enumVisible) {
       this._openEnum();
     }
     if (this._variablesView._nonEnumVisible) {
       Services.tm.dispatchToMainThread({ run: this._openNonEnum });
@@ -1496,34 +1491,34 @@ Scope.prototype = {
       // and attributes are available. (Mostly used for tests)
       return this.onexpand(this);
     }
   },
 
   /**
    * Collapses the scope, hiding all the added details.
    */
-  collapse: function () {
+  collapse: function() {
     if (!this._isExpanded || this._isLocked) {
       return;
     }
     this._arrow.removeAttribute("open");
     this._enum.removeAttribute("open");
     this._nonenum.removeAttribute("open");
     this._isExpanded = false;
 
     if (this.oncollapse) {
       this.oncollapse(this);
     }
   },
 
   /**
    * Toggles between the scope's collapsed and expanded state.
    */
-  toggle: function (e) {
+  toggle: function(e) {
     if (e && e.button != 0) {
       // Only allow left-click to trigger this event.
       return;
     }
     this.expanded ^= 1;
 
     // Make sure the scope and its contents are visibile.
     for (let [, variable] of this._store) {
@@ -1533,29 +1528,29 @@ Scope.prototype = {
     if (this.ontoggle) {
       this.ontoggle(this);
     }
   },
 
   /**
    * Shows the scope's title header.
    */
-  showHeader: function () {
+  showHeader: function() {
     if (this._isHeaderVisible || !this._nameString) {
       return;
     }
     this._target.removeAttribute("untitled");
     this._isHeaderVisible = true;
   },
 
   /**
    * Hides the scope's title header.
    * This action will automatically expand the scope.
    */
-  hideHeader: function () {
+  hideHeader: function() {
     if (!this._isHeaderVisible) {
       return;
     }
     this.expand();
     this._target.setAttribute("untitled", "");
     this._isHeaderVisible = false;
   },
 
@@ -1564,37 +1559,37 @@ Scope.prototype = {
    * This only needs to compare non-numbers since it is dealing with an array
    * which numeric-based indices are placed in order.
    *
    * @param string a
    * @param string b
    * @return number
    *         -1 if a is less than b, 0 if no change in order, +1 if a is greater than 0
    */
-  _naturalSort: function (a, b) {
+  _naturalSort: function(a, b) {
     if (isNaN(parseFloat(a)) && isNaN(parseFloat(b))) {
       return a < b ? -1 : 1;
     }
   },
 
   /**
    * Shows the scope's expand/collapse arrow.
    */
-  showArrow: function () {
+  showArrow: function() {
     if (this._isArrowVisible) {
       return;
     }
     this._arrow.removeAttribute("invisible");
     this._isArrowVisible = true;
   },
 
   /**
    * Hides the scope's expand/collapse arrow.
    */
-  hideArrow: function () {
+  hideArrow: function() {
     if (!this._isArrowVisible) {
       return;
     }
     this._arrow.setAttribute("invisible", "");
     this._isArrowVisible = false;
   },
 
   /**
@@ -1699,37 +1694,37 @@ Scope.prototype = {
       }
     }
     return true;
   },
 
   /**
    * Focus this scope.
    */
-  focus: function () {
+  focus: function() {
     this._variablesView._focusItem(this);
   },
 
   /**
    * Adds an event listener for a certain event on this scope's title.
    * @param string aName
    * @param function aCallback
    * @param boolean aCapture
    */
-  addEventListener: function (aName, aCallback, aCapture) {
+  addEventListener: function(aName, aCallback, aCapture) {
     this._title.addEventListener(aName, aCallback, aCapture);
   },
 
   /**
    * Removes an event listener for a certain event on this scope's title.
    * @param string aName
    * @param function aCallback
    * @param boolean aCapture
    */
-  removeEventListener: function (aName, aCallback, aCapture) {
+  removeEventListener: function(aName, aCallback, aCapture) {
     this._title.removeEventListener(aName, aCallback, aCapture);
   },
 
   /**
    * Gets the id associated with this item.
    * @return string
    */
   get id() {
@@ -1771,17 +1766,17 @@ Scope.prototype = {
   /**
    * Initializes this scope's id, view and binds event listeners.
    *
    * @param string aName
    *        The scope's name.
    * @param object aFlags [optional]
    *        Additional options or flags for this scope.
    */
-  _init: function (aName, aFlags) {
+  _init: function(aName, aFlags) {
     this._idString = generateId(this._nameString = aName);
     this._displayScope(aName, `${this.targetClassName} ${aFlags.customClass}`,
                        "devtools-toolbar");
     this._addEventListeners();
     this.parentNode.appendChild(this._target);
   },
 
   /**
@@ -1789,17 +1784,17 @@ Scope.prototype = {
    *
    * @param string aName
    *        The scope's name.
    * @param string aTargetClassName
    *        A custom class name for this scope's target element.
    * @param string aTitleClassName [optional]
    *        A custom class name for this scope's title element.
    */
-  _displayScope: function (aName = "", aTargetClassName, aTitleClassName = "") {
+  _displayScope: function(aName = "", aTargetClassName, aTitleClassName = "") {
     let document = this.document;
 
     let element = this._target = document.createElement("vbox");
     element.id = this._idString;
     element.className = aTargetClassName;
 
     let arrow = this._arrow = document.createElement("hbox");
     arrow.className = "arrow theme-twisty";
@@ -1824,47 +1819,47 @@ Scope.prototype = {
     element.appendChild(title);
     element.appendChild(enumerable);
     element.appendChild(nonenum);
   },
 
   /**
    * Adds the necessary event listeners for this scope.
    */
-  _addEventListeners: function () {
+  _addEventListeners: function() {
     this._title.addEventListener("mousedown", this._onClick);
   },
 
   /**
    * The click listener for this scope's title.
    */
-  _onClick: function (e) {
+  _onClick: function(e) {
     if (this.editing ||
         e.button != 0 ||
         e.target == this._editNode ||
         e.target == this._deleteNode ||
         e.target == this._addPropertyNode) {
       return;
     }
     this.toggle();
     this.focus();
   },
 
   /**
    * Opens the enumerable items container.
    */
-  _openEnum: function () {
+  _openEnum: function() {
     this._arrow.setAttribute("open", "");
     this._enum.setAttribute("open", "");
   },
 
   /**
    * Opens the non-enumerable items container.
    */
-  _openNonEnum: function () {
+  _openNonEnum: function() {
     this._nonenum.setAttribute("open", "");
   },
 
   /**
    * Specifies if enumerable properties and variables should be displayed.
    * @param boolean aFlag
    */
   set _enumVisible(aFlag) {
@@ -1903,29 +1898,28 @@ Scope.prototype = {
 
   /**
    * Performs a case insensitive search for variables or properties matching
    * the query, and hides non-matched items.
    *
    * @param string aLowerCaseQuery
    *        The lowercased name of the variable or property to search for.
    */
-  _performSearch: function (aLowerCaseQuery) {
+  _performSearch: function(aLowerCaseQuery) {
     for (let [, variable] of this._store) {
       let currentObject = variable;
       let lowerCaseName = variable._nameString.toLowerCase();
       let lowerCaseValue = variable._valueString.toLowerCase();
 
       // Non-matched variables or properties require a corresponding attribute.
       if (!lowerCaseName.includes(aLowerCaseQuery) &&
           !lowerCaseValue.includes(aLowerCaseQuery)) {
         variable._matched = false;
-      }
-      // Variable or property is matched.
-      else {
+      } else {
+        // Variable or property is matched.
         variable._matched = true;
 
         // If the variable was ever expanded, there's a possibility it may
         // contain some matched properties, so make sure they're visible
         // ("expand downwards").
         if (variable._store.size) {
           variable.expand();
         }
@@ -1970,17 +1964,17 @@ Scope.prototype = {
    * the non-enumerable children (since they are presented in separate groups).
    *
    * @param function aPredicate
    *        A function that returns true when a match is found.
    * @return Scope | Variable | Property
    *         The first visible scope, variable or property, or null if nothing
    *         is found.
    */
-  _findInVisibleItems: function (aPredicate) {
+  _findInVisibleItems: function(aPredicate) {
     if (aPredicate(this)) {
       return this;
     }
 
     if (this._isExpanded) {
       if (this._variablesView._enumVisible) {
         for (let item of this._enumItems) {
           let result = item._findInVisibleItems(aPredicate);
@@ -2011,17 +2005,17 @@ Scope.prototype = {
    * finally tests itself.
    *
    * @param function aPredicate
    *        A function that returns true when a match is found.
    * @return Scope | Variable | Property
    *         The last visible scope, variable or property, or null if nothing
    *         is found.
    */
-  _findInVisibleItemsReverse: function (aPredicate) {
+  _findInVisibleItemsReverse: function(aPredicate) {
     if (this._isExpanded) {
       if (this._variablesView._nonEnumVisible) {
         for (let i = this._nonEnumItems.length - 1; i >= 0; i--) {
           let item = this._nonEnumItems[i];
           let result = item._findInVisibleItemsReverse(aPredicate);
           if (result) {
             return result;
           }
@@ -2194,24 +2188,24 @@ Variable.prototype = extend(Scope.protot
    *        The name of the new Property.
    * @param object aDescriptor
    *        The property's descriptor.
    * @param object aOptions
    *        Options of the form accepted by Scope.addItem
    * @return Property
    *         The newly created child Property.
    */
-  _createChild: function (aName, aDescriptor, aOptions) {
+  _createChild: function(aName, aDescriptor, aOptions) {
     return new Property(this, aName, aDescriptor, aOptions);
   },
 
   /**
    * Remove this Variable from its parent and remove all children recursively.
    */
-  remove: function () {
+  remove: function() {
     if (this._linkedToInspector) {
       this.unhighlightDomNode();
       this._valueLabel.removeEventListener("mouseover", this.highlightDomNode);
       this._valueLabel.removeEventListener("mouseout", this.unhighlightDomNode);
       this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector);
     }
 
     this.ownerView._store.delete(this._nameString);
@@ -2230,17 +2224,17 @@ Variable.prototype = extend(Scope.protot
    *
    * @param object aObject
    *        The raw object you want to display.
    * @param object aOptions [optional]
    *        Additional options for adding the properties. Supported options:
    *        - sorted: true to sort all the properties before adding them
    *        - expanded: true to expand all the properties after adding them
    */
-  populate: function (aObject, aOptions = {}) {
+  populate: function(aObject, aOptions = {}) {
     // Retrieve the properties only once.
     if (this._fetched) {
       return;
     }
     this._fetched = true;
 
     let propertyNames = Object.getOwnPropertyNames(aObject);
     let prototype = Object.getPrototypeOf(aObject);
@@ -2276,34 +2270,34 @@ Variable.prototype = extend(Scope.protot
    * properties of an object
    *
    * @param Variable | Property aVar
    *        The target variable to populate.
    * @param object aObject [optional]
    *        The raw object you want to display. If unspecified, the object is
    *        assumed to be defined in a _sourceValue property on the target.
    */
-  _populateTarget: function (aVar, aObject = aVar._sourceValue) {
+  _populateTarget: function(aVar, aObject = aVar._sourceValue) {
     aVar.populate(aObject);
   },
 
   /**
    * Adds a property for this variable based on a raw value descriptor.
    *
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
    *        Specifies the exact property descriptor as returned by a call to
    *        Object.getOwnPropertyDescriptor.
    * @param object aValue
    *        The raw property value you want to display.
    * @return Property
    *         The newly added property instance.
    */
-  _addRawValueProperty: function (aName, aDescriptor, aValue) {
+  _addRawValueProperty: function(aName, aDescriptor, aValue) {
     let descriptor = Object.create(aDescriptor);
     descriptor.value = VariablesView.getGrip(aValue);
 
     let propertyItem = this.addItem(aName, descriptor);
     propertyItem._sourceValue = aValue;
 
     // Add an 'onexpand' callback for the property, lazily handling
     // the addition of new child properties.
@@ -2319,17 +2313,17 @@ Variable.prototype = extend(Scope.protot
    * @param string aName
    *        The property's name.
    * @param object aDescriptor
    *        Specifies the exact property descriptor as returned by a call to
    *        Object.getOwnPropertyDescriptor.
    * @return Property
    *         The newly added property instance.
    */
-  _addRawNonValueProperty: function (aName, aDescriptor) {
+  _addRawNonValueProperty: function(aName, aDescriptor) {
     let descriptor = Object.create(aDescriptor);
     descriptor.get = VariablesView.getGrip(aDescriptor.get);
     descriptor.set = VariablesView.getGrip(aDescriptor.set);
 
     return this.addItem(aName, descriptor);
   },
 
   /**
@@ -2369,17 +2363,17 @@ Variable.prototype = extend(Scope.protot
   },
 
   /**
    * Build this variable's path to the topmost scope in form of an array of
    * strings, one for each segment of the path.
    * For example, a symbolic path may look like ["0", "foo", "bar"].
    * @return array
    */
-  _buildSymbolicPath: function (path = []) {
+  _buildSymbolicPath: function(path = []) {
     if (this.name) {
       path.unshift(this.name);
       if (this.ownerView instanceof Variable) {
         return this.ownerView._buildSymbolicPath(path);
       }
     }
     return path;
   },
@@ -2420,17 +2414,17 @@ Variable.prototype = extend(Scope.protot
    *        Specifies the value and/or type & class of the variable.
    *        e.g. - 42
    *             - true
    *             - "nasu"
    *             - { type: "undefined" }
    *             - { type: "null" }
    *             - { type: "object", class: "Object" }
    */
-  setGrip: function (aGrip) {
+  setGrip: function(aGrip) {
     // Don't allow displaying grip information if there's no name available
     // or the grip is malformed.
     if (this._nameString === undefined || aGrip === undefined || aGrip === null) {
       return;
     }
     // Getters and setters should display grip information in sub-properties.
     if (this.getter || this.setter) {
       return;
@@ -2440,26 +2434,23 @@ Variable.prototype = extend(Scope.protot
     if (prevGrip) {
       this._valueLabel.classList.remove(VariablesView.getClass(prevGrip));
     }
     this._valueGrip = aGrip;
 
     if (aGrip && (aGrip.optimizedOut || aGrip.uninitialized || aGrip.missingArguments)) {
       if (aGrip.optimizedOut) {
         this._valueString = L10N.getStr("variablesViewOptimizedOut");
-      }
-      else if (aGrip.uninitialized) {
+      } else if (aGrip.uninitialized) {
         this._valueString = L10N.getStr("variablesViewUninitialized");
-      }
-      else if (aGrip.missingArguments) {
+      } else if (aGrip.missingArguments) {
         this._valueString = L10N.getStr("variablesViewMissingArgs");
       }
       this.eval = null;
-    }
-    else {
+    } else {
       this._valueString = VariablesView.getString(aGrip, {
         concise: true,
         noEllipsis: true,
       });
       this.eval = this.ownerView.eval;
     }
 
     this._valueClassName = VariablesView.getClass(aGrip);
@@ -2475,31 +2466,31 @@ Variable.prototype = extend(Scope.protot
   },
 
   /**
    * Marks this variable as overridden.
    *
    * @param boolean aFlag
    *        Whether this variable is overridden or not.
    */
-  setOverridden: function (aFlag) {
+  setOverridden: function(aFlag) {
     if (aFlag) {
       this._target.setAttribute("overridden", "");
     } else {
       this._target.removeAttribute("overridden");
     }
   },
 
   /**
    * Briefly flashes this variable.
    *
    * @param number aDuration [optional]
    *        An optional flash animation duration.
    */
-  flash: function (aDuration = ITEM_FLASH_DURATION) {
+  flash: function(aDuration = ITEM_FLASH_DURATION) {
     let fadeInDelay = this._variablesView.lazyEmptyDelay + 1;
     let fadeOutDelay = fadeInDelay + aDuration;
 
     setNamedTimeout("vview-flash-in" + this.absoluteName,
       fadeInDelay, () => this._target.setAttribute("changed", ""));
 
     setNamedTimeout("vview-flash-out" + this.absoluteName,
       fadeOutDelay, () => this._target.removeAttribute("changed"));
@@ -2508,17 +2499,17 @@ Variable.prototype = extend(Scope.protot
   /**
    * Initializes this variable's id, view and binds event listeners.
    *
    * @param string aName
    *        The variable's name.
    * @param object aDescriptor
    *        The variable's descriptor.
    */
-  _init: function (aName, aDescriptor) {
+  _init: function(aName, aDescriptor) {
     this._idString = generateId(this._nameString = aName);
     this._displayScope(aName, this.targetClassName);
     this._displayVariable();
     this._customizeVariable();
     this._prepareTooltips();
     this._setAttributes();
     this._addEventListeners();
 
@@ -2531,17 +2522,17 @@ Variable.prototype = extend(Scope.protot
       this.ownerView._nonenum.appendChild(this._target);
       this.ownerView._nonEnumItems.push(this);
     }
   },
 
   /**
    * Creates the necessary nodes for this variable.
    */
-  _displayVariable: function () {
+  _displayVariable: function() {
     let document = this.document;
     let descriptor = this._initialDescriptor;
 
     let separatorLabel = this._separatorLabel = document.createElement("label");
     separatorLabel.className = "plain separator";
     separatorLabel.setAttribute("value", this.separatorStr + " ");
 
     let valueLabel = this._valueLabel = document.createElement("label");
@@ -2570,20 +2561,19 @@ Variable.prototype = extend(Scope.protot
       // Changing getter/setter names is never allowed.
       this.switch = null;
 
       // Getter/setter properties require special handling when it comes to
       // evaluation and deletion.
       if (this.ownerView.eval) {
         this.delete = VariablesView.getterOrSetterDeleteCallback;
         this.evaluationMacro = VariablesView.overrideValueEvalMacro;
-      }
-      // Deleting getters and setters individually is not allowed if no
-      // evaluation method is provided.
-      else {
+      } else {
+        // Deleting getters and setters individually is not allowed if no
+        // evaluation method is provided.
         this.delete = null;
         this.evaluationMacro = null;
       }
 
       let getter = this.addItem("get", { value: descriptor.get });
       let setter = this.addItem("set", { value: descriptor.set });
       getter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
       setter.evaluationMacro = VariablesView.getterOrSetterEvalMacro;
@@ -2592,17 +2582,17 @@ Variable.prototype = extend(Scope.protot
       setter.hideArrow();
       this.expand();
     }
   },
 
   /**
    * Adds specific nodes for this variable based on custom flags.
    */
-  _customizeVariable: function () {
+  _customizeVariable: function() {
     let ownerView = this.ownerView;
     let descriptor = this._initialDescriptor;
 
     if (ownerView.eval && this.getter || this.setter) {
       let editNode = this._editNode = this.document.createElement("toolbarbutton");
       editNode.className = "plain variables-view-edit";
       editNode.addEventListener("mousedown", this._onEdit.bind(this));
       this._title.insertBefore(editNode, this._spacer);
@@ -2664,24 +2654,24 @@ Variable.prototype = extend(Scope.protot
         this._title.appendChild(nonExtensibleLabel);
       }
     }
   },
 
   /**
    * Prepares all tooltips for this variable.
    */
-  _prepareTooltips: function () {
+  _prepareTooltips: function() {
     this._target.addEventListener("mouseover", this._setTooltips);
   },
 
   /**
    * Sets all tooltips for this variable.
    */
-  _setTooltips: function () {
+  _setTooltips: function() {
     this._target.removeEventListener("mouseover", this._setTooltips);
 
     let ownerView = this.ownerView;
     if (ownerView.preventDescriptorModifiers) {
       return;
     }
 
     let tooltip = this.document.createElement("tooltip");
@@ -2726,29 +2716,29 @@ Variable.prototype = extend(Scope.protot
     return this._variablesView.toolbox;
   },
 
   /**
    * Checks if this variable is a DOMNode and is part of a variablesview that
    * has been linked to the toolbox, so that highlighting and jumping to the
    * inspector can be done.
    */
-  _isLinkableToInspector: function () {
+  _isLinkableToInspector: function() {
     let isDomNode = this._valueGrip && this._valueGrip.preview.kind === "DOMNode";
     let hasBeenLinked = this._linkedToInspector;
     let hasToolbox = !!this.toolbox;
 
     return isDomNode && !hasBeenLinked && hasToolbox;
   },
 
   /**
    * If the variable is a DOMNode, and if a toolbox is set, then link it to the
    * inspector (highlight on hover, and jump to markup-view on click)
    */
-  _linkToInspector: function () {
+  _linkToInspector: function() {
     if (!this._isLinkableToInspector()) {
       return;
     }
 
     // Listen to value mouseover/click events to highlight and jump
     this._valueLabel.addEventListener("mouseover", this.highlightDomNode);
     this._valueLabel.addEventListener("mouseout", this.unhighlightDomNode);
 
@@ -2763,24 +2753,24 @@ Variable.prototype = extend(Scope.protot
 
   /**
    * In case this variable is a DOMNode and part of a variablesview that has been
    * linked to the toolbox's inspector, then select the corresponding node in
    * the inspector, and switch the inspector tool in the toolbox
    * @return a promise that resolves when the node is selected and the inspector
    * has been switched to and is ready
    */
-  openNodeInInspector: function (event) {
+  openNodeInInspector: function(event) {
     if (!this.toolbox) {
       return promise.reject(new Error("Toolbox not available"));
     }
 
     event && event.stopPropagation();
 
-    return (async function () {
+    return (async function() {
       await this.toolbox.initInspector();
 
       let nodeFront = this._nodeFront;
       if (!nodeFront) {
         nodeFront = await this.toolbox.walker.getNodeActorFromObjectActor(this._valueGrip.actor);
       }
 
       if (nodeFront) {
@@ -2793,17 +2783,17 @@ Variable.prototype = extend(Scope.protot
       }
     }.bind(this))();
   },
 
   /**
    * In case this variable is a DOMNode and part of a variablesview that has been
    * linked to the toolbox's inspector, then highlight the corresponding node
    */
-  highlightDomNode: function () {
+  highlightDomNode: function() {
     if (this.toolbox) {
       if (this._nodeFront) {
         // If the nodeFront has been retrieved before, no need to ask the server
         // again for it
         this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront);
         return;
       }
 
@@ -2812,28 +2802,28 @@ Variable.prototype = extend(Scope.protot
       });
     }
   },
 
   /**
    * Unhighlight a previously highlit node
    * @see highlightDomNode
    */
-  unhighlightDomNode: function () {
+  unhighlightDomNode: function() {
     if (this.toolbox) {
       this.toolbox.highlighterUtils.unhighlight();
     }
   },
 
   /**
    * Sets a variable's configurable, enumerable and writable attributes,
    * and specifies if it's a 'this', '<exception>', '<return>' or '__proto__'
    * reference.
    */
-  _setAttributes: function () {
+  _setAttributes: function() {
     let ownerView = this.ownerView;
     if (ownerView.preventDescriptorModifiers) {
       return;
     }
 
     let descriptor = this._initialDescriptor;
     let target = this._target;
     let name = this._nameString;
@@ -2865,48 +2855,45 @@ Variable.prototype = extend(Scope.protot
     }
 
     if (descriptor && "getterValue" in descriptor) {
       target.setAttribute("safe-getter", "");
     }
 
     if (name == "this") {
       target.setAttribute("self", "");
-    }
-    else if (this._internalItem && name == "<exception>") {
+    } else if (this._internalItem && name == "<exception>") {
       target.setAttribute("exception", "");
       target.setAttribute("pseudo-item", "");
-    }
-    else if (this._internalItem && name == "<return>") {
+    } else if (this._internalItem && name == "<return>") {
       target.setAttribute("return", "");
       target.setAttribute("pseudo-item", "");
-    }
-    else if (name == "__proto__") {
+    } else if (name == "__proto__") {
       target.setAttribute("proto", "");
       target.setAttribute("pseudo-item", "");
     }
 
     if (Object.keys(descriptor).length == 0) {
       target.setAttribute("pseudo-item", "");
     }
   },
 
   /**
    * Adds the necessary event listeners for this variable.
    */
-  _addEventListeners: function () {
+  _addEventListeners: function() {
     this._name.addEventListener("dblclick", this._activateNameInput);
     this._valueLabel.addEventListener("mousedown", this._activateValueInput);
     this._title.addEventListener("mousedown", this._onClick);
   },
 
   /**
    * Makes this variable's name editable.
    */
-  _activateNameInput: function (e) {
+  _activateNameInput: function(e) {
     if (!this._variablesView.alignedValues) {
       this._separatorLabel.hidden = true;
       this._valueLabel.hidden = true;
     }
 
     EditableName.create(this, {
       onSave: aKey => {
         if (!this._variablesView.preventDisableOnChange) {
@@ -2921,34 +2908,34 @@ Variable.prototype = extend(Scope.protot
         }
       }
     }, e);
   },
 
   /**
    * Makes this variable's value editable.
    */
-  _activateValueInput: function (e) {
+  _activateValueInput: function(e) {
     EditableValue.create(this, {
       onSave: aString => {
         if (this._linkedToInspector) {
           this.unhighlightDomNode();
         }
         if (!this._variablesView.preventDisableOnChange) {
           this._disable();
         }
         this.ownerView.eval(this, aString);
       }
     }, e);
   },
 
   /**
    * Disables this variable prior to a new name switch or value evaluation.
    */
-  _disable: function () {
+  _disable: function() {
     // Prevent the variable from being collapsed or expanded.
     this.hideArrow();
 
     // Hide any nodes that may offer information about the variable.
     for (let node of this._title.childNodes) {
       node.hidden = node != this._arrow && node != this._name;
     }
     this._enum.hidden = true;
@@ -2959,30 +2946,30 @@ Variable.prototype = extend(Scope.protot
    * The current macro used to generate the string evaluated when performing
    * a variable or property value change.
    */
   evaluationMacro: VariablesView.simpleValueEvalMacro,
 
   /**
    * The click listener for the edit button.
    */
-  _onEdit: function (e) {
+  _onEdit: function(e) {
     if (e.button != 0) {
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
     this._activateValueInput();
   },
 
   /**
    * The click listener for the delete button.
    */
-  _onDelete: function (e) {
+  _onDelete: function(e) {
     if ("button" in e && e.button != 0) {
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
 
     if (this.ownerView.delete) {
@@ -2990,17 +2977,17 @@ Variable.prototype = extend(Scope.protot
         this.hide();
       }
     }
   },
 
   /**
    * The click listener for the add property button.
    */
-  _onAddProperty: function (e) {
+  _onAddProperty: function(e) {
     if ("button" in e && e.button != 0) {
       return;
     }
 
     e.preventDefault();
     e.stopPropagation();
 
     this.expanded = true;
@@ -3102,31 +3089,31 @@ Variable.prototype[Symbol.iterator] =
 Property.prototype[Symbol.iterator] = function* () {
   yield* this._store;
 };
 
 /**
  * Forget everything recorded about added scopes, variables or properties.
  * @see VariablesView.commitHierarchy
  */
-VariablesView.prototype.clearHierarchy = function () {
+VariablesView.prototype.clearHierarchy = function() {
   this._prevHierarchy.clear();
   this._currHierarchy.clear();
 };
 
 /**
  * Perform operations on all the VariablesView Scopes, Variables and Properties
  * after you've added all the items you wanted.
  *
  * Calling this method is optional, and does the following:
  *   - styles the items overridden by other items in parent scopes
  *   - reopens the items which were previously expanded
  *   - flashes the items whose values changed
  */
-VariablesView.prototype.commitHierarchy = function () {
+VariablesView.prototype.commitHierarchy = function() {
   for (let [, currItem] of this._currHierarchy) {
     // Avoid performing expensive operations.
     if (this.commitHierarchyIgnoredItems[currItem._nameString]) {
       continue;
     }
     let overridden = this.isOverridden(currItem);
     if (overridden) {
       currItem.setOverridden(true);
@@ -3157,34 +3144,34 @@ VariablesView.prototype.commitHierarchyI
  * Checks if the an item was previously expanded, if it existed in a
  * previous hierarchy.
  *
  * @param Scope | Variable | Property aItem
  *        The item to verify.
  * @return boolean
  *         Whether the item was expanded.
  */
-VariablesView.prototype.wasExpanded = function (aItem) {
+VariablesView.prototype.wasExpanded = function(aItem) {
   if (!(aItem instanceof Scope)) {
     return false;
   }
   let prevItem = this._prevHierarchy.get(aItem.absoluteName || aItem._nameString);
   return prevItem ? prevItem._isExpanded : false;
 };
 
 /**
  * Checks if the an item's displayed value (a representation of the grip)
  * has changed, if it existed in a previous hierarchy.
  *
  * @param Variable | Property aItem
  *        The item to verify.
  * @return boolean
  *         Whether the item has changed.
  */
-VariablesView.prototype.hasChanged = function (aItem) {
+VariablesView.prototype.hasChanged = function(aItem) {
   // Only analyze Variables and Properties for displayed value changes.
   // Scopes are just collections of Variables and Properties and
   // don't have a "value", so they can't change.
   if (!(aItem instanceof Variable)) {
     return false;
   }
   let prevItem = this._prevHierarchy.get(aItem.absoluteName);
   return prevItem ? prevItem._valueString != aItem._valueString : false;
@@ -3194,17 +3181,17 @@ VariablesView.prototype.hasChanged = fun
  * Checks if the an item was previously expanded, if it existed in a
  * previous hierarchy.
  *
  * @param Scope | Variable | Property aItem
  *        The item to verify.
  * @return boolean
  *         Whether the item was expanded.
  */
-VariablesView.prototype.isOverridden = function (aItem) {
+VariablesView.prototype.isOverridden = function(aItem) {
   // Only analyze Variables for being overridden in different Scopes.
   if (!(aItem instanceof Variable) || aItem instanceof Property) {
     return false;
   }
   let currVariableName = aItem._nameString;
   let parentScopes = this.getParentScopesForVariableOrProperty(aItem);
 
   for (let otherScope of parentScopes) {
@@ -3219,17 +3206,17 @@ VariablesView.prototype.isOverridden = f
 
 /**
  * Returns true if the descriptor represents an undefined, null or
  * primitive value.
  *
  * @param object aDescriptor
  *        The variable's descriptor.
  */
-VariablesView.isPrimitive = function (aDescriptor) {
+VariablesView.isPrimitive = function(aDescriptor) {
   // For accessor property descriptors, the getter and setter need to be
   // contained in 'get' and 'set' properties.
   let getter = aDescriptor.get;
   let setter = aDescriptor.set;
   if (getter || setter) {
     return false;
   }
 
@@ -3258,17 +3245,17 @@ VariablesView.isPrimitive = function (aD
 };
 
 /**
  * Returns true if the descriptor represents an undefined value.
  *
  * @param object aDescriptor
  *        The variable's descriptor.
  */
-VariablesView.isUndefined = function (aDescriptor) {
+VariablesView.isUndefined = function(aDescriptor) {
   // For accessor property descriptors, the getter and setter need to be
   // contained in 'get' and 'set' properties.
   let getter = aDescriptor.get;
   let setter = aDescriptor.set;
   if (typeof getter == "object" && getter.type == "undefined" &&
       typeof setter == "object" && setter.type == "undefined") {
     return true;
   }
@@ -3284,17 +3271,17 @@ VariablesView.isUndefined = function (aD
 };
 
 /**
  * Returns true if the descriptor represents a falsy value.
  *
  * @param object aDescriptor
  *        The variable's descriptor.
  */
-VariablesView.isFalsy = function (aDescriptor) {
+VariablesView.isFalsy = function(aDescriptor) {
   // As described in the remote debugger protocol, the value grip
   // must be contained in a 'value' property.
   let grip = aDescriptor.value;
   if (typeof grip != "object") {
     return !grip;
   }
 
   // For convenience, undefined, null, NaN, and -0 are all considered types.
@@ -3310,29 +3297,29 @@ VariablesView.isFalsy = function (aDescr
 };
 
 /**
  * Returns true if the value is an instance of Variable or Property.
  *
  * @param any aValue
  *        The value to test.
  */
-VariablesView.isVariable = function (aValue) {
+VariablesView.isVariable = function(aValue) {
   return aValue instanceof Variable;
 };
 
 /**
  * Returns a standard grip for a value.
  *
  * @param any aValue
  *        The raw value to get a grip for.
  * @return any
  *         The value's grip.
  */
-VariablesView.getGrip = function (aValue) {
+VariablesView.getGrip = function(aValue) {
   switch (typeof aValue) {
     case "boolean":
     case "string":
       return aValue;
     case "number":
       if (aValue === Infinity) {
         return { type: "Infinity" };
       } else if (aValue === -Infinity) {
@@ -3343,20 +3330,22 @@ VariablesView.getGrip = function (aValue
         return { type: "-0" };
       }
       return aValue;
     case "undefined":
       // document.all is also "undefined"
       if (aValue === undefined) {
         return { type: "undefined" };
       }
+      // fall through
     case "object":
       if (aValue === null) {
         return { type: "null" };
       }
+      // fall through
     case "function":
       return { type: "object",
                class: WebConsoleUtils.getObjectClassName(aValue) };
     default:
       console.error("Failed to provide a grip for value of " + typeof value +
                     ": " + aValue);
       return null;
   }
@@ -3371,17 +3360,17 @@ VariablesView.getGrip = function (aValue
  *        Options:
  *        - concise: boolean that tells you want a concisely formatted string.
  *        - noStringQuotes: boolean that tells to not quote strings.
  *        - noEllipsis: boolean that tells to not add an ellipsis after the
  *        initial text of a longString.
  * @return string
  *         The formatted property string.
  */
-VariablesView.getString = function (aGrip, aOptions = {}) {
+VariablesView.getString = function(aGrip, aOptions = {}) {
   if (aGrip && typeof aGrip == "object") {
     switch (aGrip.type) {
       case "undefined":
       case "null":
       case "NaN":
       case "Infinity":
       case "-Infinity":
       case "-0":
@@ -3411,16 +3400,17 @@ VariablesView.getString = function (aGri
     case "string":
       return VariablesView.stringifiers.byType.string(aGrip, aOptions);
     case "boolean":
       return aGrip ? "true" : "false";
     case "number":
       if (!aGrip && 1 / aGrip === -Infinity) {
         return "-0";
       }
+      // fall through
     default:
       return aGrip + "";
   }
 };
 
 /**
  * The VariablesView stringifiers are used by VariablesView.getString(). These
  * are organized by object type, object class and by object actor preview kind.
@@ -3429,116 +3419,116 @@ VariablesView.getString = function (aGri
  *
  * Any stringifier function must return a string. If null is returned, * then
  * the default stringifier will be used. When invoked, the stringifier is
  * given the same two arguments as those given to VariablesView.getString().
  */
 VariablesView.stringifiers = {};
 
 VariablesView.stringifiers.byType = {
-  string: function (aGrip, {noStringQuotes}) {
+  string: function(aGrip, {noStringQuotes}) {
     if (noStringQuotes) {
       return aGrip;
     }
     return '"' + aGrip + '"';
   },
 
-  longString: function ({initial}, {noStringQuotes, noEllipsis}) {
+  longString: function({initial}, {noStringQuotes, noEllipsis}) {
     let ellipsis = noEllipsis ? "" : ELLIPSIS;
     if (noStringQuotes) {
       return initial + ellipsis;
     }
     let result = '"' + initial + '"';
     if (!ellipsis) {
       return result;
     }
     return result.substr(0, result.length - 1) + ellipsis + '"';
   },
 
-  object: function (aGrip, aOptions) {
+  object: function(aGrip, aOptions) {
     let {preview} = aGrip;
     let stringifier;
     if (aGrip.class) {
       stringifier = VariablesView.stringifiers.byObjectClass[aGrip.class];
     }
     if (!stringifier && preview && preview.kind) {
       stringifier = VariablesView.stringifiers.byObjectKind[preview.kind];
     }
     if (stringifier) {
       return stringifier(aGrip, aOptions);
     }
     return null;
   },
 
-  symbol: function (aGrip, aOptions) {
+  symbol: function(aGrip, aOptions) {
     const name = aGrip.name || "";
     return "Symbol(" + name + ")";
   },
 
-  mapEntry: function (aGrip, {concise}) {
+  mapEntry: function(aGrip, {concise}) {
     let { preview: { key, value }} = aGrip;
 
     let keyString = VariablesView.getString(key, {
       concise: true,
       noStringQuotes: true,
     });
     let valueString = VariablesView.getString(value, { concise: true });
 
     return keyString + " \u2192 " + valueString;
   },
 
 }; // VariablesView.stringifiers.byType
 
 VariablesView.stringifiers.byObjectClass = {
-  Function: function (aGrip, {concise}) {
+  Function: function(aGrip, {concise}) {
     // TODO: Bug 948484 - support arrow functions and ES6 generators
 
     let name = aGrip.userDisplayName || aGrip.displayName || aGrip.name || "";
     name = VariablesView.getString(name, { noStringQuotes: true });
 
     // TODO: Bug 948489 - Support functions with destructured parameters and
     // rest parameters
     let params = aGrip.parameterNames || "";
     if (!concise) {
       return "function " + name + "(" + params + ")";
     }
     return (name || "function ") + "(" + params + ")";
   },
 
-  RegExp: function ({displayString}) {
+  RegExp: function({displayString}) {
     return VariablesView.getString(displayString, { noStringQuotes: true });
   },
 
-  Date: function ({preview}) {
+  Date: function({preview}) {
     if (!preview || !("timestamp" in preview)) {
       return null;
     }
 
     if (typeof preview.timestamp != "number") {
       return new Date(preview.timestamp).toString(); // invalid date
     }
 
     return "Date " + new Date(preview.timestamp).toISOString();
   },
 
-  Number: function (aGrip) {
+  Number: function(aGrip) {
     let {preview} = aGrip;
     if (preview === undefined) {
       return null;
     }
     return aGrip.class + " { " + VariablesView.getString(preview.wrappedValue) +
       " }";
   },
 }; // VariablesView.stringifiers.byObjectClass
 
 VariablesView.stringifiers.byObjectClass.Boolean =
   VariablesView.stringifiers.byObjectClass.Number;
 
 VariablesView.stringifiers.byObjectKind = {
-  ArrayLike: function (aGrip, {concise}) {
+  ArrayLike: function(aGrip, {concise}) {
     let {preview} = aGrip;
     if (concise) {
       return aGrip.class + "[" + preview.length + "]";
     }
 
     if (!preview.items) {
       return null;
     }
@@ -3566,17 +3556,17 @@ VariablesView.stringifiers.byObjectKind 
       // make sure we have the right number of commas...
       result[lastHole] += ",";
     }
 
     let prefix = aGrip.class == "Array" ? "" : aGrip.class + " ";
     return prefix + "[" + result.join(", ") + "]";
   },
 
-  MapLike: function (aGrip, {concise}) {
+  MapLike: function(aGrip, {concise}) {
     let {preview} = aGrip;
     if (concise || !preview.entries) {
       let size = typeof preview.size == "number" ?
                    "[" + preview.size + "]" : "";
       return aGrip.class + size;
     }
 
     let entries = [];
@@ -3592,35 +3582,35 @@ VariablesView.stringifiers.byObjectKind 
     if (typeof preview.size == "number" && preview.size > entries.length) {
       let n = preview.size - entries.length;
       entries.push(VariablesView.stringifiers._getNMoreString(n));
     }
 
     return aGrip.class + " {" + entries.join(", ") + "}";
   },
 
-  ObjectWithText: function (aGrip, {concise}) {
+  ObjectWithText: function(aGrip, {concise}) {
     if (concise) {
       return aGrip.class;
     }
 
     return aGrip.class + " " + VariablesView.getString(aGrip.preview.text);
   },
 
-  ObjectWithURL: function (aGrip, {concise}) {
+  ObjectWithURL: function(aGrip, {concise}) {
     let result = aGrip.class;
     let url = aGrip.preview.url;
     if (!VariablesView.isFalsy({ value: url })) {
       result += ` \u2192 ${getSourceNames(url)[concise ? "short" : "long"]}`;
     }
     return result;
   },
 
   // Stringifier for any kind of object.
-  Object: function (aGrip, {concise}) {
+  Object: function(aGrip, {concise}) {
     if (concise) {
       return aGrip.class;
     }
 
     let {preview} = aGrip;
     let props = [];
 
     if (aGrip.class == "Promise" && aGrip.promiseState) {
@@ -3664,17 +3654,17 @@ VariablesView.stringifiers.byObjectKind 
         props.push(VariablesView.stringifiers._getNMoreString(diff));
       }
     }
 
     let prefix = aGrip.class != "Object" ? aGrip.class + " " : "";
     return prefix + "{" + props.join(", ") + "}";
   }, // Object
 
-  Error: function (aGrip, {concise}) {
+  Error: function(aGrip, {concise}) {
     let {preview} = aGrip;
     let name = VariablesView.getString(preview.name, { noStringQuotes: true });
     if (concise) {
       return name || aGrip.class;
     }
 
     let msg = name + ": " +
               VariablesView.getString(preview.message, { noStringQuotes: true });
@@ -3682,17 +3672,17 @@ VariablesView.stringifiers.byObjectKind 
     if (!VariablesView.isFalsy({ value: preview.stack })) {
       msg += "\n" + L10N.getStr("variablesViewErrorStacktrace") +
              "\n" + preview.stack;
     }
 
     return msg;
   },
 
-  DOMException: function (aGrip, {concise}) {
+  DOMException: function(aGrip, {concise}) {
     let {preview} = aGrip;
     if (concise) {
       return preview.name || aGrip.class;
     }
 
     let msg = aGrip.class + " [" + preview.name + ": " +
               VariablesView.getString(preview.message) + "\n" +
               "code: " + preview.code + "\n" +
@@ -3703,17 +3693,17 @@ VariablesView.stringifiers.byObjectKind 
       if (preview.lineNumber) {
         msg += ":" + preview.lineNumber;
       }
     }
 
     return msg + "]";
   },
 
-  DOMEvent: function (aGrip, {concise}) {
+  DOMEvent: function(aGrip, {concise}) {
     let {preview} = aGrip;
     if (!preview.type) {
       return null;
     }
 
     if (concise) {
       return aGrip.class + " " + preview.type;
     }
@@ -3734,17 +3724,17 @@ VariablesView.stringifiers.byObjectKind 
     for (let prop in preview.properties) {
       let value = preview.properties[prop];
       props.push(prop + ": " + VariablesView.getString(value, { concise: true }));
     }
 
     return result + " {" + props.join(", ") + "}";
   }, // DOMEvent
 
-  DOMNode: function (aGrip, {concise}) {
+  DOMNode: function(aGrip, {concise}) {
     let {preview} = aGrip;
 
     switch (preview.nodeType) {
       case nodeConstants.DOCUMENT_NODE: {
         let result = aGrip.class;
         if (preview.location) {
           result += ` \u2192 ${getSourceNames(preview.location)[concise ? "short" : "long"]}`;
         }
@@ -3810,39 +3800,38 @@ VariablesView.stringifiers.byObjectKind 
       }
 
       default:
         return null;
     }
   }, // DOMNode
 }; // VariablesView.stringifiers.byObjectKind
 
-
 /**
  * Get the "N more…" formatted string, given an N. This is used for displaying
  * how many elements are not displayed in an object preview (eg. an array).
  *
  * @private
  * @param number aNumber
  * @return string
  */
-VariablesView.stringifiers._getNMoreString = function (aNumber) {
+VariablesView.stringifiers._getNMoreString = function(aNumber) {
   let str = L10N.getStr("variablesViewMoreObjects");
   return PluralForm.get(aNumber, str).replace("#1", aNumber);
 };
 
 /**
  * Returns a custom class style for a grip.
  *
  * @param any aGrip
  *        @see Variable.setGrip
  * @return string
  *         The custom class style.
  */
-VariablesView.getClass = function (aGrip) {
+VariablesView.getClass = function(aGrip) {
   if (aGrip && typeof aGrip == "object") {
     if (aGrip.preview) {
       switch (aGrip.preview.kind) {
         case "DOMNode":
           return "token-domnode";
       }
     }
 
@@ -3876,19 +3865,19 @@ VariablesView.getClass = function (aGrip
  * A monotonically-increasing counter, that guarantees the uniqueness of scope,
  * variables and properties ids.
  *
  * @param string aName
  *        An optional string to prefix the id with.
  * @return number
  *         A unique id.
  */
-var generateId = (function () {
+var generateId = (function() {
   let count = 0;
-  return function (aName = "") {
+  return function(aName = "") {
     return aName.toLowerCase().trim().replace(/\s+/g, "-") + (++count);
   };
 })();
 
 /**
  * Quote and escape a string. The result will be another string containing an
  * ECMAScript StringLiteral which will produce the original one when evaluated
  * by `eval` or similar.
@@ -3898,18 +3887,18 @@ var generateId = (function () {
  *       returns an empty string.
  * @return string
  */
 function escapeString(aString) {
   if (typeof aString !== "string") {
     return "";
   }
   // U+2028 and U+2029 are allowed in JSON but not in ECMAScript string literals.
-  return JSON.stringify(aString).replace(/\u2028/g, '\\u2028')
-                                .replace(/\u2029/g, '\\u2029');
+  return JSON.stringify(aString).replace(/\u2028/g, "\\u2028")
+                                .replace(/\u2029/g, "\\u2029");
 }
 
 /**
  * Escape some HTML special characters. We do not need full HTML serialization
  * here, we just want to make strings safe to display in HTML attributes, for
  * the stringifiers.
  *
  * @param string aString
@@ -3917,17 +3906,16 @@ function escapeString(aString) {
  */
 function escapeHTML(aString) {
   return aString.replace(/&/g, "&amp;")
                 .replace(/"/g, "&quot;")
                 .replace(/</g, "&lt;")
                 .replace(/>/g, "&gt;");
 }
 
-
 /**
  * An Editable encapsulates the UI of an edit box that overlays a label,
  * allowing the user to edit the value.
  *
  * @param Variable aVariable
  *        The Variable or Property to make editable.
  * @param object aOptions
  *        - onSave
@@ -3936,17 +3924,17 @@ function escapeHTML(aString) {
  *          The callback to call when the editable is removed for any reason.
  */
 function Editable(aVariable, aOptions) {
   this._variable = aVariable;
   this._onSave = aOptions.onSave;
   this._onCleanup = aOptions.onCleanup;
 }
 
-Editable.create = function (aVariable, aOptions, aEvent) {
+Editable.create = function(aVariable, aOptions, aEvent) {
   let editable = new this(aVariable, aOptions);
   editable.activate(aEvent);
   return editable;
 };
 
 Editable.prototype = {
   /**
    * The class name for targeting this Editable type's label element. Overridden
@@ -3967,17 +3955,17 @@ Editable.prototype = {
 
   /**
    * Activate this editable by replacing the input box it overlays and
    * initialize the handlers.
    *
    * @param Event e [optional]
    *        Optionally, the Event object that was used to activate the Editable.
    */
-  activate: function (e) {
+  activate: function(e) {
     if (!this.shouldActivate) {
       this._onCleanup && this._onCleanup();
       return;
     }
 
     let { label } = this;
     let initialString = label.getAttribute("value");
 
@@ -4018,17 +4006,17 @@ Editable.prototype = {
     this._variable.locked = true;
     this._variable.editing = true;
   },
 
   /**
    * Remove the input box and restore the Variable or Property to its previous
    * state.
    */
-  deactivate: function () {
+  deactivate: function() {
     this._input.removeEventListener("keydown", this._onKeydown);
     this._input.removeEventListener("blur", this.deactivate);
     this._input.parentNode.replaceChild(this.label, this._input);
     this._input = null;
 
     let { boxObject } = this._variable._variablesView;
     boxObject.scrollBy(-this._variable._target, 0);
     this._variable.locked = false;
@@ -4036,70 +4024,69 @@ Editable.prototype = {
     this._variable.expanded = this._prevExpanded;
     this._variable.editing = false;
     this._onCleanup && this._onCleanup();
   },
 
   /**
    * Save the current value and deactivate the Editable.
    */
-  _save: function () {
+  _save: function() {
     let initial = this.label.getAttribute("value");
     let current = this._input.value.trim();
     this.deactivate();
     if (initial != current) {
       this._onSave(current);
     }
   },
 
   /**
    * Called when tab is pressed, allowing subclasses to link different
    * behavior to tabbing if desired.
    */
-  _next: function () {
+  _next: function() {
     this._save();
   },
 
   /**
    * Called when escape is pressed, indicating a cancelling of editing without
    * saving.
    */
-  _reset: function () {
+  _reset: function() {
     this.deactivate();
     this._variable.focus();
   },
 
   /**
    * Event handler for when the input loses focus.
    */
-  _onBlur: function () {
+  _onBlur: function() {
     this.deactivate();
   },
 
   /**
    * Event handler for when the input receives a key press.
    */
-  _onKeydown: function (e) {
+  _onKeydown: function(e) {
     e.stopPropagation();
 
     switch (e.keyCode) {
       case KeyCodes.DOM_VK_TAB:
         this._next();
         break;
       case KeyCodes.DOM_VK_RETURN:
         this._save();
         break;
       case KeyCodes.DOM_VK_ESCAPE:
         this._reset();
         break;
     }
   },
 };
 
-
 /**
  * An Editable specific to editing the name of a Variable or Property.
  */
 function EditableName(aVariable, aOptions) {
   Editable.call(this, aVariable, aOptions);
 }
 
 EditableName.create = Editable.create;
@@ -4111,17 +4098,16 @@ EditableName.prototype = extend(Editable
     return this._variable._name;
   },
 
   get shouldActivate() {
     return !!this._variable.ownerView.switch;
   },
 });
 
-
 /**
  * An Editable specific to editing the value of a Variable or Property.
  */
 function EditableValue(aVariable, aOptions) {
   Editable.call(this, aVariable, aOptions);
 }
 
 EditableValue.create = Editable.create;
@@ -4133,46 +4119,45 @@ EditableValue.prototype = extend(Editabl
     return this._variable._valueLabel;
   },
 
   get shouldActivate() {
     return !!this._variable.ownerView.eval;
   },
 });
 
-
 /**
  * An Editable specific to editing the key and value of a new property.
  */
 function EditableNameAndValue(aVariable, aOptions) {
   EditableName.call(this, aVariable, aOptions);
 }
 
 EditableNameAndValue.create = Editable.create;
 
 EditableNameAndValue.prototype = extend(EditableName.prototype, {
-  _reset: function (e) {
+  _reset: function(e) {
     // Hide the Variable or Property if the user presses escape.
     this._variable.remove();
     this.deactivate();
   },
 
-  _next: function (e) {
+  _next: function(e) {
     // Override _next so as to set both key and value at the same time.
     let key = this._input.value;
     this.label.setAttribute("value", key);
 
     let valueEditable = EditableValue.create(this._variable, {
       onSave: aValue => {
         this._onSave([key, aValue]);
       }
     });
     valueEditable._reset = () => {
       this._variable.remove();
       valueEditable.deactivate();
     };
   },
 
-  _save: function (e) {
+  _save: function(e) {
     // Both _save and _next activate the value edit box.
     this._next(e);
   }
 });
--- a/devtools/client/shared/widgets/VariablesViewController.jsm
+++ b/devtools/client/shared/widgets/VariablesViewController.jsm
@@ -9,17 +9,17 @@ var {require} = ChromeUtils.import("reso
 var {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 var {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var {LocalizationHelper, ELLIPSIS} = require("devtools/shared/l10n");
 
 Object.defineProperty(this, "WebConsoleUtils", {
-  get: function () {
+  get: function() {
     return require("devtools/client/webconsole/utils").Utils;
   },
   configurable: true,
   enumerable: true
 });
 
 XPCOMUtils.defineLazyGetter(this, "VARIABLES_SORTING_ENABLED", () =>
   Services.prefs.getBoolPref("devtools.debugger.ui.variables-sorting-enabled")
@@ -86,17 +86,17 @@ VariablesViewController.prototype = {
    *
    * @param object aOptions
    *        Options for getting the client grips. Supported options:
    *        - getObjectClient: callback for creating an object grip client
    *        - getLongStringClient: callback for creating a long string grip client
    *        - getEnvironmentClient: callback for creating an environment client
    *        - releaseActor: callback for releasing an actor when it's no longer needed
    */
-  _setClientGetters: function (aOptions) {
+  _setClientGetters: function(aOptions) {
     if (aOptions.getObjectClient) {
       this._getObjectClient = aOptions.getObjectClient;
     }
     if (aOptions.getLongStringClient) {
       this._getLongStringClient = aOptions.getLongStringClient;
     }
     if (aOptions.getEnvironmentClient) {
       this._getEnvironmentClient = aOptions.getEnvironmentClient;
@@ -110,17 +110,17 @@ VariablesViewController.prototype = {
    * Sets the functions used when evaluating strings in the variables view.
    *
    * @param object aOptions
    *        Options for configuring the macros. Supported options:
    *        - overrideValueEvalMacro: callback for creating an overriding eval macro
    *        - getterOrSetterEvalMacro: callback for creating a getter/setter eval macro
    *        - simpleValueEvalMacro: callback for creating a simple value eval macro
    */
-  _setEvaluationMacros: function (aOptions) {
+  _setEvaluationMacros: function(aOptions) {
     if (aOptions.overrideValueEvalMacro) {
       this._overrideValueEvalMacro = aOptions.overrideValueEvalMacro;
     }
     if (aOptions.getterOrSetterEvalMacro) {
       this._getterOrSetterEvalMacro = aOptions.getterOrSetterEvalMacro;
     }
     if (aOptions.simpleValueEvalMacro) {
       this._simpleValueEvalMacro = aOptions.simpleValueEvalMacro;
@@ -132,17 +132,17 @@ VariablesViewController.prototype = {
    *
    * @param Variable aTarget
    *        The target Variable/Property to put the retrieved string into.
    * @param LongStringActor aGrip
    *        The long string grip that use to retrieve the full string.
    * @return Promise
    *         The promise that will be resolved when the string is retrieved.
    */
-  _populateFromLongString: function (aTarget, aGrip) {
+  _populateFromLongString: function(aTarget, aGrip) {
     let deferred = defer();
 
     let from = aGrip.initial.length;
     let to = Math.min(aGrip.length, MAX_LONG_STRING_LENGTH);
 
     this._getLongStringClient(aGrip).substring(from, to, aResponse => {
       // Stop tracking the actor because it's no longer needed.
       this.releaseActor(aGrip);
@@ -162,17 +162,17 @@ VariablesViewController.prototype = {
    * Adds pseudo items in case there is too many properties to display.
    * Each item can expand into property slices.
    *
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The property iterator grip.
    */
-  _populatePropertySlices: function (aTarget, aGrip) {
+  _populatePropertySlices: function(aTarget, aGrip) {
     if (aGrip.count < MAX_PROPERTY_ITEMS) {
       return this._populateFromPropertyIterator(aTarget, aGrip);
     }
 
     // Divide the keys into quarters.
     let items = Math.ceil(aGrip.count / 4);
     let iterator = aGrip.propertyIterator;
     let promises = [];
@@ -186,17 +186,17 @@ VariablesViewController.prototype = {
         propertyIterator: iterator,
         start: start,
         count: count
       };
 
       // Query the name of the first and last items for this slice
       let deferred = defer();
       iterator.names([start, start + count - 1], ({ names }) => {
-          let label = "[" + names[0] + ELLIPSIS + names[1] + "]";
+        let label = "[" + names[0] + ELLIPSIS + names[1] + "]";
         let item = aTarget.addItem(label, {}, { internalItem: true });
         item.showArrow();
         this.addExpander(item, sliceGrip);
         deferred.resolve();
       });
       promises.push(deferred.promise);
     }
 
@@ -207,17 +207,17 @@ VariablesViewController.prototype = {
    * Adds a property slice for a Variable in the view using the already
    * property iterator
    *
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The property iterator grip.
    */
-  _populateFromPropertyIterator: function (aTarget, aGrip) {
+  _populateFromPropertyIterator: function(aTarget, aGrip) {
     if (aGrip.count >= MAX_PROPERTY_ITEMS) {
       // We already started to split, but there is still too many properties, split again.
       return this._populatePropertySlices(aTarget, aGrip);
     }
     // We started slicing properties, and the slice is now small enough to be displayed
     let deferred = defer();
     aGrip.propertyIterator.slice(aGrip.start, aGrip.count,
       ({ ownProperties }) => {
@@ -240,17 +240,17 @@ VariablesViewController.prototype = {
    *
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The grip to use to populate the target.
    * @param string aQuery [optional]
    *        The query string used to fetch only a subset of properties
    */
-  _populateFromObjectWithIterator: function (aTarget, aGrip, aQuery) {
+  _populateFromObjectWithIterator: function(aTarget, aGrip, aQuery) {
     // FF40+ starts exposing `ownPropertyLength` on ObjectActor's grip,
     // as well as `enumProperties` request.
     let deferred = defer();
     let objectClient = this._getObjectClient(aGrip);
     let isArray = aGrip.preview && aGrip.preview.kind === "ArrayLike";
     if (isArray) {
       // First enumerate array items, e.g. properties from `0` to `array.length`.
       let options = {
@@ -289,47 +289,46 @@ VariablesViewController.prototype = {
         let sliceGrip = {
           type: "property-iterator",
           propertyIterator: iterator,
           start: 0,
           count: iterator.count
         };
         deferred.resolve(this._populatePropertySlices(aTarget, sliceGrip));
       });
-
     }
     return deferred.promise;
   },
 
   /**
    * Adds the given prototype in the view.
    *
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aProtype
    *        The prototype grip.
    */
-  _populateObjectPrototype: function (aTarget, aPrototype) {
+  _populateObjectPrototype: function(aTarget, aPrototype) {
     // Add the variable's __proto__.
     if (aPrototype && aPrototype.type != "null") {
       let proto = aTarget.addItem("__proto__", { value: aPrototype });
       this.addExpander(proto, aPrototype);
     }
   },
 
   /**
    * Adds properties to a Scope, Variable, or Property in the view. Triggered
    * when a scope is expanded or certain variables are hovered.
    *
    * @param Scope aTarget
    *        The Scope where the properties will be placed into.
    * @param object aGrip
    *        The grip to use to populate the target.
    */
-  _populateFromObject: function (aTarget, aGrip) {
+  _populateFromObject: function(aTarget, aGrip) {
     if (aGrip.class === "Proxy") {
       this.addExpander(
         aTarget.addItem("<target>", { value: aGrip.proxyTarget }, { internalItem: true }),
         aGrip.proxyTarget);
       this.addExpander(
         aTarget.addItem("<handler>", { value: aGrip.proxyHandler }, { internalItem: true }),
         aGrip.proxyHandler);
 
@@ -372,17 +371,17 @@ VariablesViewController.prototype = {
                    });
                    return deferred.promise;
                  });
     }
 
     return this._populateProperties(aTarget, aGrip);
   },
 
-  _populateProperties: function (aTarget, aGrip, aOptions) {
+  _populateProperties: function(aTarget, aGrip, aOptions) {
     let deferred = defer();
 
     let objectClient = this._getObjectClient(aGrip);
     objectClient.getPrototypeAndProperties(aResponse => {
       let ownProperties = aResponse.ownProperties || {};
       let prototype = aResponse.prototype || null;
       // 'safeGetterValues' is new and isn't necessary defined on old actors.
       let safeGetterValues = aResponse.safeGetterValues || {};
@@ -435,17 +434,17 @@ VariablesViewController.prototype = {
   /**
    * Adds the scope chain elements (closures) of a function variable.
    *
    * @param Variable aTarget
    *        The variable where the properties will be placed into.
    * @param Scope aScope
    *        The lexical environment form as specified in the protocol.
    */
-  _populateWithClosure: function (aTarget, aScope) {
+  _populateWithClosure: function(aTarget, aScope) {
     let objectScopes = [];
     let environment = aScope;
     let funcScope = aTarget.addItem("<Closure>");
     funcScope.target.setAttribute("scope", "");
     funcScope.showArrow();
 
     do {
       // Create a scope to contain all the inspected variables.
@@ -478,17 +477,17 @@ VariablesViewController.prototype = {
   /**
    * Adds nodes for every specified binding to the closure node.
    *
    * @param Variable aTarget
    *        The variable where the bindings will be placed into.
    * @param object aBindings
    *        The bindings form as specified in the protocol.
    */
-  _populateWithEnvironmentBindings: function (aTarget, aBindings) {
+  _populateWithEnvironmentBindings: function(aTarget, aBindings) {
     // Add nodes for every argument in the scope.
     aTarget.addItems(aBindings.arguments.reduce((accumulator, arg) => {
       let name = Object.getOwnPropertyNames(arg)[0];
       let descriptor = arg[name];
       accumulator[name] = descriptor;
       return accumulator;
     }, {}), {
       // Arguments aren't sorted.
@@ -501,20 +500,21 @@ VariablesViewController.prototype = {
     aTarget.addItems(aBindings.variables, {
       // Not all variables need to force sorted properties.
       sorted: VARIABLES_SORTING_ENABLED,
       // Expansion handlers must be set after the properties are added.
       callback: this.addExpander
     });
   },
 
-  _populateFromEntries: function (target, grip) {
+  _populateFromEntries: function(target, grip) {
     let objGrip = grip.obj;
     let objectClient = this._getObjectClient(objGrip);
 
+    // eslint-disable-next-line new-cap
     return new promise((resolve, reject) => {
       objectClient.enumEntries((response) => {
         if (response.error) {
           // Older server might not support the enumEntries method
           console.warn(response.error + ": " + response.message);
           resolve();
         } else {
           let sliceGrip = {
@@ -534,17 +534,17 @@ VariablesViewController.prototype = {
    * Adds an 'onexpand' callback for a variable, lazily handling
    * the addition of new properties.
    *
    * @param Variable aTarget
    *        The variable where the properties will be placed into.
    * @param any aSource
    *        The source to use to populate the target.
    */
-  addExpander: function (aTarget, aSource) {
+  addExpander: function(aTarget, aSource) {
     // Attach evaluation macros as necessary.
     if (aTarget.getter || aTarget.setter) {
       aTarget.evaluationMacro = this._overrideValueEvalMacro;
       let getter = aTarget.get("get");
       if (getter) {
         getter.evaluationMacro = this._getterOrSetterEvalMacro;
       }
       let setter = aTarget.get("set");
@@ -590,17 +590,17 @@ VariablesViewController.prototype = {
    *
    * @param Scope aTarget
    *        The Scope to be expanded.
    * @param object aSource
    *        The source to use to populate the target.
    * @return Promise
    *         The promise that is resolved once the target has been expanded.
    */
-  populate: function (aTarget, aSource) {
+  populate: function(aTarget, aSource) {
     // Fetch the variables only once.
     if (aTarget._fetched) {
       return aTarget._fetched;
     }
     // Make sure the source grip is available.
     if (!aSource) {
       return promise.reject(new Error("No actor grip was given for the variable."));
     }
@@ -676,57 +676,57 @@ VariablesViewController.prototype = {
     return deferred.promise;
   },
 
   /**
    * Indicates to the view if the targeted actor supports properties search
    *
    * @return boolean True, if the actor supports enumProperty request
    */
-  supportsSearch: function () {
+  supportsSearch: function() {
     // FF40+ starts exposing ownPropertyLength on object actor's grip
     // as well as enumProperty which allows to query a subset of properties.
     return this.objectActor && ("ownPropertyLength" in this.objectActor);
   },
 
   /**
    * Try to use the actor to perform an attribute search.
    *
    * @param Scope aScope
    *        The Scope instance to populate with properties
    * @param string aToken
    *        The query string
    */
-  performSearch: function (aScope, aToken) {
+  performSearch: function(aScope, aToken) {
     this._populateFromObjectWithIterator(aScope, this.objectActor, aToken)
         .then(() => {
           this.view.emit("fetched", "search", aScope);
         });
   },
 
   /**
    * Release an actor from the controller.
    *
    * @param object aActor
    *        The actor to release.
    */
-  releaseActor: function (aActor) {
+  releaseActor: function(aActor) {
     if (this._releaseActor) {
       this._releaseActor(aActor);
     }
     this._actors.delete(aActor);
   },
 
   /**
    * Release all the actors referenced by the controller, optionally filtered.
    *
    * @param function aFilter [optional]
    *        Callback to filter which actors are released.
    */
-  releaseActors: function (aFilter) {
+  releaseActors: function(aFilter) {
     for (let actor of this._actors) {
       if (!aFilter || aFilter(actor)) {
         this.releaseActor(actor);
       }
     }
   },
 
   /**
@@ -744,17 +744,17 @@ VariablesViewController.prototype = {
    *        Additional options for the controller:
    *        - overrideValueEvalMacro: @see _setEvaluationMacros
    *        - getterOrSetterEvalMacro: @see _setEvaluationMacros
    *        - simpleValueEvalMacro: @see _setEvaluationMacros
    * @return Object
    *         - variable: the created Variable.
    *         - expanded: the Promise that resolves when the variable expands.
    */
-  setSingleVariable: function (options, configuration = {}) {
+  setSingleVariable: function(options, configuration = {}) {
     this._setEvaluationMacros(configuration);
     this.view.empty();
 
     let scope = this.view.addScope(options.label);
     scope.expanded = true; // Expand the scope by default.
     scope.locked = true; // Prevent collapsing the scope.
 
     let variable = scope.addItem(undefined, { enumerable: true });
@@ -773,28 +773,27 @@ VariablesViewController.prototype = {
       variable.populate(options.rawObject, { expanded: true });
       populated = promise.resolve();
     }
 
     return { variable: variable, expanded: populated };
   },
 };
 
-
 /**
  * Attaches a VariablesViewController to a VariablesView if it doesn't already
  * have one.
  *
  * @param VariablesView aView
  *        The view to attach to.
  * @param object aOptions
  *        The options to use in creating the controller.
  * @return VariablesViewController
  */
-VariablesViewController.attach = function (aView, aOptions) {
+VariablesViewController.attach = function(aView, aOptions) {
   if (aView.controller) {
     return aView.controller;
   }
   return new VariablesViewController(aView, aOptions);
 };
 
 /**
  * Utility functions for handling stackframes.
@@ -802,41 +801,40 @@ VariablesViewController.attach = functio
 var StackFrameUtils = this.StackFrameUtils = {
   /**
    * Create a textual representation for the specified stack frame
    * to display in the stackframes container.
    *
    * @param object aFrame
    *        The stack frame to label.
    */
-  getFrameTitle: function (aFrame) {
+  getFrameTitle: function(aFrame) {
     if (aFrame.type == "call") {
       let c = aFrame.callee;
       return (c.name || c.userDisplayName || c.displayName || "(anonymous)");
     }
     return "(" + aFrame.type + ")";
   },
 
   /**
    * Constructs a scope label based on its environment.
    *
    * @param object aEnv
    *        The scope's environment.
    * @return string
    *         The scope's label.
    */
-  getScopeLabel: function (aEnv) {
+  getScopeLabel: function(aEnv) {
     let name = "";
 
     // Name the outermost scope Global.
     if (!aEnv.parent) {
       name = L10N.getStr("globalScopeLabel");
-    }
-    // Otherwise construct the scope name.
-    else {
+    } else {
+      // Otherwise construct the scope name.
       name = aEnv.type.charAt(0).toUpperCase() + aEnv.type.slice(1);
     }
 
     let label = L10N.getFormatStr("scopeLabel", name);
     switch (aEnv.type) {
       case "with":
       case "object":
         label += " [" + aEnv.object.class + "]";
--- a/devtools/client/storage/test/browser_storage_basic.js
+++ b/devtools/client/storage/test/browser_storage_basic.js
@@ -90,17 +90,17 @@ const testCases = [
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree() {
   let doc = gPanelWindow.document;
   for (let [item] of testCases) {
     ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
-       "Tree item " + item[0] + " should be present in the storage tree");
+      `Tree item ${item.toSource()} should be present in the storage tree`);
   }
 }
 
 /**
  * Test that correct table entries are shown for each of the tree item
  */
 async function testTables() {
   let doc = gPanelWindow.document;
--- a/devtools/client/storage/test/browser_storage_basic_usercontextid_1.js
+++ b/devtools/client/storage/test/browser_storage_basic_usercontextid_1.js
@@ -75,17 +75,17 @@ const testCases = [
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree(tests) {
   let doc = gPanelWindow.document;
   for (let [item] of tests) {
     ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
-       "Tree item " + item[0] + " should be present in the storage tree");
+      `Tree item ${item.toSource()} should be present in the storage tree`);
   }
 }
 
 /**
  * Test that correct table entries are shown for each of the tree item
  */
 async function testTables(tests) {
   let doc = gPanelWindow.document;
--- a/devtools/client/storage/test/browser_storage_basic_usercontextid_2.js
+++ b/devtools/client/storage/test/browser_storage_basic_usercontextid_2.js
@@ -69,17 +69,17 @@ const testCasesUserContextId = [
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree(tests) {
   let doc = gPanelWindow.document;
   for (let [item] of tests) {
     ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
-       "Tree item " + item[0] + " should be present in the storage tree");
+      `Tree item ${item.toSource()} should be present in the storage tree`);
   }
 }
 
 /**
  * Test that correct table entries are shown for each of the tree item
  */
 async function testTables(tests) {
   let doc = gPanelWindow.document;
--- a/devtools/client/storage/test/browser_storage_basic_with_fragment.js
+++ b/devtools/client/storage/test/browser_storage_basic_with_fragment.js
@@ -93,17 +93,17 @@ const testCases = [
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree() {
   let doc = gPanelWindow.document;
   for (let [item] of testCases) {
     ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
-       "Tree item " + item[0] + " should be present in the storage tree");
+      `Tree item ${item.toSource()} should be present in the storage tree`);
   }
 }
 
 /**
  * Test that correct table entries are shown for each of the tree item
  */
 async function testTables() {
   let doc = gPanelWindow.document;
--- a/devtools/client/storage/test/browser_storage_delete_usercontextid.js
+++ b/devtools/client/storage/test/browser_storage_delete_usercontextid.js
@@ -93,17 +93,17 @@ const storageItemsForDefault = [
 
 /**
  * Test that the desired number of tree items are present
  */
 function testTree(tests) {
   let doc = gPanelWindow.document;
   for (let [item] of tests) {
     ok(doc.querySelector("[data-id='" + JSON.stringify(item) + "']"),
-       "Tree item " + item[0] + " should be present in the storage tree");
+      `Tree item ${item.toSource()} should be present in the storage tree`);
   }
 }
 
 /**
  * Test that correct table entries are shown for each of the tree item
  */
 async function testTables(tests) {
   let doc = gPanelWindow.document;
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -54,16 +54,18 @@ const COOKIE_KEY_MAP = {
   expires: "Expires",
   isSecure: "Secure",
   isHttpOnly: "HttpOnly",
   isDomain: "HostOnly",
   creationTime: "CreationTime",
   lastAccessed: "LastAccessed"
 };
 
+const SAFE_HOSTS_PREFIXES_REGEX = /^(about:|https?:|file:|moz-extension:)/;
+
 // Maximum length of item name to show in context menu label - will be
 // trimmed with ellipsis if it's longer.
 const ITEM_NAME_MAX_LENGTH = 32;
 
 /**
  * StorageUI is controls and builds the UI of the Storage Inspector.
  *
  * @param {Front} front
@@ -124,16 +126,36 @@ class StorageUI {
     });
     let key = L10N.getStr("storage.filter.key");
     shortcuts.on(key, event => {
       event.preventDefault();
       this.searchBox.focus();
     });
 
     this.front.listStores().then(storageTypes => {
+      // When we are in the browser console we list indexedDBs internal to
+      // Firefox e.g. defined inside a .jsm. Because there is no way before this
+      // point to know whether or not we are inside the browser toolbox we have
+      // already fetched the hostnames of these databases.
+      //
+      // If we are not inside the browser toolbox we need to delete these
+      // hostnames.
+      if (!this._target.chrome && storageTypes.indexedDB) {
+        let hosts = storageTypes.indexedDB.hosts;
+        let newHosts = {};
+
+        for (let [host, dbs] of Object.entries(hosts)) {
+          if (SAFE_HOSTS_PREFIXES_REGEX.test(host)) {
+            newHosts[host] = dbs;
+          }
+        }
+
+        storageTypes.indexedDB.hosts = newHosts;
+      }
+
       this.populateStorageTree(storageTypes);
     }).catch(e => {
       if (!this._toolbox || this._toolbox._destroyer) {
         // The toolbox is in the process of being destroyed... in this case throwing here
         // is expected and normal so let's ignore the error.
         return;
       }
 
--- a/devtools/docs/frontend/telemetry.md
+++ b/devtools/docs/frontend/telemetry.md
@@ -4,54 +4,58 @@ We use telemetry to get metrics of usage
 
 ## Adding metrics to a tool
 
 The process to add metrics to a tool roughly consists in:
 
 1. Adding the probe to Firefox
 2. Using Histograms.json probes in DevTools code
 3. Using Scalars.yaml probes in DevTools code
-4. Getting approval from the data team
+4. Using Events.yaml probes in DevTools code for analysis in Amplitude.
+5. Getting approval from the data team
 
 ### 1. Adding the probe to Firefox
 
 The first step involves creating entries for the probe in one of the files that contain declarations for all data that Firefox might report to Mozilla.
 
 These files are:
+
 - `toolkit/components/telemetry/Histograms.json`
 - `toolkit/components/telemetry/Scalars.yaml`
-
+- `toolkit/components/telemetry/Events.yaml`
 
 Scalars allow collection of simple values, like counts, booleans and strings and are to be used whenever possible instead of histograms.
 
 Histograms allow collection of multiple different values, but aggregate them into a number of buckets. Each bucket has a value range and a count of how many values we recorded.
 
+Events allow collection of a number of properties keyed to a category, method, object and value. Event telemetry helps us tell a story about how a user is interacting with the browser.
+
 Both scalars & histograms allow recording by keys. This allows for more flexible, two-level data collection.
 
-#### Why the different file formats?
+#### The different file formats
 
-The data team chose YAML for `Scalars.yaml` because it is easy to write and provides a number of features not available in JSON including comments, extensible data types, relational anchors, strings without quotation marks, and mapping types preserving key order.
+The data team chose YAML for `Scalars.yaml` and `Events.yaml` because it is easy to write and provides a number of features not available in JSON including comments, extensible data types, relational anchors, strings without quotation marks, and mapping types preserving key order.
 
 While we previously used JSON for similar purposes in histograms.json, we have used YAML here because it allows for comments and is generally easier to write.
 
-When the YAML format is proven the data team are considering moving the histograms over to YAML format at some point.
+The data team are considering moving the histograms over to YAML format at some point.
 
 If it's the first time you add one of these, it's advised to follow the style of existing entries.
 
 New data types have been added over the years, so it's quite feasible that some of our probes are not the most suitable nowadays.
 
-There's more information about types (and telemetry in general) on [this page](https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Adding_a_new_Telemetry_probe) and [this other page](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/index.html).
+There's more information about types (and telemetry in general) on [this page](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/start/adding-a-new-probe.html) and [this other page](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/collection/index.html).
 
 And of course, in case of doubt, ask!
 
 ### Adding probes to `Histograms.json`
 
 Our entries are prefixed with `DEVTOOLS_`. For example:
 
-```javascript
+```json
   "DEVTOOLS_DOM_OPENED_COUNT": {
     "alert_emails": ["dev-developer-tools@lists.mozilla.org"],
     "expires_in_version": "never",
     "kind": "count",
     "bug_numbers": [1343501],
     "description": "Number of times the DevTools DOM Inspector has been opened.",
     "releaseChannelCollection": "opt-out"
   },
@@ -67,17 +71,17 @@ Our entries are prefixed with `DEVTOOLS_
 ```
 
 There are different types of probes you can use. These are specified by the `kind` field. Normally we use `count` for counting how many times the tools are opened, and `exponential` for how many times a panel is active.
 
 ### Adding probes to `Scalars.yaml`
 
 Our entries are prefixed with `devtools.`. For example:
 
-```javascript
+```yaml
 devtools.toolbar.eyedropper:
   opened:
     bug_numbers:
       - 1247985
       - 1352115
     description: Number of times the DevTools Eyedropper has been opened via the inspector toolbar.
     expires: never
     kind: uint
@@ -97,153 +101,303 @@ devtools.copy.unique.css.selector:
     kind: uint
     notification_emails:
       - dev-developer-tools@lists.mozilla.org
     release_channel_collection: opt-out
     record_in_processes:
       - 'main'
 ```
 
+### Adding probes to `Events.yaml`
+
+Our entries are prefixed with `devtools.`. For example:
+
+```yaml
+devtools.main:
+  open:
+    objects: ["tools"]
+    bug_numbers: [1416024]
+    notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
+    record_in_processes: ["main"]
+    description: User opens devtools toolbox.
+    release_channel_collection: opt-out
+    expiry_version: never
+    extra_keys:
+      entrypoint: How was the toolbox opened? CommandLine, ContextMenu, DeveloperToolbar, HamburgerMenu, KeyShortcut, SessionRestore or SystemMenu
+      first_panel: The name of the first panel opened.
+      host: "Toolbox host (positioning): bottom, side, window or other."
+      splitconsole: Indicates whether the split console was open.
+      width: Toolbox width (px).
+```
+
 ### 2. Using Histograms.json probes in DevTools code
 
 Once the probe has been declared in the `Histograms.json` file, you'll need to actually use it in our code.
 
 First, you need to give it an id in `devtools/client/shared/telemetry.js`. Similarly to the `Histograms.json` case, you'll want to follow the style of existing entries. For example:
 
-```javascript
+```js
 dom: {
   histogram: "DEVTOOLS_DOM_OPENED_COUNT",
   timerHistogram: "DEVTOOLS_DOM_TIME_ACTIVE_SECONDS"
 },
 ```
 
 ... would correspond to the probes we declared in the previous section.
 
 Then, include that module on each tool that requires telemetry:
 
-```javascript
+```js
 let Telemetry = require("devtools/client/shared/telemetry");
 ```
 
 Create a telemetry instance on the tool constructor:
 
-```javascript
+```js
 this._telemetry = new Telemetry();
 ```
 
 And use the instance to report e.g. tool opening...
 
-```javascript
+```js
 this._telemetry.toolOpened("mytoolname");
 ```
 
 ... or closing:
 
-```javascript
+```js
 this._telemetry.toolClosed("mytoolname");
 ```
 
 Note that `mytoolname` is the id we declared in the `telemetry.js` module.
 
 ### 3. Using Scalars.yaml probes in DevTools code
 
 Once the probe has been declared in the `Scalars.yaml` file, you'll need to actually use it in our code.
 
 First, you need to give it an id in `devtools/client/shared/telemetry.js`. You will want to follow the style of existing lowercase histogram entries. For example:
 
-```javascript
+```js
 toolbareyedropper: {
   scalar: "devtools.toolbar.eyedropper.opened", // Note that the scalar is lowercase
 },
 copyuniquecssselector: {
   scalar: "devtools.copy.unique.css.selector.opened",
 },
 ```
 
 ... would correspond to the probes we declared in the previous section.
 
 Then, include that module on each tool that requires telemetry:
 
-```javascript
+```js
+let Telemetry = require("devtools/client/shared/telemetry");
+```
+
+Create a telemetry instance on the tool constructor:
+
+```js
+this._telemetry = new Telemetry();
+```
+
+And use the instance to report e.g. tool opening...
+
+```js
+this._telemetry.toolOpened("mytoolname");
+```
+
+Notes:
+
+- `mytoolname` is the id we declared in the `Scalars.yaml` module.
+- Because we are not logging tool's time opened in `Scalars.yaml` we don't care
+  about toolClosed. Of course, if there was an accompanying `timerHistogram`
+  field defined in `telemetry.js` and `histograms.json` then `toolClosed` should
+  also be added.
+
+### 4. Using Events.yaml probes in DevTools code
+
+Once the probe has been declared in the `Events.yaml` file, you'll need to actually use it in our code.
+
+It is crucial to understand that event telemetry have a string identifier which is constructed from the `category`, `method`, `object` (name) and `value` on which the event occured. This key points to an "extra" object that contains further information about the event (we will give examples later in this section).
+
+Because these "extra" objects can be from completely independant code paths we
+can send events and leave them in a pending state until all of the expected extra properties have been received.
+
+First, include the telemetry module in each tool that requires telemetry:
+
+```js
 let Telemetry = require("devtools/client/shared/telemetry");
 ```
 
 Create a telemetry instance on the tool constructor:
 
-```javascript
+```js
 this._telemetry = new Telemetry();
 ```
 
 And use the instance to report e.g. tool opening...
 
-```javascript
-this._telemetry.toolOpened("mytoolname");
+```js
+// Event telemetry is disabled by default so enable it for your category.
+this._telemetry.setEventRecordingEnabled("devtools.main", true);
+
+// If you already have all the properties for the event you can send the
+// telemetry event using:
+// this._telemetry.recordEvent(category, method, object, value, extra) e.g.
+this._telemetry.recordEvent("devtools.main", "open", "tools", null, {
+  entrypoint: "ContextMenu",
+  first_panel: "Inspector",
+  host: "bottom",
+  splitconsole: false,
+  width: 1024
+});
+
+// If your "extra" properties are in different code paths you will need to
+// create a "pending event." These events contain a list of expected properties
+// that can be populated before or after creating the pending event.
+
+// Use the category, method, object, value combinations above to add a
+// property... we do this before creating the pending event simply to
+// demonstrate that properties can be sent before the pending event is created.
+this._telemetry.addEventProperty(
+  "devtools.main", "open", "tools", null, "entrypoint", "ContextMenu");
+
+// In this example `"devtools.main", "open", "tools", null` make up the
+// signature of the event and needs to be sent with all properties.
+
+// Create the pending event using
+// this._telemetry.preparePendingEvent(category, method, object, value, expectedPropertyNames) e.g.
+this._telemetry.preparePendingEvent("devtools.main", "open", "tools", null,
+  ["entrypoint", "first_panel", "host", "splitconsole", "width"]
+);
+
+// Use the category, method, object, value combinations above to add each
+// property.
+this._telemetry.addEventProperty(
+  "devtools.main", "open", "tools", null, "first_panel", "inspector");
+this._telemetry.addEventProperty(
+  "devtools.main", "open", "tools", null, "host", "bottom");
+this._telemetry.addEventProperty(
+  "devtools.main", "open", "tools", null, "splitconsole", false);
+this._telemetry.addEventProperty(
+  "devtools.main", "open", "tools", null, "width", 1024);
 ```
 
 Notes:
 
-  - `mytoolname` is the id we declared in the `Scalars.yaml` module.
-  - Because we are not logging tool's time opened in `Scalars.yaml` we don't care
-about toolClosed. Of course, if there was an accompanying `timerHistogram` field defined
-in `telemetry.js` and `histograms.json` then `toolClosed` should also be added.
+- `mytoolname` is the id we declared in the `Scalars.yaml` module.
+- Because we are not logging tool's time opened in `Scalars.yaml` we don't care
+  about toolClosed. Of course, if there was an accompanying `timerHistogram`
+  field defined in `telemetry.js` and `histograms.json` then `toolClosed` should
+  also be added.
 
 #### Note on top level panels
 
 The code for the tabs uses their ids to automatically report telemetry when you switch between panels, so you don't need to explicitly call `toolOpened` and `toolClosed` on top level panels.
 
 You will still need to call those functions on subpanels, or tools such as about:debugging which are not opened as tabs.
 
 #### Testing
 
 The telemetry module will print warnings to stdout if there are missing ids. It is strongly advisable to ensure this is working correctly, as the module will attribute usage for undeclared ids to a generic `custom` bucket. This is not good for accurate results!
 
 To see these warnings, you need to have the `browser.dom.window.dump.enabled` browser preference set to `true` in `about:config` (and restart the browser).
 
 Then, try doing things that trigger telemetry calls (e.g. opening a tool). Imagine we had a typo when reporting the tool was opened:
 
-```javascript
+```js
 this._telemetry.toolOpened('mytoolnmae');
                                   ^^^^ typo, should be *mytoolname*
 ```
 
 Would report an error to stdout:
 
-```
+```text
 Warning: An attempt was made to write to the mytoolnmae histogram, which is not defined in Histograms.json
 ```
 
 So watch out for errors.
 
-#### Compile it!
+#### Testing Event Telemetry
+
+This is best shown via an example:
 
-It's strongly recommended that you do a full Firefox build if you have edited either `Histograms.json` or `Scalars.yaml`, as they are processed at build time, and various checks will be run on it to guarantee it is valid.
+```js
+const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
+const expected = [
+  [null, "devtools.main", "open", "tools", null, {
+    entrypoint: "ContextMenu",
+    first_panel: "inspector",
+    host: "bottom",
+    splitconsole: "false",
+    width: "1440"
+  }],
+  [null, "devtools.main", "open", "tools", null, {
+    entrypoint: "KeyShortcut",
+    first_panel: "webconsole",
+    host: "bottom",
+    splitconsole: "false",
+    width: "1440"
+  }]
+];
+
+let snapshot = Services.telemetry.snapshotEvents(OPTOUT, true);
+is(snapshot.parent.length, 0, "No events have been logged");
+
+// Do something to trigger some event telemetry.
+
+for (let i in snapshot.parent) {
+  let actual = snapshot.parent[i];
+
+  // ignore timestamp
+  is(actual.category, expected[i].category, "category is correct");
+  is(actual.method, expected[i].method, "method is correct");
+  is(actual.object, expected[i].object, "object is correct");
+  is(actual.value, expected[i].value, "value is correct");
+
+  is(actual.extra.entrypoint, expected[i].extra.entrypoint, "entrypoint is correct");
+  is(actual.extra.first_panel, expected[i].extra.first_panel, "first_panel is correct");
+  is(actual.extra.host, expected[i].extra.host, "host is correct");
+  is(actual.extra.splitconsole, expected[i].extra.splitconsole, "splitconsole is correct");
+  is(actual.extra.width, expected[i].extra.width, "width is correct");
+}
 
 ```
+
+#### Compile it
+
+You need to do a full Firefox build if you have edited either `Histograms.json` or `Events.yaml`, as they are processed at build time, and various checks will be run on them to guarantee they are valid.
+
+```bash
 ./mach build
 ```
 
 If you use `mach build faster` or artifact builds, the checks will not be performed, and your try builds will fail ("bust") when the checks are run there.
 
 Save yourself some time and run the checks locally.
 
+NOTE: Changes to `Scalars.yaml` *are* processed when doing an artifact build.
+
 ### 4. Getting approval from the data team
 
 This is required before the changes make their way into `mozilla-central`.
 
 To get approval, attach your patch to the bug in Bugzilla, and set two flags:
 
-* a `feedback?` flag for bsmedberg (or someone else from the data team)
-* a `needinfo?` flag to clarkbw (our product manager, so he vouches that we're using the data)
+- a `review?` flag for a data steward.
+- a `needinfo?` flag to hkirschner (our product manager, so he vouches that we're using the data)
 
 Be sure to explain very clearly what is the new probe for. E.g. "We're seeking approval for tracking opens of a new panel for debugging Web API ABCD" is much better than just asking for feedback without background info.
 
 This review shouldn't take too long: if there's something wrong, they should tell you what to fix. If you see no signs of activity after a few days, you can ask in `#developers`.
 
 Note that this review is *in addition* to normal colleague reviews.
 
+Click [here](https://wiki.mozilla.org/Firefox/Data_Collection#Requesting_Data_Collection) for more details.
+
 ## Accessing existing data
 
 ### Local data
 
 Go to [about:telemetry](about:telemetry) to see stats relating to your local instance.
 
 ### Global data
 
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -1,36 +1,42 @@
 /* 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";
 
-const {Ci, Cu, CC} = require("chrome");
+const {Cc, Ci, Cu, CC} = require("chrome");
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
 const defer = require("devtools/shared/defer");
 const {isWindowIncluded} = require("devtools/shared/layout/utils");
 const specs = require("devtools/shared/specs/storage");
 
+const CHROME_ENABLED_PREF = "devtools.chrome.enabled";
+const REMOTE_ENABLED_PREF = "devtools.debugger.remote-enabled";
+
 const DEFAULT_VALUE = "value";
 
 loader.lazyRequireGetter(this, "naturalSortCaseInsensitive",
   "devtools/client/shared/natural-sort", true);
 
 // "Lax", "Strict" and "Unset" are special values of the sameSite property
 // that should not be translated.
 const COOKIE_SAMESITE = {
   LAX: "Lax",
   STRICT: "Strict",
   UNSET: "Unset"
 };
 
+const SAFE_HOSTS_PREFIXES_REGEX =
+  /^(about\+|https?\+|file\+|moz-extension\+)/;
+
 // GUID to be used as a separator in compound keys. This must match the same
 // constant in devtools/client/storage/ui.js,
 // devtools/client/storage/test/head.js and
 // devtools/server/tests/browser/head.js
 const SEPARATOR_GUID = "{9d414cc5-8319-0a04-0586-c0a6ae01670a}";
 
 loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
 loader.lazyImporter(this, "Sqlite", "resource://gre/modules/Sqlite.jsm");
@@ -124,28 +130,34 @@ StorageActors.defaults = function(typeNa
   return {
     typeName: typeName,
 
     get conn() {
       return this.storageActor.conn;
     },
 
     /**
-     * Returns a list of currently knwon hosts for the target window. This list
-     * contains unique hosts from the window + all inner windows.
+     * Returns a list of currently known hosts for the target window. This list
+     * contains unique hosts from the window + all inner windows. If
+     * this._internalHosts is defined then these will also be added to the list.
      */
     get hosts() {
       let hosts = new Set();
       for (let {location} of this.storageActor.windows) {
         let host = this.getHostName(location);
 
         if (host) {
           hosts.add(host);
         }
       }
+      if (this._internalHosts) {
+        for (let host of this._internalHosts) {
+          hosts.add(host);
+        }
+      }
       return hosts;
     },
 
     /**
      * Returns all the windows present on the page. Includes main window + inner
      * iframe windows.
      */
     get windows() {
@@ -338,19 +350,17 @@ StorageActors.defaults = function(typeNa
         data: []
       };
 
       let principal = null;
       if (this.typeName === "indexedDB") {
         // We only acquire principal when the type of the storage is indexedDB
         // because the principal only matters the indexedDB.
         let win = this.storageActor.getWindowFromHost(host);
-        if (win) {
-          principal = win.document.nodePrincipal;
-        }
+        principal = this.getPrincipal(win);
       }
 
       if (names) {
         for (let name of names) {
           let values = await this.getValuesForHost(host, name, options,
             this.hostVsStores, principal);
 
           let {result, objectStores} = values;
@@ -403,16 +413,26 @@ StorageActors.defaults = function(typeNa
             return naturalSortCaseInsensitive(a[sortOn], b[sortOn]);
           });
           let sliced = sorted.slice(offset, offset + size);
           toReturn.data = sliced.map(object => this.toStoreObject(object));
         }
       }
 
       return toReturn;
+    },
+
+    getPrincipal(win) {
+      if (win) {
+        return win.document.nodePrincipal;
+      }
+      // We are running in the browser toolbox and viewing system DBs so we
+      // need to use system principal.
+      return Cc["@mozilla.org/systemprincipal;1"]
+                .createInstance(Ci.nsIPrincipal);
     }
   };
 };
 
 /**
  * Creates an actor and its corresponding front and registers it to the Storage
  * Actor.
  *
@@ -1613,16 +1633,31 @@ StorageActors.createActor({
     this.storageActor.off("window-destroyed", this.onWindowDestroyed);
 
     protocol.Actor.prototype.destroy.call(this);
 
     this.storageActor = null;
   },
 
   /**
+   * Returns a list of currently known hosts for the target window. This list
+   * contains unique hosts from the window, all inner windows and all permanent
+   * indexedDB hosts defined inside the browser.
+   */
+  async getHosts() {
+    // Add internal hosts to this._internalHosts, which will be picked up by
+    // the this.hosts getter. Because this.hosts is a property on the default
+    // storage actor and inherited by all storage actors we have to do it this
+    // way.
+    this._internalHosts = await this.getInternalHosts();
+
+    return this.hosts;
+  },
+
+  /**
    * Remove an indexedDB database from given host with a given name.
    */
   async removeDatabase(host, name) {
     let win = this.storageActor.getWindowFromHost(host);
     if (!win) {
       return { error: `Window for host ${host} not found` };
     }
 
@@ -1730,36 +1765,35 @@ StorageActors.createActor({
    * Purpose of this method is same as populateStoresForHosts but this is async.
    * This exact same operation cannot be performed in populateStoresForHosts
    * method, as that method is called in initialize method of the actor, which
    * cannot be asynchronous.
    */
   async preListStores() {
     this.hostVsStores = new Map();
 
-    for (let host of this.hosts) {
+    for (let host of await this.getHosts()) {
       await this.populateStoresForHost(host);
     }
   },
 
   async populateStoresForHost(host) {
     let storeMap = new Map();
 
     let win = this.storageActor.getWindowFromHost(host);
-    if (win) {
-      let principal = win.document.nodePrincipal;
-      let {names} = await this.getDBNamesForHost(host, principal);
-
-      for (let {name, storage} of names) {
-        let metadata = await this.getDBMetaData(host, principal, name, storage);
-
-        metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
-
-        storeMap.set(`${name} (${storage})`, metadata);
-      }
+    let principal = this.getPrincipal(win);
+
+    let {names} = await this.getDBNamesForHost(host, principal);
+
+    for (let {name, storage} of names) {
+      let metadata = await this.getDBMetaData(host, principal, name, storage);
+
+      metadata = indexedDBHelpers.patchMetadataMapsAndProtos(metadata);
+
+      storeMap.set(`${name} (${storage})`, metadata);
     }
 
     this.hostVsStores.set(host, storeMap);
   },
 
   /**
    * Returns the over-the-wire implementation of the indexed db entity.
    */
@@ -1848,29 +1882,31 @@ StorageActors.createActor({
       this.getNameFromDatabaseFile = indexedDBHelpers.getNameFromDatabaseFile;
       this.getObjectStoreData = indexedDBHelpers.getObjectStoreData;
       this.getSanitizedHost = indexedDBHelpers.getSanitizedHost;
       this.getValuesForHost = indexedDBHelpers.getValuesForHost;
       this.openWithPrincipal = indexedDBHelpers.openWithPrincipal;
       this.removeDB = indexedDBHelpers.removeDB;
       this.removeDBRecord = indexedDBHelpers.removeDBRecord;
       this.splitNameAndStorage = indexedDBHelpers.splitNameAndStorage;
+      this.getInternalHosts = indexedDBHelpers.getInternalHosts;
       return;
     }
 
     const { sendAsyncMessage, addMessageListener } =
       this.conn.parentMessageManager;
 
     this.conn.setupInParent({
       module: "devtools/server/actors/storage",
       setupParent: "setupParentProcessForIndexedDB"
     });
 
     this.getDBMetaData = callParentProcessAsync.bind(null, "getDBMetaData");
     this.splitNameAndStorage = callParentProcessAsync.bind(null, "splitNameAndStorage");
+    this.getInternalHosts = callParentProcessAsync.bind(null, "getInternalHosts");
     this.getDBNamesForHost = callParentProcessAsync.bind(null, "getDBNamesForHost");
     this.getValuesForHost = callParentProcessAsync.bind(null, "getValuesForHost");
     this.removeDB = callParentProcessAsync.bind(null, "removeDB");
     this.removeDBRecord = callParentProcessAsync.bind(null, "removeDBRecord");
     this.clearDBStore = callParentProcessAsync.bind(null, "clearDBStore");
 
     addMessageListener("debug:storage-indexedDB-request-child", msg => {
       switch (msg.json.method) {
@@ -1984,16 +2020,42 @@ var indexedDBHelpers = {
     let storage = name.substr(lastOpenBracketIndex + 1, delta);
 
     name = name.substr(0, lastOpenBracketIndex - 1);
 
     return { storage, name };
   },
 
   /**
+   * Get all "internal" hosts. Internal hosts are database namespaces used by
+   * the browser.
+   */
+  async getInternalHosts() {
+    // Return an empty array if the browser toolbox is not enabled.
+    if (!Services.prefs.getBoolPref(CHROME_ENABLED_PREF) ||
+        !Services.prefs.getBoolPref(REMOTE_ENABLED_PREF)) {
+      return this.backToChild("getInternalHosts", []);
+    }
+
+    let profileDir = OS.Constants.Path.profileDir;
+    let storagePath = OS.Path.join(profileDir, "storage", "permanent");
+    let iterator = new OS.File.DirectoryIterator(storagePath);
+    let hosts = [];
+
+    await iterator.forEach(entry => {
+      if (entry.isDir && !SAFE_HOSTS_PREFIXES_REGEX.test(entry.name)) {
+        hosts.push(entry.name);
+      }
+    });
+    iterator.close();
+
+    return this.backToChild("getInternalHosts", hosts);
+  },
+
+  /**
    * Opens an indexed db connection for the given `principal` and
    * database `name`.
    */
   openWithPrincipal: function(principal, name, storage) {
     return indexedDBForStorage.openForPrincipal(principal, name,
                                                 { storage: storage });
   },
 
@@ -2422,16 +2484,19 @@ var indexedDBHelpers = {
   handleChildRequest(msg) {
     let args = msg.data.args;
 
     switch (msg.json.method) {
       case "getDBMetaData": {
         let [host, principal, name, storage] = args;
         return indexedDBHelpers.getDBMetaData(host, principal, name, storage);
       }
+      case "getInternalHosts": {
+        return indexedDBHelpers.getInternalHosts();
+      }
       case "splitNameAndStorage": {
         let [name] = args;
         return indexedDBHelpers.splitNameAndStorage(name);
       }
       case "getDBNamesForHost": {
         let [host, principal] = args;
         return indexedDBHelpers.getDBNamesForHost(host, principal);
       }
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -88,16 +88,17 @@ skip-if = true # Needs to be updated for
 skip-if = true # Needs to be updated for async actor destruction
 [browser_perf-realtime-markers.js]
 [browser_perf-recording-actor-01.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-recording-actor-02.js]
 skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
 [browser_perf-samples-01.js]
 [browser_perf-samples-02.js]
+[browser_storage_browser_toolbox_indexeddb.js]
 [browser_storage_cookies-duplicate-names.js]
 [browser_storage_dynamic_windows.js]
 [browser_storage_listings.js]
 [browser_storage_updates.js]
 [browser_stylesheets_getTextEmpty.js]
 [browser_stylesheets_nested-iframes.js]
 [browser_timeline.js]
 [browser_timeline_actors.js]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_browser_toolbox_indexeddb.js
@@ -0,0 +1,57 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from storage-helpers.js */
+
+"use strict";
+
+const { StorageFront } = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js",
+  this);
+
+add_task(async function() {
+  await pushPref("devtools.chrome.enabled", true);
+  await pushPref("devtools.debugger.remote-enabled", true);
+  await openTabAndSetupStorage(MAIN_DOMAIN + "storage-updates.html");
+  await createIndexedDB();
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = await connectDebuggerClient(client);
+  let front = StorageFront(client, form);
+
+  await testInternalDBs(front);
+  await clearStorage();
+
+  // Forcing GC/CC to get rid of docshells and windows created by this test.
+  forceCollections();
+  await client.close();
+  forceCollections();
+  DebuggerServer.destroy();
+  forceCollections();
+});
+
+async function createIndexedDB() {
+  let request = indexedDB.open("MyDatabase", 1);
+
+  request.onupgradeneeded = function() {
+    let db = request.result;
+    let store = db.createObjectStore("MyObjectStore", {keyPath: "id"});
+    store.createIndex("NameIndex", ["name.last", "name.first"]);
+  };
+
+  await undefined;
+}
+
+async function testInternalDBs(front) {
+  let data = await front.listStores();
+  let hosts = data.indexedDB.hosts;
+
+  ok(hosts.chrome, `indexedDB hosts contains "chrome"`);
+
+  let path = `["MyDatabase (persistent)","MyObjectStore"]`;
+  let foundDB = hosts.chrome.includes(path);
+  ok(foundDB, `Host "chrome" includes ${path}`);
+}
--- a/devtools/shared/event-emitter.js
+++ b/devtools/shared/event-emitter.js
@@ -257,55 +257,120 @@ class EventEmitter {
 }
 
 module.exports = EventEmitter;
 
 const isEventHandler = (listener) =>
   listener && handler in listener && typeof listener[handler] === "function";
 
 const Services = require("Services");
-const { describeNthCaller } = require("devtools/shared/platform/stack");
+const { getNthPathExcluding } = require("devtools/shared/platform/stack");
 let loggingEnabled = false;
 
 if (!isWorker) {
   loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
   Services.prefs.addObserver("devtools.dump.emit", {
     observe: () => {
       loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
     }
   });
 }
 
 function serialize(target) {
-  let out = String(target);
+  const MAXLEN = 60;
+
+  // Undefined
+  if (typeof target === "undefined") {
+    return "undefined";
+  }
+
+  if (target === null) {
+    return "null";
+  }
 
-  if (target && target.nodeName) {
-    out += " (" + target.nodeName;
+  // Number / String
+  if (typeof target === "string" ||
+      typeof target === "number") {
+    return truncate(target, MAXLEN);
+  }
+
+  // HTML Node
+  if (target.nodeName) {
+    let out = target.nodeName;
+
     if (target.id) {
       out += "#" + target.id;
     }
     if (target.className) {
       out += "." + target.className;
     }
-    out += ")";
+
+    return out;
+  }
+
+  // Array
+  if (Array.isArray(target)) {
+    return truncate(target.toSource(), MAXLEN);
+  }
+
+  // Function
+  if (typeof target === "function") {
+    return `function ${target.name ? target.name : "anonymous"}()`;
+  }
+
+  // Window
+  if (target.constructor &&
+      target.constructor.name &&
+      target.constructor.name === "Window") {
+    return `window (${target.location.origin})`;
   }
 
-  return out;
+  // Object
+  if (typeof target === "object") {
+    let out = "{";
+
+    let entries = Object.entries(target);
+    for (let i = 0; i < Math.min(10, entries.length); i++) {
+      let [name, value] = entries[i];
+
+      if (i > 0) {
+        out += ", ";
+      }
+
+      out += `${name}: ${truncate(value, MAXLEN)}`;
+    }
+
+    return out + "}";
+  }
+
+  // Other
+  return truncate(target.toSource(), MAXLEN);
+}
+
+function truncate(value, maxLen) {
+  // We don't use value.toString() because it can throw.
+  let str = String(value);
+  return str.length > maxLen ? str.substring(0, maxLen) + "..." : str;
 }
 
 function logEvent(type, args) {
   if (!loggingEnabled) {
     return;
   }
 
   let argsOut = "";
-  let description = describeNthCaller(2);
 
   // We need this try / catch to prevent any dead object errors.
   try {
-    argsOut = args.map(serialize).join(", ");
+    argsOut = `${args.map(serialize).join(", ")}`;
   } catch (e) {
     // Object is dead so the toolbox is most likely shutting down,
     // do nothing.
   }
 
-  dump(`EMITTING: emit(${type}${argsOut}) from ${description}\n`);
+  const path = getNthPathExcluding(0, "devtools/shared/event-emitter.js");
+
+  if (args.length > 0) {
+    dump(`EMITTING: emit(${type}, ${argsOut}) from ${path}\n`);
+  } else {
+    dump(`EMITTING: emit(${type}) from ${path}\n`);
+  }
 }
--- a/devtools/shared/old-event-emitter.js
+++ b/devtools/shared/old-event-emitter.js
@@ -1,17 +1,17 @@
 /* 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";
 
 const Services = require("Services");
 const defer = require("devtools/shared/defer");
-const { describeNthCaller } = require("devtools/shared/platform/stack");
+const { getNthPathExcluding } = require("devtools/shared/platform/stack");
 let loggingEnabled = false;
 
 if (!isWorker) {
   loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
   Services.prefs.addObserver("devtools.dump.emit", {
     observe: () => {
       loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
     }
@@ -145,17 +145,17 @@ EventEmitter.prototype = {
     }
   },
 
   logEvent(event, args) {
     if (!loggingEnabled) {
       return;
     }
 
-    let description = describeNthCaller(2);
+    let description = getNthPathExcluding(0, "devtools/shared/old-event-emitter.js");
 
     let argOut = "(";
     if (args.length === 1) {
       argOut += event;
     }
 
     let out = "EMITTING: ";
 
--- a/devtools/shared/platform/stack.js
+++ b/devtools/shared/platform/stack.js
@@ -5,41 +5,46 @@
 // A few wrappers for stack-manipulation.  This version of the module
 // is used in chrome code.
 
 "use strict";
 
 const { Cu, components } = require("chrome");
 
 /**
- * Return a description of the Nth caller, suitable for logging.
+ * Return the Nth path from the stack excluding substr.
  *
- * @param {Number} n the caller to describe
- * @return {String} a description of the nth caller.
+ * @param {Number}
+ *        n the Nth path from the stack to describe.
+ * @param {String} substr
+ *        A segment of the path that should be excluded.
  */
-function describeNthCaller(n) {
-  if (isWorker) {
-    return "";
+function getNthPathExcluding(n, substr) {
+  let stack = components.stack.formattedStack.split("\n");
+
+  // Remove this method from the stack
+  stack = stack.splice(1);
+
+  stack = stack.map(line => {
+    if (line.includes(" -> ")) {
+      return line.split(" -> ")[1];
+    }
+    return line;
+  });
+
+  if (substr) {
+    stack = stack.filter(line => {
+      return line && !line.includes(substr);
+    });
   }
 
-  let caller = components.stack;
-  // Do one extra iteration to skip this function.
-  while (n >= 0) {
-    --n;
-    caller = caller.caller;
+  if (!stack[n]) {
+    n = 0;
   }
-
-  let func = caller.name;
-  let file = caller.filename;
-  if (file.includes(" -> ")) {
-    file = caller.filename.split(/ -> /)[1];
-  }
-  let path = file + ":" + caller.lineNumber;
-
-  return func + "() -> " + path;
+  return (stack[n] || "");
 }
 
 /**
  * Return a stack object that can be serialized and, when
  * deserialized, passed to callFunctionWithAsyncStack.
  */
 function getStack() {
   return components.stack.caller;
@@ -52,10 +57,10 @@ function getStack() {
 function callFunctionWithAsyncStack(callee, stack, id) {
   if (isWorker) {
     return callee();
   }
   return Cu.callFunctionWithAsyncStack(callee, stack, id);
 }
 
 exports.callFunctionWithAsyncStack = callFunctionWithAsyncStack;
-exports.describeNthCaller = describeNthCaller;
+exports.getNthPathExcluding = getNthPathExcluding;
 exports.getStack = getStack;
--- a/devtools/startup/DevToolsShim.jsm
+++ b/devtools/startup/DevToolsShim.jsm
@@ -8,16 +8,25 @@ const { Services } = ChromeUtils.import(
 
 const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
 XPCOMUtils.defineLazyGetter(this, "DevtoolsStartup", () => {
   return Cc["@mozilla.org/devtools/startup-clh;1"]
             .getService(Ci.nsICommandLineHandler)
             .wrappedJSObject;
 });
 
+// We don't want to spend time initializing the full loader here so we create
+// our own lazy require.
+XPCOMUtils.defineLazyGetter(this, "Telemetry", function() {
+  const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+  const Telemetry = require("devtools/client/shared/telemetry");
+
+  return Telemetry;
+});
+
 const DEVTOOLS_ENABLED_PREF = "devtools.enabled";
 const DEVTOOLS_POLICY_DISABLED_PREF = "devtools.policy.disabled";
 
 this.EXPORTED_SYMBOLS = [
   "DevToolsShim",
 ];
 
 function removeItem(array, callback) {
@@ -34,16 +43,24 @@ function removeItem(array, callback) {
  * It can be used to start listening to devtools events before DevTools are ready. As soon
  * as DevTools are enabled, the DevToolsShim will forward all the requests received until
  * then to the real DevTools instance.
  */
 this.DevToolsShim = {
   _gDevTools: null,
   listeners: [],
 
+  get telemetry() {
+    if (!this._telemetry) {
+      this._telemetry = new Telemetry();
+      this._telemetry.setEventRecordingEnabled("devtools.main", true);
+    }
+    return this._telemetry;
+  },
+
   /**
    * Returns true if DevTools are enabled for the current profile. If devtools are not
    * enabled, initializing DevTools will open the onboarding page. Some entry points
    * should no-op in this case.
    */
   isEnabled: function() {
     let enabled = Services.prefs.getBoolPref(DEVTOOLS_ENABLED_PREF);
     return enabled && !this.isDisabledByPolicy();
@@ -208,16 +225,22 @@ this.DevToolsShim = {
    *        optional, if provided should be a valid entry point for DEVTOOLS_ENTRY_POINT
    *        in toolkit/components/telemetry/Histograms.json
    */
   initDevTools: function(reason) {
     if (!this.isEnabled()) {
       throw new Error("DevTools are not enabled and can not be initialized.");
     }
 
+    if (reason) {
+      this.telemetry.addEventProperty(
+        "devtools.main", "open", "tools", null, "entrypoint", reason
+      );
+    }
+
     if (!this.isInitialized()) {
       DevtoolsStartup.initDevTools(reason);
     }
   },
 };
 
 /**
  * Compatibility layer for webextensions.
--- a/devtools/startup/devtools-startup.js
+++ b/devtools/startup/devtools-startup.js
@@ -30,25 +30,35 @@ const kDebuggerPrefs = [
 // startup.
 const TOOLBAR_VISIBLE_PREF = "devtools.toolbar.visible";
 
 const DEVTOOLS_ENABLED_PREF = "devtools.enabled";
 
 const DEVTOOLS_POLICY_DISABLED_PREF = "devtools.policy.disabled";
 
 const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "AppConstants",
                                "resource://gre/modules/AppConstants.jsm");
 ChromeUtils.defineModuleGetter(this, "CustomizableUI",
                                "resource:///modules/CustomizableUI.jsm");
 ChromeUtils.defineModuleGetter(this, "CustomizableWidgets",
                                "resource:///modules/CustomizableWidgets.jsm");
 
+// We don't want to spend time initializing the full loader here so we create
+// our own lazy require.
+XPCOMUtils.defineLazyGetter(this, "Telemetry", function() {
+  const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+  const Telemetry = require("devtools/client/shared/telemetry");
+
+  return Telemetry;
+});
+
 XPCOMUtils.defineLazyGetter(this, "StartupBundle", function() {
   const url = "chrome://devtools-startup/locale/startup.properties";
   return Services.strings.createBundle(url);
 });
 
 XPCOMUtils.defineLazyGetter(this, "KeyShortcutsBundle", function() {
   const url = "chrome://devtools-startup/locale/key-shortcuts.properties";
   return Services.strings.createBundle(url);
@@ -184,16 +194,24 @@ DevToolsStartup.prototype = {
 
   /**
    * Boolean flag to check if the devtools initialization was already sent to telemetry.
    * We only want to record one devtools entry point per Firefox run, but we are not
    * interested in all the entry points (e.g. devtools.toolbar.visible).
    */
   recorded: false,
 
+  get telemetry() {
+    if (!this._telemetry) {
+      this._telemetry = new Telemetry();
+      this._telemetry.setEventRecordingEnabled("devtools.main", true);
+    }
+    return this._telemetry;
+  },
+
   /**
    * Flag that indicates if the developer toggle was already added to customizableUI.
    */
   developerToggleCreated: false,
 
   isDisabledByPolicy: function() {
     return Services.prefs.getBoolPref(DEVTOOLS_POLICY_DISABLED_PREF, false);
   },
@@ -311,17 +329,17 @@ DevToolsStartup.prototype = {
   pingOnboardingTelemetry() {
     // Only ping telemetry once per profile.
     let alreadyLoggedPref = "devtools.onboarding.telemetry.logged";
     if (Services.prefs.getBoolPref(alreadyLoggedPref)) {
       return;
     }
 
     let scalarId = "devtools.onboarding.is_devtools_user";
-    Services.telemetry.scalarSet(scalarId, this.isDevToolsUser());
+    this.telemetry.logScalar(scalarId, this.isDevToolsUser());
     Services.prefs.setBoolPref(alreadyLoggedPref, true);
   },
 
   /**
    * Register listeners to all possible entry points for Developer Tools.
    * But instead of implementing the actual actions, defer to DevTools codebase.
    * In most cases, it only needs to call this.initDevTools which handles the rest.
    * We do that to prevent loading any DevTools module until the user intent to use them.
@@ -821,28 +839,37 @@ DevToolsStartup.prototype = {
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   sendEntryPointTelemetry(reason) {
-    if (reason && !this.recorded) {
-      // Only save the first call for each firefox run as next call
-      // won't necessarely start the tool. For example key shortcuts may
-      // only change the currently selected tool.
-      try {
-        Services.telemetry.getHistogramById("DEVTOOLS_ENTRY_POINT")
-                          .add(reason);
-      } catch (e) {
-        dump("DevTools telemetry entry point failed: " + e + "\n");
-      }
-      this.recorded = true;
+    if (!reason) {
+      return;
+    }
+
+    this.telemetry.addEventProperty(
+      "devtools.main", "open", "tools", null, "entrypoint", reason
+    );
+
+    if (this.recorded) {
+      return;
     }
+
+    // Only save the first call for each firefox run as next call
+    // won't necessarely start the tool. For example key shortcuts may
+    // only change the currently selected tool.
+    try {
+      this.telemetry.log("DEVTOOLS_ENTRY_POINT", reason);
+    } catch (e) {
+      dump("DevTools telemetry entry point failed: " + e + "\n");
+    }
+    this.recorded = true;
   },
 
   // Used by tests and the toolbox to register the same key shortcuts in toolboxes loaded
   // in a window window.
   get KeyShortcuts() {
     return KeyShortcuts;
   },
   get wrappedJSObject() {
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1452839.html
@@ -0,0 +1,8 @@
+<html>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+  document.appendChild(document.createComment(''));
+  document.documentElement.remove();
+})
+</script>
+</html>
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -524,8 +524,9 @@ pref(dom.webcomponents.shadowdom.enabled
 load 1429961.html
 load 1435015.html
 load 1429962.html
 pref(dom.webcomponents.shadowdom.enabled,true) load 1439016.html
 load 1442506.html
 load 1437155.html
 load 1443027-1.html
 load 1448841-1.html
+load 1452839.html
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -8178,17 +8178,17 @@ nsCSSFrameConstructor::ContentRemoved(ns
     // If we're just reconstructing frames for the element, then the
     // following ContentInserted notification on the element will
     // take care of fixing up any adjacent text nodes.  We don't need
     // to do this if the table parent type of our parent type is not
     // eTypeBlock, though, because in that case the whitespace isn't
     // being suppressed due to us anyway.
     if (aOldNextSibling && aFlags == REMOVE_CONTENT &&
         GetParentType(parentType) == eTypeBlock) {
-      MOZ_ASSERT(aChild->GetParent(),
+      MOZ_ASSERT(aChild->GetParentNode(),
                  "How did we have a sibling without a parent?");
       // Adjacent whitespace-only text nodes might have been suppressed if
       // this node does not have inline ends. Create frames for them now
       // if necessary.
       // Reframe any text node just before the node being removed, if there is
       // one, and if it's not the last child or the first child. If a whitespace
       // textframe was being suppressed and it's now the last child or first
       // child then it can stay suppressed since the parent must be a block
--- a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
+++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
@@ -87,17 +87,17 @@ class AccessibleCaretCursorModeTestCase(
 
         # Tap the front of the input to make first caret appear.
         el.tap()
         sel.move_cursor_to_front()
         el.tap(*sel.cursor_location())
 
         # Move first caret to the bottom-right corner of the element.
         src_x, src_y = sel.first_caret_location()
-        dest_x, dest_y = el.size['width'], el.size['height']
+        dest_x, dest_y = el.rect['width'], el.rect['height']
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
         self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         self.assertEqual(target_content, sel.content)
 
     @parameterized(_input_id, el_id=_input_id)
     @parameterized(_textarea_id, el_id=_textarea_id)
     @parameterized(_contenteditable_id, el_id=_contenteditable_id)
@@ -214,17 +214,17 @@ class AccessibleCaretCursorModeTestCase(
         # Tap to make the cursor appear.
         before_image_1 = self.marionette.find_element(By.ID, 'before-image-1')
         before_image_1.tap()
 
         # Tap the front of the content to make first caret appear.
         sel.move_cursor_to_front()
         el.tap(*sel.cursor_location())
         src_x, src_y = sel.first_caret_location()
-        dest_x, dest_y = el.size['width'], el.size['height']
+        dest_x, dest_y = el.rect['width'], el.rect['height']
 
         # Drag the first caret to the bottom-right corner of the element.
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
         self.actions.key_down(content_to_add).key_up(content_to_add).perform()
         self.assertEqual(target_content, sel.content)
 
     def test_move_cursor_to_front_by_dragging_caret_to_front_br_element(self):
--- a/servo/components/script/dom/webgl_validations/tex_image_2d.rs
+++ b/servo/components/script/dom/webgl_validations/tex_image_2d.rs
@@ -146,16 +146,23 @@ impl<'a> WebGLValidator for CommonTexIma
         let internal_format = match TexFormat::from_gl_constant(self.internal_format) {
             Some(format) => format,
             None => {
                 self.context.webgl_error(InvalidEnum);
                 return Err(TexImageValidationError::InvalidTextureFormat);
             }
         };
 
+        // GL_INVALID_VALUE is generated if target is one of the six cube map 2D
+        // image targets and the width and height parameters are not equal.
+        if target.is_cubic() && self.width != self.height {
+            self.context.webgl_error(InvalidValue);
+            return Err(TexImageValidationError::InvalidCubicTextureDimensions);
+        }
+
         // GL_INVALID_VALUE is generated if level is less than 0.
         if self.level < 0 {
             self.context.webgl_error(InvalidValue);
             return Err(TexImageValidationError::NegativeLevel);
         }
 
         // GL_INVALID_VALUE is generated if width or height is less than 0
         if self.width < 0 || self.height < 0 {
@@ -282,23 +289,16 @@ impl<'a> WebGLValidator for TexImage2DVa
             target,
             level,
             internal_format,
             width,
             height,
             border,
         } = self.common_validator.validate()?;
 
-        // GL_INVALID_VALUE is generated if target is one of the six cube map 2D
-        // image targets and the width and height parameters are not equal.
-        if target.is_cubic() && width != height {
-            context.webgl_error(InvalidValue);
-            return Err(TexImageValidationError::InvalidCubicTextureDimensions);
-        }
-
         // GL_INVALID_ENUM is generated if format or data_type is not an
         // accepted value.
         let data_type = match TexDataType::from_gl_constant(self.data_type) {
             Some(data_type) => data_type,
             None => {
                 context.webgl_error(InvalidEnum);
                 return Err(TexImageValidationError::InvalidDataType);
             },
--- a/servo/components/script/dom/webglprogram.rs
+++ b/servo/components/script/dom/webglprogram.rs
@@ -298,20 +298,21 @@ impl WebGLProgram {
             return Err(WebGLError::InvalidOperation);
         }
         if name.len() > MAX_UNIFORM_AND_ATTRIBUTE_LEN {
             return Err(WebGLError::InvalidValue);
         }
 
         // Check if the name is reserved
         if name.starts_with("gl_") {
-            return Err(WebGLError::InvalidOperation);
+            return Ok(None);
         }
 
-        if name.starts_with("webgl") || name.starts_with("_webgl_") {
+        // https://www.khronos.org/registry/webgl/specs/latest/1.0/#GLSL_CONSTRUCTS
+        if name.starts_with("webgl_") || name.starts_with("_webgl_") {
             return Ok(None);
         }
 
         let name = to_name_in_compiled_shader(&name);
 
         let (sender, receiver) = webgl_channel().unwrap();
         self.renderer
             .send(WebGLCommand::GetAttribLocation(self.id, name, sender))
--- a/servo/components/script/dom/webgltexture.rs
+++ b/servo/components/script/dom/webgltexture.rs
@@ -104,17 +104,17 @@ impl WebGLTexture {
             if target != previous_target {
                 return Err(WebGLError::InvalidOperation);
             }
         } else {
             // This is the first time binding
             let face_count = match target {
                 constants::TEXTURE_2D => 1,
                 constants::TEXTURE_CUBE_MAP => 6,
-                _ => return Err(WebGLError::InvalidOperation)
+                _ => return Err(WebGLError::InvalidEnum)
             };
             self.face_count.set(face_count);
             self.target.set(Some(target));
         }
 
         let msg = WebGLCommand::BindTexture(target, Some(self.id));
         self.renderer.send(msg).unwrap();
 
--- a/servo/components/selectors/builder.rs
+++ b/servo/components/selectors/builder.rs
@@ -309,17 +309,17 @@ fn complex_selector_specificity<Impl>(mu
             Component::Class(..) |
             Component::AttributeInNoNamespace { .. } |
             Component::AttributeInNoNamespaceExists { .. } |
             Component::AttributeOther(..) |
 
             Component::FirstChild | Component::LastChild |
             Component::OnlyChild | Component::Root |
             Component::Empty | Component::Scope |
-            Component::Host |
+            Component::Host(..) |
             Component::NthChild(..) |
             Component::NthLastChild(..) |
             Component::NthOfType(..) |
             Component::NthLastOfType(..) |
             Component::FirstOfType | Component::LastOfType |
             Component::OnlyOfType |
             Component::NonTSPseudoClass(..) => {
                 specificity.class_like_selectors += 1
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -687,20 +687,20 @@ where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     debug_assert!(context.shared.is_nested() || !context.shared.in_negation());
 
     match *selector {
         Component::Combinator(_) => unreachable!(),
         Component::Slotted(ref selector) => {
+            // <slots> are never flattened tree slottables.
+            !element.is_html_slot_element() &&
+            element.assigned_slot().is_some() &&
             context.shared.nest(|context| {
-                // <slots> are never flattened tree slottables.
-                !element.is_html_slot_element() &&
-                element.assigned_slot().is_some() &&
                 matches_complex_selector(
                     selector.iter(),
                     element,
                     context,
                     flags_setter,
                 )
             })
         }
@@ -809,18 +809,28 @@ where
         }
         Component::Root => {
             element.is_root()
         }
         Component::Empty => {
             flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR);
             element.is_empty()
         }
-        Component::Host => {
-            context.shared.shadow_host().map_or(false, |host| host == element.opaque())
+        Component::Host(ref selector) => {
+            context.shared.shadow_host().map_or(false, |host| host == element.opaque()) &&
+            selector.as_ref().map_or(true, |selector| {
+                context.shared.nest(|context| {
+                    matches_complex_selector(
+                        selector.iter(),
+                        element,
+                        context,
+                        flags_setter,
+                    )
+                })
+            })
         }
         Component::Scope => {
             match context.shared.scope_element {
                 Some(ref scope_element) => element.opaque() == *scope_element,
                 None => element.is_root(),
             }
         }
         Component::NthChild(a, b) => {
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -376,21 +376,24 @@ where
         V: SelectorVisitor<Impl = Impl>,
     {
         use self::Component::*;
         if !visitor.visit_simple_selector(self) {
             return false;
         }
 
         match *self {
-            Slotted(ref selectors) => {
-                for selector in selectors.iter() {
-                    if !selector.visit(visitor) {
-                        return false;
-                    }
+            Slotted(ref selector) => {
+                if !selector.visit(visitor) {
+                    return false;
+                }
+            }
+            Host(Some(ref selector)) => {
+                if !selector.visit(visitor) {
+                    return false;
                 }
             }
             Negation(ref negated) => {
                 for component in negated.iter() {
                     if !component.visit(visitor) {
                         return false;
                     }
                 }
@@ -613,17 +616,17 @@ impl<'a, Impl: 'a + SelectorImpl> Select
     pub fn next_sequence(&mut self) -> Option<Combinator> {
         self.next_combinator.take()
     }
 
     /// Whether this selector is a featureless host selector, with no
     /// combinators to the left.
     #[inline]
     pub(crate) fn is_featureless_host_selector(&mut self) -> bool {
-        self.all(|component| matches!(*component, Component::Host)) &&
+        self.all(|component| matches!(*component, Component::Host(..))) &&
         self.next_sequence().is_none()
     }
 
     /// Returns remaining count of the simple selectors and combinators in the Selector.
     #[inline]
     pub fn selector_length(&self) -> usize {
         self.iter.len()
     }
@@ -788,20 +791,16 @@ pub enum Component<Impl: SelectorImpl> {
     /// need to think about how this should interact with
     /// visit_complex_selector, and what the consumers of those APIs should do
     /// about the presence of combinators in negation.
     Negation(Box<[Component<Impl>]>),
     FirstChild, LastChild, OnlyChild,
     Root,
     Empty,
     Scope,
-    /// The `:host` pseudo-class:
-    ///
-    /// https://drafts.csswg.org/css-scoping/#host-selector
-    Host,
     NthChild(i32, i32),
     NthLastChild(i32, i32),
     NthOfType(i32, i32),
     NthLastOfType(i32, i32),
     FirstOfType,
     LastOfType,
     OnlyOfType,
     NonTSPseudoClass(Impl::NonTSPseudoClass),
@@ -810,17 +809,29 @@ pub enum Component<Impl: SelectorImpl> {
     ///
     /// https://drafts.csswg.org/css-scoping/#slotted-pseudo
     ///
     /// The selector here is a compound selector, that is, no combinators.
     ///
     /// NOTE(emilio): This should support a list of selectors, but as of this
     /// writing no other browser does, and that allows them to put ::slotted()
     /// in the rule hash, so we do that too.
+    ///
+    /// See https://github.com/w3c/csswg-drafts/issues/2158
     Slotted(Selector<Impl>),
+    /// The `:host` pseudo-class:
+    ///
+    /// https://drafts.csswg.org/css-scoping/#host-selector
+    ///
+    /// NOTE(emilio): This should support a list of selectors, but as of this
+    /// writing no other browser does, and that allows them to put :host()
+    /// in the rule hash, so we do that too.
+    ///
+    /// See https://github.com/w3c/csswg-drafts/issues/2158
+    Host(Option<Selector<Impl>>),
     PseudoElement(Impl::PseudoElement),
 }
 
 impl<Impl: SelectorImpl> Component<Impl> {
     /// Compute the ancestor hash to check against the bloom filter.
     fn ancestor_hash(&self, quirks_mode: QuirksMode) -> Option<u32>
         where Impl::Identifier: PrecomputedHash,
               Impl::ClassName: PrecomputedHash,
@@ -1114,17 +1125,25 @@ impl<Impl: SelectorImpl> ToCss for Compo
             }
 
             FirstChild => dest.write_str(":first-child"),
             LastChild => dest.write_str(":last-child"),
             OnlyChild => dest.write_str(":only-child"),
             Root => dest.write_str(":root"),
             Empty => dest.write_str(":empty"),
             Scope => dest.write_str(":scope"),
-            Host => dest.write_str(":host"),
+            Host(ref selector) => {
+                dest.write_str(":host")?;
+                if let Some(ref selector) = *selector {
+                    dest.write_char('(')?;
+                    selector.to_css(dest)?;
+                    dest.write_char(')')?;
+                }
+                Ok(())
+            },
             FirstOfType => dest.write_str(":first-of-type"),
             LastOfType => dest.write_str(":last-of-type"),
             OnlyOfType => dest.write_str(":only-of-type"),
             NthChild(a, b) | NthLastChild(a, b) | NthOfType(a, b) | NthLastOfType(a, b) => {
                 match *self {
                     NthChild(_, _) => dest.write_str(":nth-child(")?,
                     NthLastChild(_, _) => dest.write_str(":nth-last-child(")?,
                     NthOfType(_, _) => dest.write_str(":nth-of-type(")?,
@@ -1811,16 +1830,17 @@ where
     P: Parser<'i, Impl=Impl>,
     Impl: SelectorImpl,
 {
     match_ignore_ascii_case! { &name,
         "nth-child" => return Ok(parse_nth_pseudo_class(input, Component::NthChild)?),
         "nth-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthOfType)?),
         "nth-last-child" => return Ok(parse_nth_pseudo_class(input, Component::NthLastChild)?),
         "nth-last-of-type" => return Ok(parse_nth_pseudo_class(input, Component::NthLastOfType)?),
+        "host" => return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input)?))),
         "not" => {
             if inside_negation {
                 return Err(input.new_custom_error(
                     SelectorParseErrorKind::UnexpectedIdent("not".into())
                 ));
             }
             return parse_negation(parser, input)
         },
@@ -1964,17 +1984,17 @@ where
 {
     (match_ignore_ascii_case! { &name,
         "first-child" => Ok(Component::FirstChild),
         "last-child"  => Ok(Component::LastChild),
         "only-child"  => Ok(Component::OnlyChild),
         "root" => Ok(Component::Root),
         "empty" => Ok(Component::Empty),
         "scope" => Ok(Component::Scope),
-        "host" if P::parse_host(parser) => Ok(Component::Host),
+        "host" if P::parse_host(parser) => Ok(Component::Host(None)),
         "first-of-type" => Ok(Component::FirstOfType),
         "last-of-type" => Ok(Component::LastOfType),
         "only-of-type" => Ok(Component::OnlyOfType),
         _ => Err(())
     }).or_else(|()| {
         P::parse_non_ts_pseudo_class(parser, location, name)
             .map(Component::NonTSPseudoClass)
     })
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -789,16 +789,24 @@ pub trait TElement
             doc_rules_apply = false;
             f(
                 shadow.style_data(),
                 self.as_node().owner_doc().quirks_mode(),
                 Some(shadow.host()),
             );
         }
 
+        if let Some(shadow) = self.shadow_root() {
+            f(
+                shadow.style_data(),
+                self.as_node().owner_doc().quirks_mode(),
+                Some(shadow.host()),
+            );
+        }
+
         let mut current = self.assigned_slot();
         while let Some(slot) = current {
             // Slots can only have assigned nodes when in a shadow tree.
             let shadow = slot.containing_shadow().unwrap();
             f(
                 shadow.style_data(),
                 self.as_node().owner_doc().quirks_mode(),
                 Some(shadow.host()),
--- a/servo/components/style/invalidation/element/invalidation_map.rs
+++ b/servo/components/style/invalidation/element/invalidation_map.rs
@@ -202,44 +202,35 @@ impl InvalidationMap {
         self.id_to_selector.iter().fold(0, |accum, (_, ref v)| {
             accum + v.len()
         }) +
         self.class_to_selector.iter().fold(0, |accum, (_, ref v)| {
             accum + v.len()
         })
     }
 
-    /// Adds a selector to this `InvalidationMap`.  Returns Err(..) to
-    /// signify OOM.
-    pub fn note_selector(
-        &mut self,
-        selector: &Selector<SelectorImpl>,
-        quirks_mode: QuirksMode,
-    ) -> Result<(), FailedAllocationError> {
-        self.collect_invalidations_for(selector, quirks_mode)
-    }
-
     /// Clears this map, leaving it empty.
     pub fn clear(&mut self) {
         self.class_to_selector.clear();
         self.id_to_selector.clear();
         self.state_affecting_selectors.clear();
         self.document_state_selectors.clear();
         self.other_attribute_affecting_selectors.clear();
         self.has_id_attribute_selectors = false;
         self.has_class_attribute_selectors = false;
     }
 
-    // Returns Err(..) to signify OOM.
-    fn collect_invalidations_for(
+    /// Adds a selector to this `InvalidationMap`.  Returns Err(..) to
+    /// signify OOM.
+    pub fn note_selector(
         &mut self,
         selector: &Selector<SelectorImpl>,
-        quirks_mode: QuirksMode
+        quirks_mode: QuirksMode,
     ) -> Result<(), FailedAllocationError> {
-        debug!("InvalidationMap::collect_invalidations_for({:?})", selector);
+        debug!("InvalidationMap::note_selector({:?})", selector);
 
         let mut iter = selector.iter();
         let mut combinator;
         let mut index = 0;
 
         let mut document_state = DocumentState::empty();
 
         loop {
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 //! The struct that takes care of encapsulating all the logic on where and how
 //! element styles need to be invalidated.
 
 use context::StackLimitChecker;
-use dom::{TElement, TNode};
+use dom::{TElement, TNode, TShadowRoot};
 use selector_parser::SelectorImpl;
 use selectors::matching::{CompoundSelectorMatchingResult, MatchingContext};
 use selectors::matching::matches_compound_selector_from;
 use selectors::parser::{Combinator, Component, Selector};
 use smallvec::SmallVec;
 use std::fmt;
 
 /// A trait to abstract the collection of invalidations for a given pass.
@@ -529,30 +529,32 @@ where
 
         if self.processor.light_tree_only() {
             let node = self.element.as_node();
             return self.invalidate_dom_descendants_of(node, invalidations);
         }
 
         let mut any_descendant = false;
 
-        // NOTE(emilio): This should not be needed for Shadow DOM for normal
-        // element state / attribute invalidations (it's needed for XBL though,
-        // due to the weird way the anon content there works (it doesn't block
-        // combinators)).
+        // NOTE(emilio): This is only needed for Shadow DOM to invalidate
+        // correctly on :host(..) changes. Instead of doing this, we could add
+        // a third kind of invalidation list that walks shadow root children,
+        // but it's not clear it's worth it.
         //
-        // However, it's needed as of right now for document state invalidation,
-        // were we rely on iterating every element that ends up in the composed
-        // doc.
-        //
-        // Also, we could avoid having that special-case for document state
-        // invalidations if we invalidate for document state changes per
-        // subtree, though that's kind of annoying because we need to invalidate
-        // the shadow host subtree (to handle :host and ::slotted), and the
-        // actual shadow tree (to handle all other rules in the ShadowRoot).
+        // Also, it's needed as of right now for document state invalidation,
+        // where we rely on iterating every element that ends up in the composed
+        // doc, but we could fix that invalidating per subtree.
+        if let Some(root) = self.element.shadow_root() {
+            any_descendant |=
+                self.invalidate_dom_descendants_of(root.as_node(), invalidations);
+        }
+
+        // This is needed for XBL (technically) unconditionally, because XBL
+        // bindings do not block combinators in any way. However this is kinda
+        // broken anyway, since we should be looking at XBL rules too.
         if let Some(anon_content) = self.element.xbl_binding_anonymous_content() {
             any_descendant |=
                 self.invalidate_dom_descendants_of(anon_content, invalidations);
         }
 
         if let Some(before) = self.element.before_pseudo_element() {
             any_descendant |=
                 self.invalidate_pseudo_element_or_nac(before, invalidations);
--- a/servo/components/style/properties/data.py
+++ b/servo/components/style/properties/data.py
@@ -205,17 +205,17 @@ class Longhand(object):
             and animation_value_type != "discrete"
         self.is_animatable_with_computed_value = animation_value_type == "ComputedValue" \
             or animation_value_type == "discrete"
         if self.logical:
             # Logical properties will be animatable (i.e. the animation type is
             # discrete). For now, it is still non-animatable.
             self.animatable = False
             self.transitionable = False
-            self.animation_type = None
+            self.animation_value_type = None
 
         # See compute_damage for the various values this can take
         self.servo_restyle_damage = servo_restyle_damage
 
     def experimental(self, product):
         if product == "gecko":
             return bool(self.gecko_pref)
         return bool(self.servo_pref)
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -452,16 +452,17 @@ fn specific_bucket_for<'a>(
         // match the slotted <span>.
         //
         // FIXME(emilio, bug 1426516): The line below causes valgrind failures
         // and it's probably a false positive, we should uncomment whenever
         // jseward is back to confirm / whitelist it.
         //
         // Meanwhile taking the code path below is slower, but still correct.
         // Component::Slotted(ref selector) => find_bucket(selector.iter()),
+        Component::Host(Some(ref selector)) => find_bucket(selector.iter()),
         _ => Bucket::Universal
     }
 }
 
 /// Searches a compound selector from left to right, and returns the appropriate
 /// bucket for it.
 #[inline(always)]
 fn find_bucket<'a>(mut iter: SelectorIter<'a, SelectorImpl>) -> Bucket<'a> {
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -2233,17 +2233,20 @@ impl CascadeData {
                         let rule = Rule::new(
                             selector.clone(),
                             hashes,
                             locked.clone(),
                             self.rules_source_order
                         );
 
                         if rebuild_kind.should_rebuild_invalidation() {
-                            self.invalidation_map.note_selector(&rule.selector, quirks_mode)?;
+                            self.invalidation_map.note_selector(
+                                selector,
+                                quirks_mode,
+                            )?;
                             let mut visitor = StylistSelectorVisitor {
                                 needs_revalidation: false,
                                 passed_rightmost_selector: false,
                                 attribute_dependencies: &mut self.attribute_dependencies,
                                 style_attribute_dependency: &mut self.style_attribute_dependency,
                                 state_dependencies: &mut self.state_dependencies,
                                 document_state_dependencies: &mut self.document_state_dependencies,
                                 mapped_ids: &mut self.mapped_ids,
--- a/testing/geckodriver/CHANGES.md
+++ b/testing/geckodriver/CHANGES.md
@@ -1,13 +1,22 @@
 Change log
 ==========
 
 All notable changes to this program is documented in this file.
 
+
+Unreleased
+----------
+
+### Added
+
+- Support for the new chrome element identifier in Marionette.
+
+
 0.20.1 (2018-04-06)
 -------------------
 
 ### Fixed
 
 - Avoid attempting to kill Firefox process that has stopped.
 
   With the change to allow Firefox enough time to shut down in
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -50,16 +50,18 @@ use webdriver::error::{ErrorStatus, WebD
 use webdriver::server::{WebDriverHandler, Session};
 use webdriver::httpapi::{WebDriverExtensionRoute};
 
 use capabilities::{FirefoxCapabilities, FirefoxOptions};
 use logging;
 use prefs;
 
 const DEFAULT_HOST: &'static str = "localhost";
+const CHROME_ELEMENT_KEY: &'static str = "chromeelement-9fc5-4b51-a3c8-01716eedeb04";
+const LEGACY_ELEMENT_KEY: &'static str = "ELEMENT";
 
 pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> {
     return vec![(Method::Get, "/session/{sessionId}/moz/context", GeckoExtensionRoute::GetContext),
              (Method::Post, "/session/{sessionId}/moz/context", GeckoExtensionRoute::SetContext),
              (Method::Post,
               "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",
               GeckoExtensionRoute::XblAnonymousChildren),
              (Method::Post,
@@ -642,35 +644,36 @@ impl MarionetteSession {
                 self.session_id = session_id.to_string().clone();
             },
             _ => {}
         }
         Ok(())
     }
 
     fn to_web_element(&self, json_data: &Json) -> WebDriverResult<WebElement> {
-        let data = try_opt!(json_data.as_object(),
-                            ErrorStatus::UnknownError,
-                            "Failed to convert data to an object");
+        let data = try_opt!(
+            json_data.as_object(),
+            ErrorStatus::UnknownError,
+            "Failed to convert data to an object"
+        );
+
+        let web_element = data.get(ELEMENT_KEY);
+        let chrome_element = data.get(CHROME_ELEMENT_KEY);
+        let legacy_element = data.get(LEGACY_ELEMENT_KEY);
+
+        let value = try_opt!(
+            web_element.or(chrome_element).or(legacy_element),
+            ErrorStatus::UnknownError,
+            "Failed to extract web element from Marionette response"
+        );
         let id = try_opt!(
-            try_opt!(
-                match data.get("ELEMENT") {
-                    Some(id) => Some(id),
-                    None => {
-                        match data.get(ELEMENT_KEY) {
-                            Some(id) => Some(id),
-                            None => None
-                        }
-                    }
-                },
-                ErrorStatus::UnknownError,
-                "Failed to extract Web Element from response").as_string(),
+            value.as_string(),
             ErrorStatus::UnknownError,
-            "Failed to convert id value to string"
-            ).to_string();
+            "Failed to convert web element reference value to string"
+        ).to_string();
         Ok(WebElement::new(id))
     }
 
     pub fn next_command_id(&mut self) -> u64 {
         self.command_id = self.command_id + 1;
         self.command_id
     }
 
--- a/testing/marionette/client/marionette_driver/addons.py
+++ b/testing/marionette/client/marionette_driver/addons.py
@@ -44,17 +44,18 @@ class Addons(object):
 
         :returns: The addon ID string of the newly installed addon.
 
         :raises: :exc:`AddonInstallException`
 
         """
         body = {"path": path, "temporary": temp}
         try:
-            return self._mn._send_message("addon:install", body, key="value")
+            return self._mn._send_message("Addon:Install",
+                                          body, key="value")
         except errors.UnknownException as e:
             raise AddonInstallException(e)
 
     def uninstall(self, addon_id):
         """Uninstall a Firefox addon.
 
         If the addon is restartless, it will be uninstalled right away.
         Otherwise a restart using :func:`~marionette_driver.marionette.Marionette.restart`
@@ -63,10 +64,10 @@ class Addons(object):
         If the call to uninstall is resulting in a `ScriptTimeoutException`,
         an invalid ID is likely being passed in. Unfortunately due to
         AddonManager's implementation, it's hard to retrieve this error from
         Python.
 
         :param addon_id: The addon ID string to uninstall.
 
         """
-        body = {"id": addon_id}
-        self._mn._send_message("addon:uninstall", body)
+        self._mn._send_message("Addon:Uninstall",
+                               {"id": addon_id})
--- a/testing/marionette/client/marionette_driver/localization.py
+++ b/testing/marionette/client/marionette_driver/localization.py
@@ -33,24 +33,24 @@ class L10n(object):
 
         :param dtd_urls: List of .dtd URLs which will be used to search for the entity.
         :param entity_id: ID of the entity to retrieve the localized string for.
 
         :returns: The localized string for the requested entity.
         :raises: :exc:`NoSuchElementException`
         """
         body = {"urls": dtd_urls, "id": entity_id}
-        return self._marionette._send_message("localization:l10n:localizeEntity",
+        return self._marionette._send_message("L10n:LocalizeEntity",
                                               body, key="value")
 
     def localize_property(self, properties_urls, property_id):
         """Retrieve the localized string for the specified property id.
 
         :param properties_urls: List of .properties URLs which will be used to
                                 search for the property.
         :param property_id: ID of the property to retrieve the localized string for.
 
         :returns: The localized string for the requested property.
         :raises: :exc:`NoSuchElementException`
         """
         body = {"urls": properties_urls, "id": property_id}
-        return self._marionette._send_message("localization:l10n:localizeProperty",
+        return self._marionette._send_message("L10n:LocalizeProperty",
                                               body, key="value")
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -63,138 +63,124 @@ class HTMLElement(object):
         """
         return self.marionette.find_elements(method, target, self.id)
 
     def get_attribute(self, name):
         """Returns the requested attribute, or None if no attribute
         is set.
         """
         body = {"id": self.id, "name": name}
-        return self.marionette._send_message("getElementAttribute", body, key="value")
+        return self.marionette._send_message("WebDriver:GetElementAttribute",
+                                             body, key="value")
 
     def get_property(self, name):
         """Returns the requested property, or None if the property is
         not set.
         """
         try:
             body = {"id": self.id, "name": name}
-            return self.marionette._send_message("getElementProperty", body, key="value")
+            return self.marionette._send_message("WebDriver:GetElementProperty",
+                                                 body, key="value")
         except errors.UnknownCommandException:
             # Keep backward compatibility for code which uses get_attribute() to
             # also retrieve element properties.
             # Remove when Firefox 55 is stable.
             return self.get_attribute(name)
 
     def click(self):
         """Simulates a click on the element."""
-        self.marionette._send_message("clickElement", {"id": self.id})
+        self.marionette._send_message("WebDriver:ElementClick",
+                                      {"id": self.id})
 
     def tap(self, x=None, y=None):
         """Simulates a set of tap events on the element.
 
         :param x: X coordinate of tap event.  If not given, default to
             the centre of the element.
         :param y: Y coordinate of tap event. If not given, default to
             the centre of the element.
         """
         body = {"id": self.id, "x": x, "y": y}
         self.marionette._send_message("singleTap", body)
 
     @property
     def text(self):
         """Returns the visible text of the element, and its child elements."""
         body = {"id": self.id}
-        return self.marionette._send_message("getElementText", body, key="value")
+        return self.marionette._send_message("WebDriver:GetElementText",
+                                             body, key="value")
 
     def send_keys(self, *strings):
         """Sends the string via synthesized keypresses to the element.
            If an array is passed in like `marionette.send_keys(Keys.SHIFT, "a")` it
            will be joined into a string.
            If an integer is passed in like `marionette.send_keys(1234)` it will be
            coerced into a string.
         """
         keys = Marionette.convert_keys(*strings)
-        body = {"id": self.id, "text": keys}
-        self.marionette._send_message("sendKeysToElement", body)
+        self.marionette._send_message("WebDriver:ElementSendKeys",
+                                      {"id": self.id, "text": keys})
 
     def clear(self):
         """Clears the input of the element."""
-        self.marionette._send_message("clearElement", {"id": self.id})
+        self.marionette._send_message("WebDriver:ElementClear",
+                                      {"id": self.id})
 
     def is_selected(self):
         """Returns True if the element is selected."""
         body = {"id": self.id}
-        return self.marionette._send_message("isElementSelected", body, key="value")
+        return self.marionette._send_message("WebDriver:IsElementSelected",
+                                             body, key="value")
 
     def is_enabled(self):
         """This command will return False if all the following criteria
         are met otherwise return True:
 
         * A form control is disabled.
         * A ``HTMLElement`` has a disabled boolean attribute.
         """
         body = {"id": self.id}
-        return self.marionette._send_message("isElementEnabled", body, key="value")
+        return self.marionette._send_message("WebDriver:IsElementEnabled",
+                                             body, key="value")
 
     def is_displayed(self):
         """Returns True if the element is displayed, False otherwise."""
         body = {"id": self.id}
-        return self.marionette._send_message("isElementDisplayed", body, key="value")
-
-    @property
-    def size(self):
-        """A dictionary with the size of the element."""
-        warnings.warn("The size property has been deprecated and will be removed in a future version. \
-            Please use HTMLElement#rect", DeprecationWarning)
-        rect = self.rect
-        return {"width": rect["width"], "height": rect["height"]}
+        return self.marionette._send_message("WebDriver:IsElementDisplayed",
+                                             body, key="value")
 
     @property
     def tag_name(self):
         """The tag name of the element."""
         body = {"id": self.id}
-        return self.marionette._send_message("getElementTagName", body, key="value")
-
-    @property
-    def location(self):
-        """Get an element's location on the page.
-
-        The returned point will contain the x and y coordinates of the
-        top left-hand corner of the given element.  The point (0,0)
-        refers to the upper-left corner of the document.
-
-        :returns: a dictionary containing x and y as entries
-        """
-        warnings.warn("The location property has been deprecated and will be removed in a future version. \
-            Please use HTMLElement#rect", DeprecationWarning)
-        rect = self.rect
-        return {"x": rect["x"], "y": rect["y"]}
+        return self.marionette._send_message("WebDriver:GetElementTagName",
+                                             body, key="value")
 
     @property
     def rect(self):
         """Gets the element's bounding rectangle.
 
         This will return a dictionary with the following:
 
           * x and y represent the top left coordinates of the ``HTMLElement``
             relative to top left corner of the document.
           * height and the width will contain the height and the width
             of the DOMRect of the ``HTMLElement``.
         """
-        body = {"id": self.id}
-        return self.marionette._send_message("getElementRect", body)
+        return self.marionette._send_message("WebDriver:GetElementRect",
+                                             {"id": self.id})
 
     def value_of_css_property(self, property_name):
         """Gets the value of the specified CSS property name.
 
         :param property_name: Property name to get the value of.
         """
         body = {"id": self.id, "propertyName": property_name}
-        return self.marionette._send_message(
-            "getElementValueOfCssProperty", body, key="value")
+        return self.marionette._send_message("WebDriver:GetElementCSSValue",
+                                             body, key="value")
 
 
 class MouseButton(object):
     """Enum-like class for mouse button constants."""
     LEFT = 0
     MIDDLE = 1
     RIGHT = 2
 
@@ -465,17 +451,22 @@ class Actions(object):
         """
         self.action_chain.append(['keyUp', key_code])
         return self
 
     def perform(self):
         """Sends the action chain built so far to the server side for
         execution and clears the current chain of actions."""
         body = {"chain": self.action_chain, "nextId": self.current_id}
-        self.current_id = self.marionette._send_message("actionChain", body, key="value")
+        try:
+            self.current_id = self.marionette._send_message("Marionette:ActionChain",
+                                                            body, key="value")
+        except errors.UnknownCommandException:
+            self.current_id = self.marionette._send_message("actionChain",
+                                                            body, key="value")
         self.action_chain = []
         return self
 
 
 class MultiActions(object):
     '''
     A MultiActions object represents a sequence of actions that may be
     performed at the same time. Its intent is to allow the simulation
@@ -510,49 +501,54 @@ class MultiActions(object):
         self.multi_actions.append(action.action_chain)
         if len(action.action_chain) > self.max_length:
             self.max_length = len(action.action_chain)
         return self
 
     def perform(self):
         """Perform all the actions added to this object."""
         body = {"value": self.multi_actions, "max_length": self.max_length}
-        self.marionette._send_message("multiAction", body)
+        try:
+            self.marionette._send_message("Marionette:MultiAction", body)
+        except errors.UnknownCommandException:
+            self.marionette._send_message("multiAction", body)
 
 
 class Alert(object):
     """A class for interacting with alerts.
 
     ::
 
         Alert(marionette).accept()
         Alert(marionette).dismiss()
     """
 
     def __init__(self, marionette):
         self.marionette = marionette
 
     def accept(self):
         """Accept a currently displayed modal dialog."""
-        self.marionette._send_message("acceptDialog")
+        # TODO: Switch to "WebDriver:AcceptAlert" in Firefox 62
+        self.marionette._send_message("WebDriver:AcceptDialog")
 
     def dismiss(self):
         """Dismiss a currently displayed modal dialog."""
-        self.marionette._send_message("dismissDialog")
+        self.marionette._send_message("WebDriver:DismissAlert")
 
     @property
     def text(self):
         """Return the currently displayed text in a tab modal."""
-        return self.marionette._send_message("getTextFromDialog", key="value")
+        return self.marionette._send_message("WebDriver:GetAlertText",
+                                             key="value")
 
     def send_keys(self, *string):
         """Send keys to the currently displayed text input area in an open
         tab modal dialog."""
-        body = {"text": Marionette.convert_keys(*string)}
-        self.marionette._send_message("sendKeysToDialog", body)
+        self.marionette._send_message("WebDriver:SendAlertText",
+                                      {"text": Marionette.convert_keys(*string)})
 
 
 class Marionette(object):
     """Represents a Marionette connection to a browser or device."""
 
     CONTEXT_CHROME = "chrome"  # non-browser content: windows, dialogs, etc.
     CONTEXT_CONTENT = "content"  # browser content: iframes, divs, etc.
     DEFAULT_STARTUP_TIMEOUT = 120
@@ -725,17 +721,17 @@ class Marionette(object):
         :param name: Requested command key.
         :param params: Optional dictionary of key/value arguments.
         :param key: Optional key to extract from response.
 
         :returns: Full response from the server, or if `key` is given,
             the value of said key in the response.
         """
 
-        if not self.session_id and name != "newSession":
+        if not self.session_id and name != "WebDriver:NewSession":
             raise errors.MarionetteException("Please start a session")
 
         try:
             msg = self.client.request(name, params)
 
         except IOError:
             self.delete_session(send_request=False)
             raise
@@ -991,17 +987,18 @@ class Marionette(object):
                     case prefInterface.PREF_INVALID:
                         return false;
                 }}
                 """.format(pref, value))
                 if not pref_exists:
                     break
 
         if not pref_exists:
-            context = self._send_message("getContext", key="value")
+            context = self._send_message("Marionette:GetContext",
+                                         key="value")
             self.delete_session()
             self.instance.restart(prefs)
             self.raise_for_port()
             self.start_session()
 
             # Restore the context as used before the restart
             self.set_context(context)
 
@@ -1052,17 +1049,18 @@ class Marionette(object):
             if canceled:
                 raise errors.MarionetteException(
                     "Something cancelled the quit application request")
 
         body = None
         if len(flags) > 0:
             body = {"flags": list(flags)}
 
-        return self._send_message("quitApplication", body, key="cause")
+        return self._send_message("Marionette:Quit",
+                                  body, key="cause")
 
     @do_process_check
     def quit(self, clean=False, in_app=False, callback=None):
         """Terminate the currently running instance.
 
         This command will delete the active marionette session. It also allows
         manipulation of eg. the profile data while the application is not running.
         To start the application again, :func:`start_session` has to be called.
@@ -1081,17 +1079,18 @@ class Marionette(object):
                                              "on Gecko instances launched by Marionette")
 
         cause = None
         if in_app:
             if callback is not None:
                 if not callable(callback):
                     raise ValueError("Specified callback '{}' is not callable".format(callback))
 
-                self._send_message("acceptConnections", {"value": False})
+                self._send_message("Marionette:AcceptConnections",
+                                   {"value": False})
                 callback()
             else:
                 cause = self._request_in_app_shutdown()
 
             self.delete_session(send_request=False)
 
             # Give the application some time to shutdown
             returncode = self.instance.runner.wait(timeout=self.DEFAULT_SHUTDOWN_TIMEOUT)
@@ -1124,28 +1123,30 @@ class Marionette(object):
                        browser. Otherwise the browser will be restarted immediately
                        by killing the process.
         :param callback: If provided and `in_app` is True, the callback will be
                          used to trigger the restart.
         """
         if not self.instance:
             raise errors.MarionetteException("restart() can only be called "
                                              "on Gecko instances launched by Marionette")
-        context = self._send_message("getContext", key="value")
+        context = self._send_message("Marionette:GetContext",
+                                     key="value")
 
         cause = None
         if in_app:
             if clean:
                 raise ValueError("An in_app restart cannot be triggered with the clean flag set")
 
             if callback is not None:
                 if not callable(callback):
                     raise ValueError("Specified callback '{}' is not callable".format(callback))
 
-                self._send_message("acceptConnections", {"value": False})
+                self._send_message("Marionette:AcceptConnections",
+                                   {"value": False})
                 callback()
             else:
                 cause = self._request_in_app_shutdown("eRestart")
 
             self.delete_session(send_request=False)
 
             try:
                 timeout = self.DEFAULT_SHUTDOWN_TIMEOUT + self.DEFAULT_STARTUP_TIMEOUT
@@ -1233,17 +1234,18 @@ class Marionette(object):
         # top-level, and after bug 1388424 removed support for overriding
         # the session ID, we also do this with this client.  However,
         # because this client is used with older Firefoxen (through upgrade
         # tests et al.) we need to preserve backwards compatibility until
         # Firefox 60.
         if "capabilities" not in body and capabilities is not None:
             body["capabilities"] = dict(capabilities)
 
-        resp = self._send_message("newSession", body)
+        resp = self._send_message("WebDriver:NewSession",
+                                  body)
         self.session_id = resp["sessionId"]
         self.session = resp["capabilities"]
         # fallback to processId can be removed in Firefox 55
         self.process_id = self.session.get("moz:processID", self.session.get("processId"))
         self.profile = self.session.get("moz:profile")
 
         return self.session
 
@@ -1259,17 +1261,17 @@ class Marionette(object):
         """Close the current session and disconnect from the server.
 
         :param send_request: Optional, if `True` a request to close the session on
             the server side will be sent. Use `False` in case of eg. in_app restart()
             or quit(), which trigger a deletion themselves. Defaults to `True`.
         """
         try:
             if send_request:
-                self._send_message("deleteSession")
+                self._send_message("WebDriver:DeleteSession")
         finally:
             self.process_id = None
             self.profile = None
             self.session = None
             self.session_id = None
             self.window = None
 
             if self.client is not None:
@@ -1278,104 +1280,47 @@ class Marionette(object):
     @property
     def session_capabilities(self):
         """A JSON dictionary representing the capabilities of the
         current session.
 
         """
         return self.session
 
-    def set_script_timeout(self, timeout):
-        """Sets the maximum number of ms that an asynchronous script is
-        allowed to run.
-
-        If a script does not return in the specified amount of time,
-        a ``ScriptTimeoutException`` is raised.
-
-        :param timeout: The maximum number of milliseconds an asynchronous
-            script can run without causing an ``ScriptTimeoutException`` to
-            be raised
-
-        .. note:: `set_script_timeout` is deprecated, please use
-            `timeout.script` setter.
-
-        """
-        warnings.warn(
-            "set_script_timeout is deprecated, please use timeout.script setter",
-            DeprecationWarning)
-        self.timeout.script = timeout / 1000
-
-    def set_search_timeout(self, timeout):
-        """Sets a timeout for the find methods.
-
-        When searching for an element using either :func:`find_element` or
-        :func:`find_elements`, the method will continue
-        trying to locate the element for up to timeout ms. This can be
-        useful if, for example, the element you're looking for might
-        not exist immediately, because it belongs to a page which is
-        currently being loaded.
-
-        :param timeout: Timeout in milliseconds.
-
-        .. note:: `set_search_timeout` is deprecated, please use
-            `timeout.implicit` setter.
-
-        """
-        warnings.warn(
-            "set_search_timeout is deprecated, please use timeout.implicit setter",
-            DeprecationWarning)
-        self.timeout.implicit = timeout / 1000
-
-    def set_page_load_timeout(self, timeout):
-        """Sets a timeout for loading pages.
-
-        A page load timeout specifies the amount of time the Marionette
-        instance should wait for a page load operation to complete. A
-        ``TimeoutException`` is returned if this limit is exceeded.
-
-        :param timeout: Timeout in milliseconds.
-
-        .. note:: `set_page_load_timeout` is deprecated, please use
-            `timeout.page_load` setter.
-
-        """
-        warnings.warn(
-            "set_page_load_timeout is deprecated, please use timeout.page_load setter",
-            DeprecationWarning)
-        self.timeout.page_load = timeout / 1000
-
     @property
     def current_window_handle(self):
         """Get the current window's handle.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
 
         :returns: unique window handle
         :rtype: string
         """
-        self.window = self._send_message("getWindowHandle", key="value")
+        self.window = self._send_message("WebDriver:GetWindowHandle",
+                                         key="value")
         return self.window
 
     @property
     def current_chrome_window_handle(self):
         """Get the current chrome window's handle. Corresponds to
         a chrome window that may itself contain tabs identified by
         window_handles.
 
         Returns an opaque server-assigned identifier to this window
         that uniquely identifies it within this Marionette instance.
         This can be used to switch to this window at a later point.
 
         :returns: unique window handle
         :rtype: string
         """
-        self.chrome_window = self._send_message(
-            "getCurrentChromeWindowHandle", key="value")
+        self.chrome_window = self._send_message("WebDriver:GetChromeWindowHandle",
+                                                key="value")
+
         return self.chrome_window
 
     def get_window_position(self):
         """Get the current window's position.
 
         :returns: a dictionary with x and y
         """
         warnings.warn("get_window_position() has been deprecated, please use get_window_rect()",
@@ -1404,107 +1349,112 @@ class Marionette(object):
         :param x: x coordinate for the top left of the window
         :param y: y coordinate for the top left of the window
         :param width: The width to resize the window to.
         :param height: The height to resize the window to.
         """
         if (x is None and y is None) and (height is None and width is None):
             raise errors.InvalidArgumentException("x and y or height and width need values")
 
-        return self._send_message("setWindowRect", {"x": x, "y": y,
-                                                    "height": height,
-                                                    "width": width})
+        body = {"x": x, "y": y, "height": height, "width": width}
+        return self._send_message("WebDriver:SetWindowRect",
+                                  body)
 
     @property
     def window_rect(self):
-        return self._send_message("getWindowRect")
+        return self._send_message("WebDriver:GetWindowRect")
 
     @property
     def title(self):
         """Current title of the active window."""
-        return self._send_message("getTitle", key="value")
+        return self._send_message("WebDriver:GetTitle",
+                                  key="value")
 
     @property
     def window_handles(self):
         """Get list of windows in the current context.
 
         If called in the content context it will return a list of
         references to all available browser windows.  Called in the
         chrome context, it will list all available windows, not just
         browser windows (e.g. not just navigator.browser).
 
         Each window handle is assigned by the server, and the list of
         strings returned does not have a guaranteed ordering.
 
         :returns: Unordered list of unique window handles as strings
         """
-        return self._send_message("getWindowHandles")
+        return self._send_message("WebDriver:GetWindowHandles")
 
     @property
     def chrome_window_handles(self):
         """Get a list of currently open chrome windows.
 
         Each window handle is assigned by the server, and the list of
         strings returned does not have a guaranteed ordering.
 
         :returns: Unordered list of unique chrome window handles as strings
         """
-        return self._send_message("getChromeWindowHandles")
+        return self._send_message("WebDriver:GetChromeWindowHandles")
 
     @property
     def page_source(self):
         """A string representation of the DOM."""
-        return self._send_message("getPageSource", key="value")
+        return self._send_message("WebDriver:GetPageSource",
+                                  key="value")
 
     def close(self):
         """Close the current window, ending the session if it's the last
         window currently open.
 
         :returns: Unordered list of remaining unique window handles as strings
         """
-        return self._send_message("close")
+        return self._send_message("WebDriver:CloseWindow")
 
     def close_chrome_window(self):
         """Close the currently selected chrome window, ending the session
         if it's the last window open.
 
         :returns: Unordered list of remaining unique chrome window handles as strings
         """
-        return self._send_message("closeChromeWindow")
+        return self._send_message("WebDriver:CloseChromeWindow")
 
     def set_context(self, context):
         """Sets the context that Marionette commands are running in.
 
         :param context: Context, may be one of the class properties
             `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
             marionette.set_context(marionette.CONTEXT_CHROME)
         """
         if context not in [self.CONTEXT_CHROME, self.CONTEXT_CONTENT]:
             raise ValueError("Unknown context: {}".format(context))
-        self._send_message("setContext", {"value": context})
+
+        self._send_message("Marionette:SetContext",
+                           {"value": context})
 
     @contextmanager
     def using_context(self, context):
         """Sets the context that Marionette commands are running in using
         a `with` statement. The state of the context on the server is
         saved before entering the block, and restored upon exiting it.
 
         :param context: Context, may be one of the class properties
             `CONTEXT_CHROME` or `CONTEXT_CONTENT`.
 
         Usage example::
 
             with marionette.using_context(marionette.CONTEXT_CHROME):
                 # chrome scope
                 ... do stuff ...
         """
-        scope = self._send_message("getContext", key="value")
+        scope = self._send_message("Marionette:GetContext",
+                                   key="value")
         self.set_context(context)
         try:
             yield
         finally:
             self.set_context(scope)
 
     def switch_to_alert(self):
         """Returns an :class:`~marionette_driver.marionette.Alert` object for
@@ -1522,34 +1472,35 @@ class Marionette(object):
         """Switch to the specified window; subsequent commands will be
         directed at the new window.
 
         :param window_id: The id or name of the window to switch to.
 
         :param focus: A boolean value which determins whether to focus
             the window that we just switched to.
         """
-        body = {"focus": focus, "name": window_id}
-        self._send_message("switchToWindow", body)
+        self._send_message("WebDriver:SwitchToWindow",
+                           {"focus": focus, "name": window_id})
         self.window = window_id
 
     def get_active_frame(self):
         """Returns an :class:`~marionette_driver.marionette.HTMLElement`
         representing the frame Marionette is currently acting on."""
-        return self._send_message("getActiveFrame", key="value")
+        return self._send_message("WebDriver:GetActiveFrame",
+                                  key="value")
 
     def switch_to_default_content(self):
         """Switch the current context to page's default content."""
         return self.switch_to_frame()
 
     def switch_to_parent_frame(self):
         """
            Switch to the Parent Frame
         """
-        self._send_message("switchToParentFrame")
+        self._send_message("WebDriver:SwitchToParentFrame")
 
     def switch_to_frame(self, frame=None, focus=True):
         """Switch the current context to the specified frame. Subsequent
         commands will operate in the context of the specified frame,
         if applicable.
 
         :param frame: A reference to the frame to switch to.  This can
             be an :class:`~marionette_driver.marionette.HTMLElement`,
@@ -1560,55 +1511,64 @@ class Marionette(object):
         :param focus: A boolean value which determins whether to focus
             the frame that we just switched to.
         """
         body = {"focus": focus}
         if isinstance(frame, HTMLElement):
             body["element"] = frame.id
         elif frame is not None:
             body["id"] = frame
-        self._send_message("switchToFrame", body)
+
+        self._send_message("WebDriver:SwitchToFrame",
+                           body)
 
     def switch_to_shadow_root(self, host=None):
         """Switch the current context to the specified host's Shadow DOM.
         Subsequent commands will operate in the context of the specified Shadow
         DOM, if applicable.
 
         :param host: A reference to the host element containing Shadow DOM.
             This can be an :class:`~marionette_driver.marionette.HTMLElement`.
             If you call ``switch_to_shadow_root`` without an argument, it will
             switch to the parent Shadow DOM or the top-level frame.
         """
         body = {}
         if isinstance(host, HTMLElement):
             body["id"] = host.id
-        return self._send_message("switchToShadowRoot", body)
+
+        return self._send_message("WebDriver:SwitchToShadowRoot",
+                                  body)
 
     def get_url(self):
         """Get a string representing the current URL.
 
         On Desktop this returns a string representation of the URL of
         the current top level browsing context.  This is equivalent to
         document.location.href.
 
         When in the context of the chrome, this returns the canonical
         URL of the current resource.
 
         :returns: string representation of URL
         """
-        return self._send_message("getCurrentUrl", key="value")
+        return self._send_message("WebDriver:GetCurrentURL",
+                                  key="value")
 
     def get_window_type(self):
         """Gets the windowtype attribute of the window Marionette is
         currently acting on.
 
         This command only makes sense in a chrome context. You might use this
         method to distinguish a browser window from an editor window.
         """
-        return self._send_message("getWindowType", key="value")
+        try:
+            return self._send_message("Marionette:GetWindowType",
+                                      key="value")
+        except errors.UnknownCommandException:
+            return self._send_message("getWindowType", key="value")
 
     def navigate(self, url):
         """Navigate to given `url`.
 
         Navigates the current top-level browsing context's content
         frame to the given URL and waits for the document to load or
         the session's page timeout duration to elapse before returning.
 
@@ -1623,29 +1583,30 @@ class Marionette(object):
         `window` triggers and `document.readyState` is "complete".
 
         In chrome context it will change the current `window`'s location
         to the supplied URL and wait until `document.readyState` equals
         "complete" or the page timeout duration has elapsed.
 
         :param url: The URL to navigate to.
         """
-        self._send_message("get", {"url": url})
+        self._send_message("WebDriver:Navigate",
+                           {"url": url})
 
     def go_back(self):
         """Causes the browser to perform a back navigation."""
-        self._send_message("goBack")
+        self._send_message("WebDriver:Back")
 
     def go_forward(self):
         """Causes the browser to perform a forward navigation."""
-        self._send_message("goForward")
+        self._send_message("WebDriver:Forward")
 
     def refresh(self):
         """Causes the browser to perform to refresh the current page."""
-        self._send_message("refresh")
+        self._send_message("WebDriver:Refresh")
 
     def _to_json(self, args):
         if isinstance(args, list) or isinstance(args, tuple):
             wrapped = []
             for arg in args:
                 wrapped.append(self._to_json(arg))
         elif isinstance(args, dict):
             wrapped = {}
@@ -1674,32 +1635,16 @@ class Marionette(object):
                     unwrapped = HTMLElement(self, value[key])
                     break
                 else:
                     unwrapped[key] = self._from_json(value[key])
         else:
             unwrapped = value
         return unwrapped
 
-    def execute_js_script(self, script, script_args=(), async=True,
-                          new_sandbox=True, script_timeout=None,
-                          inactivity_timeout=None, filename=None,
-                          sandbox='default'):
-        args = self._to_json(script_args)
-        body = {"script": script,
-                "args": args,
-                "async": async,
-                "newSandbox": new_sandbox,
-                "scriptTimeout": script_timeout,
-                "inactivityTimeout": inactivity_timeout,
-                "filename": filename,
-                "line": None}
-        rv = self._send_message("executeJSScript", body, key="value")
-        return self._from_json(rv)
-
     def execute_script(self, script, script_args=(), new_sandbox=True,
                        sandbox="default", script_timeout=None):
         """Executes a synchronous JavaScript script, and returns the
         result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
         :func:`set_context` call, or to the CONTEXT_CONTENT context if
         :func:`set_context` has not been called.
@@ -1763,17 +1708,19 @@ class Marionette(object):
         frame = stack[-2:-1][0]  # grab the second-to-last frame
         body = {"script": script,
                 "args": args,
                 "newSandbox": new_sandbox,
                 "sandbox": sandbox,
                 "scriptTimeout": script_timeout,
                 "line": int(frame[1]),
                 "filename": os.path.basename(frame[0])}
-        rv = self._send_message("executeScript", body, key="value")
+
+        rv = self._send_message("WebDriver:ExecuteScript",
+                                body, key="value")
         return self._from_json(rv)
 
     def execute_async_script(self, script, script_args=(), new_sandbox=True,
                              sandbox="default", script_timeout=None,
                              debug_script=False):
         """Executes an asynchronous JavaScript script, and returns the
         result (or None if the script does return a value).
 
@@ -1812,17 +1759,19 @@ class Marionette(object):
         body = {"script": script,
                 "args": args,
                 "newSandbox": new_sandbox,
                 "sandbox": sandbox,
                 "scriptTimeout": script_timeout,
                 "line": int(frame[1]),
                 "filename": os.path.basename(frame[0]),
                 "debug_script": debug_script}
-        rv = self._send_message("executeAsyncScript", body, key="value")
+
+        rv = self._send_message("WebDriver:ExecuteAsyncScript",
+                                body, key="value")
         return self._from_json(rv)
 
     def find_element(self, method, target, id=None):
         """Returns an :class:`~marionette_driver.marionette.HTMLElement`
         instance that matches the specified method and target in the current
         context.
 
         An :class:`~marionette_driver.marionette.HTMLElement` instance may be
@@ -1843,17 +1792,19 @@ class Marionette(object):
             "tag", target might equal "div".  If method = "id", target would
             be an element id.
         :param id: If specified, search for elements only inside the element
             with the specified id.
         """
         body = {"value": target, "using": method}
         if id:
             body["element"] = id
-        return self._send_message("findElement", body, key="value")
+
+        return self._send_message("WebDriver:FindElement",
+                                  body, key="value")
 
     def find_elements(self, method, target, id=None):
         """Returns a list of all
         :class:`~marionette_driver.marionette.HTMLElement` instances that match
         the specified method and target in the current context.
 
         An :class:`~marionette_driver.marionette.HTMLElement` instance may be
         used to call other methods on the element, such as
@@ -1871,20 +1822,23 @@ class Marionette(object):
             "tag", target might equal "div".  If method = "id", target would be
             an element id.
         :param id: If specified, search for elements only inside the element
             with the specified id.
         """
         body = {"value": target, "using": method}
         if id:
             body["element"] = id
-        return self._send_message("findElements", body)
+
+        return self._send_message("WebDriver:FindElements",
+                                  body)
 
     def get_active_element(self):
-        el_or_ref = self._send_message("getActiveElement", key="value")
+        el_or_ref = self._send_message("WebDriver:GetActiveElement",
+                                       key="value")
         return el_or_ref
 
     def add_cookie(self, cookie):
         """Adds a cookie to your current session.
 
         :param cookie: A dictionary object, with required keys - "name"
             and "value"; optional keys - "path", "domain", "secure",
             "expiry".
@@ -1893,42 +1847,43 @@ class Marionette(object):
 
         ::
 
             driver.add_cookie({"name": "foo", "value": "bar"})
             driver.add_cookie({"name": "foo", "value": "bar", "path": "/"})
             driver.add_cookie({"name": "foo", "value": "bar", "path": "/",
                                "secure": True})
         """
-        body = {"cookie": cookie}
-        self._send_message("addCookie", body)
+        self._send_message("WebDriver:AddCookie",
+                           {"cookie": cookie})
 
     def delete_all_cookies(self):
         """Delete all cookies in the scope of the current session.
 
         Usage example:
 
         ::
 
             driver.delete_all_cookies()
         """
-        self._send_message("deleteAllCookies")
+        self._send_message("WebDriver:DeleteAllCookies")
 
     def delete_cookie(self, name):
         """Delete a cookie by its name.
 
         :param name: Name of cookie to delete.
 
         Usage example:
 
         ::
 
             driver.delete_cookie("foo")
         """
-        self._send_message("deleteCookie", {"name": name})
+        self._send_message("WebDriver:DeleteCookie",
+                           {"name": name})
 
     def get_cookie(self, name):
         """Get a single cookie by name. Returns the cookie if found,
         None if not.
 
         :param name: Name of cookie to get.
         """
         cookies = self.get_cookies()
@@ -1940,17 +1895,17 @@ class Marionette(object):
     def get_cookies(self):
         """Get all the cookies for the current domain.
 
         This is the equivalent of calling `document.cookie` and
         parsing the result.
 
         :returns: A list of cookies for the current domain.
         """
-        return self._send_message("getCookies")
+        return self._send_message("WebDriver:GetCookies")
 
     def screenshot(self, element=None, highlights=None, format="base64",
                    full=True, scroll=True):
         """Takes a screenshot of a web element or the current frame.
 
         The screen capture is returned as a lossless PNG image encoded
         as a base 64 string by default. If the `element` argument is defined the
         capture area will be limited to the bounding box of that
@@ -1986,17 +1941,19 @@ class Marionette(object):
 
         body = {"id": element,
                 "highlights": lights,
                 "full": full,
                 "hash": False,
                 "scroll": scroll}
         if format == "hash":
             body["hash"] = True
-        data = self._send_message("takeScreenshot", body, key="value")
+
+        data = self._send_message("WebDriver:TakeScreenshot",
+                                  body, key="value")
 
         if format == "base64" or format == "hash":
             return data
         elif format == "binary":
             return base64.b64decode(data.encode("ascii"))
         else:
             raise ValueError("format parameter must be either 'base64'"
                              " or 'binary', not {0}".format(repr(format)))
@@ -2004,34 +1961,41 @@ class Marionette(object):
     @property
     def orientation(self):
         """Get the current browser orientation.
 
         Will return one of the valid primary orientation values
         portrait-primary, landscape-primary, portrait-secondary, or
         landscape-secondary.
         """
-        return self._send_message("getScreenOrientation", key="value")
+        try:
+            return self._send_message("Marionette:GetScreenOrientation",
+                                      key="value")
+        except errors.UnknownCommandException:
+            return self._send_message("getScreenOrientation", key="value")
 
     def set_orientation(self, orientation):
         """Set the current browser orientation.
 
         The supplied orientation should be given as one of the valid
         orientation values.  If the orientation is unknown, an error
         will be raised.
 
         Valid orientations are "portrait" and "landscape", which fall
         back to "portrait-primary" and "landscape-primary"
         respectively, and "portrait-secondary" as well as
         "landscape-secondary".
 
         :param orientation: The orientation to lock the screen in.
         """
         body = {"orientation": orientation}
-        self._send_message("setScreenOrientation", body)
+        try:
+            self._send_message("Marionette:SetScreenOrientation", body)
+        except errors.UnknownCommandException:
+            self._send_message("setScreenOrientation", body)
 
     @property
     def window_size(self):
         """Get the current browser window size.
 
         Will return the current browser window size in pixels. Refers to
         window outerWidth and outerHeight values, which include scroll bars,
         title bars, etc.
@@ -2079,18 +2043,18 @@ class Marionette(object):
         button in the OS window.
 
 
         Note that this command is not available on Fennec.  It may also
         not be available in certain window managers.
 
         :returns: Window rect.
         """
-        return self._send_message("maximizeWindow")
+        return self._send_message("WebDriver:MaximizeWindow")
 
     def fullscreen(self):
         """Synchronously sets the user agent window to full screen as
         if the user had done "View > Enter Full Screen",  or restores
         it if it is already in full screen.
 
         :returns: Window rect.
         """
-        return self._send_message("fullscreen")
+        return self._send_message("WebDriver:FullscreenWindow")
--- a/testing/marionette/client/marionette_driver/timeout.py
+++ b/testing/marionette/client/marionette_driver/timeout.py
@@ -25,20 +25,20 @@ class Timeouts(object):
 
     """
 
     def __init__(self, marionette):
         self._marionette = marionette
 
     def _set(self, name, sec):
         ms = sec * 1000
-        self._marionette._send_message("setTimeouts", {name: ms})
+        self._marionette._send_message("WebDriver:SetTimeouts", {name: ms})
 
     def _get(self, name):
-        ts = self._marionette._send_message("getTimeouts")
+        ts = self._marionette._send_message("WebDriver:GetTimeouts")
         if name not in ts:
             raise KeyError()
         ms = ts[name]
         return ms / 1000
 
     @property
     def script(self):
         """Get the session's script timeout.  This specifies the time
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -3490,45 +3490,65 @@ GeckoDriver.prototype.teardownReftest = 
   }
 
   this._reftest.abort();
   this._reftest = null;
 };
 
 GeckoDriver.prototype.commands = {
   // Marionette service
+  "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
+  "Marionette:GetContext": GeckoDriver.prototype.getContext,
+  "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
+  "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
+  "Marionette:Quit": GeckoDriver.prototype.quit,
   "Marionette:SetContext": GeckoDriver.prototype.setContext,
-  "setContext": GeckoDriver.prototype.setContext,  // deprecated, remove in Firefox 60
-  "Marionette:GetContext": GeckoDriver.prototype.getContext,
+  "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
+  // Bug 1354578 - Remove legacy action commands
+  "Marionette:ActionChain": GeckoDriver.prototype.actionChain,
+  "Marionette:MultiAction": GeckoDriver.prototype.multiAction,
+  "Marionette:SingleTap": GeckoDriver.prototype.singleTap,
+  // deprecated Marionette commands, remove in Firefox 63
+  "actionChain": GeckoDriver.prototype.actionChain,
+  "acceptConnections": GeckoDriver.prototype.acceptConnections,
+  "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow,
+  "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
   "getContext": GeckoDriver.prototype.getContext,
-  "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
-  "acceptConnections": GeckoDriver.prototype.acceptConnections,  // deprecated, remove in Firefox 60
-  "Marionette:Quit": GeckoDriver.prototype.quit,
-  "quit": GeckoDriver.prototype.quit,  // deprecated, remove in Firefox 60
-  "quitApplication": GeckoDriver.prototype.quit,  // deprecated, remove in Firefox 60
+  "getCurrentChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
+  "getScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
+  "getWindowType": GeckoDriver.prototype.getWindowType,
+  "multiAction": GeckoDriver.prototype.multiAction,
+  "quit": GeckoDriver.prototype.quit,
+  "quitApplication": GeckoDriver.prototype.quit,
+  "setContext": GeckoDriver.prototype.setContext,
+  "setScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
+  "singleTap": GeckoDriver.prototype.singleTap,
 
   // Addon service
   "Addon:Install": GeckoDriver.prototype.installAddon,
-  "addon:install": GeckoDriver.prototype.installAddon,  // deprecated, remove in Firefox 60
   "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
-  "addon:uninstall": GeckoDriver.prototype.uninstallAddon,  // deprecated, remove in Firefox 60
+  // deprecated Addon commands, remove in Firefox 63
+  "addon:install": GeckoDriver.prototype.installAddon,
+  "addon:uninstall": GeckoDriver.prototype.uninstallAddon,
 
   // L10n service
   "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
-  "localization:l10n:localizeEntity": GeckoDriver.prototype.localizeEntity,  // deprecated, remove in Firefox 60
   "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
-  "localization:l10n:localizeProperty": GeckoDriver.prototype.localizeProperty,  // deprecated, remove in Firefox 60
+  // deprecated L10n commands, remove in Firefox 63
+  "localization:l10n:localizeEntity": GeckoDriver.prototype.localizeEntity,
+  "localization:l10n:localizeProperty": GeckoDriver.prototype.localizeProperty,
 
   // Reftest service
   "reftest:setup": GeckoDriver.prototype.setupReftest,
   "reftest:run": GeckoDriver.prototype.runReftest,
   "reftest:teardown": GeckoDriver.prototype.teardownReftest,
 
   // WebDriver service
-  "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
+  "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
+  "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,  // deprecated, remove in Firefox 63
   "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
   "WebDriver:Back": GeckoDriver.prototype.goBack,
   "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
   "WebDriver:CloseWindow": GeckoDriver.prototype.close,
   "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
   "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
   "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
   "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
@@ -3552,105 +3572,92 @@ GeckoDriver.prototype.commands = {
   "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
   "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
   "WebDriver:GetElementCSSValue": GeckoDriver.prototype.getElementValueOfCssProperty,
   "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
   "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
   "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
   "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
   "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
-  "WebDriver:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
   "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
   "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
   "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
   "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
   "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
-  "WebDriver:GetWindowType": GeckoDriver.prototype.getWindowType,
   "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
   "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
   "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
   "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
   "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
   "WebDriver:Navigate": GeckoDriver.prototype.get,
   "WebDriver:NewSession": GeckoDriver.prototype.newSession,
   "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
   "WebDriver:Refresh":  GeckoDriver.prototype.refresh,
   "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
   "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
-  "WebDriver:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
   "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
   "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
   "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
   "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
   "WebDriver:SwitchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
   "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
   "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
 
-  // deprecated WebDriver commands, remove in Firefox 60
+  // deprecated WebDriver commands, remove in Firefox 63
   "acceptDialog": GeckoDriver.prototype.acceptDialog,
-  "actionChain": GeckoDriver.prototype.actionChain,
   "addCookie": GeckoDriver.prototype.addCookie,
   "clearElement": GeckoDriver.prototype.clearElement,
   "clickElement": GeckoDriver.prototype.clickElement,
-  "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow,
   "close": GeckoDriver.prototype.close,
   "deleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
   "deleteCookie": GeckoDriver.prototype.deleteCookie,
   "deleteSession": GeckoDriver.prototype.deleteSession,
   "dismissDialog": GeckoDriver.prototype.dismissDialog,
   "executeAsyncScript": GeckoDriver.prototype.executeAsyncScript,
   "executeScript": GeckoDriver.prototype.executeScript,
   "findElement": GeckoDriver.prototype.findElement,
   "findElements": GeckoDriver.prototype.findElements,
   "fullscreen": GeckoDriver.prototype.fullscreenWindow,
   "getActiveElement": GeckoDriver.prototype.getActiveElement,
   "getActiveFrame": GeckoDriver.prototype.getActiveFrame,
-  "getChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
-  "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
   "getCookies": GeckoDriver.prototype.getCookies,
-  "getCurrentChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
   "getCurrentUrl": GeckoDriver.prototype.getCurrentUrl,
   "getElementAttribute": GeckoDriver.prototype.getElementAttribute,
   "getElementProperty": GeckoDriver.prototype.getElementProperty,
   "getElementRect": GeckoDriver.prototype.getElementRect,
   "getElementTagName": GeckoDriver.prototype.getElementTagName,
   "getElementText": GeckoDriver.prototype.getElementText,
   "getElementValueOfCssProperty": GeckoDriver.prototype.getElementValueOfCssProperty,
   "get": GeckoDriver.prototype.get,
   "getPageSource": GeckoDriver.prototype.getPageSource,
-  "getScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
   "getSessionCapabilities": GeckoDriver.prototype.getSessionCapabilities,
   "getTextFromDialog": GeckoDriver.prototype.getTextFromDialog,
   "getTimeouts": GeckoDriver.prototype.getTimeouts,
   "getTitle": GeckoDriver.prototype.getTitle,
   "getWindowHandle": GeckoDriver.prototype.getWindowHandle,
   "getWindowHandles": GeckoDriver.prototype.getWindowHandles,
   "getWindowPosition": GeckoDriver.prototype.getWindowRect, // redirect for compatibility
   "getWindowRect": GeckoDriver.prototype.getWindowRect,
   "getWindowSize": GeckoDriver.prototype.getWindowRect, // redirect for compatibility
-  "getWindowType": GeckoDriver.prototype.getWindowType,
   "goBack": GeckoDriver.prototype.goBack,
   "goForward": GeckoDriver.prototype.goForward,
   "isElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
   "isElementEnabled": GeckoDriver.prototype.isElementEnabled,
   "isElementSelected": GeckoDriver.prototype.isElementSelected,
   "maximizeWindow": GeckoDriver.prototype.maximizeWindow,
-  "multiAction": GeckoDriver.prototype.multiAction,
   "newSession": GeckoDriver.prototype.newSession,
   "performActions": GeckoDriver.prototype.performActions,
   "refresh":  GeckoDriver.prototype.refresh,
   "releaseActions": GeckoDriver.prototype.releaseActions,
   "sendKeysToDialog": GeckoDriver.prototype.sendKeysToDialog,
   "sendKeysToElement": GeckoDriver.prototype.sendKeysToElement,
-  "setScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
   "setTimeouts": GeckoDriver.prototype.setTimeouts,
   "setWindowPosition": GeckoDriver.prototype.setWindowRect, // redirect for compatibility
   "setWindowRect": GeckoDriver.prototype.setWindowRect,
   "setWindowSize": GeckoDriver.prototype.setWindowRect, // redirect for compatibility
-  "singleTap": GeckoDriver.prototype.singleTap,
   "switchToFrame": GeckoDriver.prototype.switchToFrame,
   "switchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
   "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
   "switchToWindow": GeckoDriver.prototype.switchToWindow,
   "takeScreenshot": GeckoDriver.prototype.takeScreenshot,
 };
 
 function getOuterWindowId(win) {
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_mouse_action.py
@@ -82,18 +82,18 @@ class BaseMouseAction(MarionetteTestCase
         return self.marionette.execute_script("""
           if (window.click_x && window.click_y) {
             return {x: window.click_x, y: window.click_y};
           }
         """, sandbox=None)
 
     def get_element_center_point(self, elem):
         return {
-            "x": elem.location["x"] + elem.size["width"] / 2,
-            "y": elem.location["y"] + elem.size["height"] / 2
+            "x": elem.rect["x"] + elem.rect["width"] / 2,
+            "y": elem.rect["y"] + elem.rect["height"] / 2
         }
 
 
 class TestNonSpecCompliantPointerOrigin(BaseMouseAction):
 
     def setUp(self):
         super(TestNonSpecCompliantPointerOrigin, self).setUp()
 
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_timeouts.py
@@ -93,20 +93,8 @@ class TestTimeouts(MarionetteTestCase):
     def test_no_timeout_settimeout(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         self.marionette.timeout.script = 1
         self.assertTrue(self.marionette.execute_async_script("""
              var callback = arguments[arguments.length - 1];
              setTimeout(function() { callback(true); }, 500);
              """))
-
-    def test_deprecated_set_search_timeout(self):
-        self.marionette.set_search_timeout(1000)
-        self.assertEqual(1, self.marionette.timeout.implicit)
-
-    def test_deprecated_set_script_timeout(self):
-        self.marionette.set_script_timeout(2000)
-        self.assertEqual(2, self.marionette.timeout.script)
-
-    def test_deprecated_set_page_load_timeout(self):
-        self.marionette.set_page_load_timeout(3000)
-        self.assertEqual(3, self.marionette.timeout.page_load)
--- a/testing/mozbase/mozprofile/mozprofile/profile.py
+++ b/testing/mozbase/mozprofile/mozprofile/profile.py
@@ -148,18 +148,18 @@ class Profile(object):
 
     def cleanup(self):
         """Cleanup operations for the profile."""
 
         if self.restore:
             # If copies of those class instances exist ensure we correctly
             # reset them all (see bug 934484)
             self.clean_preferences()
-            if getattr(self, 'addon_manager', None) is not None:
-                self.addon_manager.clean()
+            if getattr(self, 'addons', None) is not None:
+                self.addons.clean()
             if getattr(self, 'permissions', None) is not None:
                 self.permissions.clean_db()
 
             # If it's a temporary profile we have to remove it
             if self.create_new:
                 mozfile.remove(self.profile)
 
     def reset(self):
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -313762,16 +313762,22 @@
     ]
    ],
    "css/css-scoping/host-dom-001.html": [
     [
      "/css/css-scoping/host-dom-001.html",
      {}
     ]
    ],
+   "css/css-scoping/host-functional-descendant-invalidation.html": [
+    [
+     "/css/css-scoping/host-functional-descendant-invalidation.html",
+     {}
+    ]
+   ],
    "css/css-scoping/shadow-cascade-order-001.html": [
     [
      "/css/css-scoping/shadow-cascade-order-001.html",
      {}
     ]
    ],
    "css/css-scoping/slotted-invalidation.html": [
     [
@@ -506746,21 +506752,25 @@
    "703ba0d07ece44f4cc017b5351dea3057337f234",
    "reftest"
   ],
   "css/css-scoping/host-descendant-002.html": [
    "c96ebd0da1c7b2ce00e56bbef54bdd382789e2f2",
    "reftest"
   ],
   "css/css-scoping/host-descendant-invalidation.html": [
-   "ec27e3cbe587470ecb945357c74954baf139d797",
+   "33ad555cd243bb5cd486b6ffb776f657d1185228",
    "testharness"
   ],
   "css/css-scoping/host-dom-001.html": [
-   "f77b672837e1c9728e53d74b533d79530fbd1249",
+   "33f34152fff538f6080b6fa36de337dca8a2b694",
+   "testharness"
+  ],
+  "css/css-scoping/host-functional-descendant-invalidation.html": [
+   "891d852131c37fd2148d46f5b6dfe32e1fc45bf3",
    "testharness"
   ],
   "css/css-scoping/host-multiple-001.html": [
    "eb45ceb8c80d2cfbeb5bd317ab906f0881a13435",
    "reftest"
   ],
   "css/css-scoping/host-nested-001.html": [
    "a0b74d2e6bf24e9142904a925f95e969d206db20",
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-scoping/css-scoping-shadow-host-functional-rule.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[css-scoping-shadow-host-functional-rule.html]
-  expected: FAIL
--- a/testing/web-platform/meta/mediacapture-streams/GUM-deny.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/GUM-deny.https.html.ini
@@ -1,4 +1,5 @@
 [GUM-deny.https.html]
+  prefs: [media.getusermedia.camera.deny:true]
   [Tests that the error callback is triggered when permission is denied]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/GUM-optional-constraint.https.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[GUM-optional-constraint.https.html]
-  [Tests that setting an optional constraint in getUserMedia is handled as optional]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/GUM-trivial-constraint.https.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[GUM-trivial-constraint.https.html]
-  [Tests that setting a trivial mandatory constraint in getUserMedia works]
-    expected: FAIL
-
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html.ini
@@ -1,29 +1,7 @@
 [MediaStream-MediaElement-preload-none.https.html]
-  expected:
-    if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if not debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-    if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
   [Test that preload 'none' is ignored for MediaStream object URL used as src]
-    expected:
-      if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if not debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-      FAIL
+    expected: FAIL
 
   [Test that preload 'none' is ignored for MediaStream used as srcObject]
     expected: FAIL
 
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html.ini
@@ -1,5 +1,4 @@
 [MediaStream-MediaElement-srcObject.https.html]
-  expected: TIMEOUT
   [Tests that a MediaStream can be assigned to a video element with srcObject]
-    expected: TIMEOUT
+    expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-add-audio-track.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStream-add-audio-track.https.html]
-  expected: TIMEOUT
-  [Tests that adding a track to a MediaStream works as expected]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-audio-only.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStream-audio-only.https.html]
-  expected: TIMEOUT
-  [Tests that a MediaStream with exactly one audio track is returned]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-finished-add.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStream-finished-add.https.html]
-  expected: TIMEOUT
-  [Tests that adding a track to an inactive MediaStream is allowed]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-gettrackid.https.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[MediaStream-gettrackid.https.html]
-  [Tests that MediaStream.getTrackById works as expected]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-idl.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStream-idl.https.html]
-  expected: TIMEOUT
-  [Tests that a MediaStream constructor follows the algorithm set in the spec]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-removetrack.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStream-removetrack.https.html]
-  expected: TIMEOUT
-  [Tests that a removal from a MediaStream works as expected]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStream-video-only.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStream-video-only.https.html]
-  expected: TIMEOUT
-  [Tests that a MediaStream with at least one video track is returned]
-    expected: TIMEOUT
-
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html]
-  expected: TIMEOUT
-  [Tests that a disabled audio track in a MediaStream is rendered as silence]
-    expected: TIMEOUT
-
--- a/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html.ini
@@ -1,8 +1,7 @@
 [MediaStreamTrack-MediaElement-disabled-video-is-black.https.html]
-  expected: TIMEOUT
   [Tests that a disabled video track in a MediaStream is rendered as blackness]
-    expected: TIMEOUT
+    expected: FAIL
 
   [A disabled video track is rendered as blackness]
     expected: FAIL
 
--- a/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-getSettings.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-getSettings.https.html.ini
@@ -1,7 +1,4 @@
 [MediaStreamTrack-getSettings.https.html]
-  [A device can be opened twice and have the same device ID]
-    expected: FAIL
-
   [A device can be opened twice with different resolutions]
     expected: FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-id.https.html.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[MediaStreamTrack-id.https.html]
-  [Tests that distinct mediastream tracks have distinct ids ]
-    expected: FAIL
-
--- a/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-idl.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-idl.https.html.ini
@@ -1,27 +1,19 @@
 [MediaStreamTrack-idl.https.html]
-  expected:
-    if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if not debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-    if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-  [Test driver]
-    expected:
-      if not debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): FAIL
-      if debug and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): FAIL
-      if debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if not debug and e10s and (os == "win") and (version == "10.0.15063") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-      if not debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-      if debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
-      if debug and not e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): TIMEOUT
+  [MediaStreamTrack interface: attribute onoverconstrained]
+    expected: FAIL
+
+  [MediaStreamTrack interface: operation getCapabilities()]
+    expected: FAIL
+
+  [MediaStreamTrack must be primary interface of track]
+    expected: FAIL
 
+  [Stringification of track]
+    expected: FAIL
+
+  [MediaStreamTrack interface: track must inherit property "onoverconstrained" with the proper type]
+    expected: FAIL
+
+  [MediaStreamTrack interface: track must inherit property "getCapabilities()" with the proper type]
+    expected: FAIL
+
--- a/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-init.https.html.ini
+++ b/testing/web-platform/meta/mediacapture-streams/MediaStreamTrack-init.https.html.ini
@@ -1,7 +1,4 @@
 [MediaStreamTrack-init.https.html]
   [Tests that the video MediaStreamTrack objects are properly initialized]
     expected: TIMEOUT
 
-  [getUserMedia({video:true}) creates a stream with a properly initialized video track]
-    expected: FAIL
-
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/mediacapture-streams/__dir__.ini
@@ -0,0 +1,2 @@
+prefs: [media.navigator.permission.disabled:true,
+        media.navigator.streams.fake:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-scoping/host-functional-descendant-invalidation.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<title>CSS Test: element style is correctly updated for rule with :host(..)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-scoping/#host-selector">
+<div id="host"><div id="slotted"></div></div>
+<script>
+test(function() {
+  let root = host.attachShadow({ mode: "open" });
+  root.innerHTML = `
+    <style>
+      :host ::slotted(div) { width: 100px; height: 100px; background: red; }
+      :host(.foo) ::slotted(div) { background: green; }
+    </style>
+    <slot></slot>
+  `;
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(255, 0, 0)");
+  host.classList.add('foo');
+  assert_equals(getComputedStyle(slotted).backgroundColor, "rgb(0, 128, 0)");
+});
+</script>
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -156,8 +156,24 @@ telemetry.test.second:
     objects: ["object1", "object2", "object3"]
     bug_numbers: [1286606]
     notification_emails: ["telemetry-client-dev@mozilla.com"]
     record_in_processes: ["main"]
     description: This is a test entry for Telemetry.
     expiry_version: never
     extra_keys:
       key1: This is just a test description.
+
+devtools.main:
+  open:
+    objects: ["tools"]
+    bug_numbers: [1416024]
+    notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
+    record_in_processes: ["main"]
+    description: User opens devtools toolbox.
+    release_channel_collection: opt-out
+    expiry_version: never
+    extra_keys:
+      entrypoint: How was the toolbox opened? CommandLine, ContextMenu, DeveloperToolbar, HamburgerMenu, KeyShortcut, SessionRestore or SystemMenu
+      first_panel: The name of the first panel opened.
+      host: "Toolbox host (positioning): bottom, side, window or other."
+      splitconsole: Indicates whether the split console was open.
+      width: Toolbox width rounded up to the nearest 50px.