merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 17 Feb 2016 12:07:39 +0100
changeset 284516 c007ec81b75ae1c51da2c2d2bab21180290fab6b
parent 284470 15621f98b53b1994c7ae2e2703a6e50203c5304c (current diff)
parent 284515 db7c5f35321fae1b076a088553ec92a9b89d883b (diff)
child 284517 7ced13d644be8b05f0de4beeaaa3458910eb2ad6
child 284524 937e039f8b8c14ed1b8f6a4a045ce847dbf82f0d
child 284583 60f020c84b23b526517f01b1c24f92a236d2bb11
push id30004
push usercbook@mozilla.com
push dateWed, 17 Feb 2016 11:07:54 +0000
treeherdermozilla-central@c007ec81b75a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.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 fx-team to mozilla-central a=merge
browser/base/content/test/general/browser_contextSearchTabPosition.js
configure.in
devtools/client/shared/DeveloperToolbar.jsm
mobile/android/app/mobile.js
testing/eslint-plugin-mozilla/docs/components-imports.rst
testing/eslint-plugin-mozilla/docs/import-globals-from.rst
testing/eslint-plugin-mozilla/docs/this-top-level-scope.rst
testing/eslint-plugin-mozilla/lib/rules/components-imports.js
testing/eslint-plugin-mozilla/lib/rules/import-globals-from.js
testing/eslint-plugin-mozilla/lib/rules/this-top-level-scope.js
testing/mozharness/mozharness/mozilla/building/buildbase.py
toolkit/components/telemetry/Histograms.json
--- a/.eslintignore
+++ b/.eslintignore
@@ -182,18 +182,18 @@ toolkit/components/workerloader/tests/mo
 # Tests old non-star function generators
 toolkit/modules/tests/xpcshell/test_task.js
 
 # Not yet updated
 toolkit/components/osfile/**
 toolkit/components/passwordmgr/**
 
 # Uses preprocessing
-toolkit/content/contentAreaUtils.js
 toolkit/content/widgets/videocontrols.xml
+toolkit/content/widgets/wizard.xml
 toolkit/components/jsdownloads/src/DownloadIntegration.jsm
 toolkit/components/search/nsSearchService.js
 toolkit/components/url-classifier/**
 toolkit/components/urlformatter/nsURLFormatter.js
 toolkit/identity/FirefoxAccounts.jsm
 toolkit/modules/AppConstants.jsm
 toolkit/mozapps/downloads/nsHelperAppDlg.js
 toolkit/mozapps/extensions/internal/AddonConstants.jsm
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,14 +1,12 @@
 {
   // When adding items to this file please check for effects on sub-directories.
   "plugins": [
     "mozilla"
   ],
   "rules": {
-    "mozilla/components-imports": 1,
-    "mozilla/import-globals-from": 1,
-    "mozilla/this-top-level-scope": 1,
+    "mozilla/import-globals": 1,
   },
   "env": {
     "es6": true
   },
 }
--- a/addon-sdk/source/lib/sdk/places/host/host-query.js
+++ b/addon-sdk/source/lib/sdk/places/host/host-query.js
@@ -42,28 +42,42 @@ const MANUAL_QUERY_PROPERTIES = [
 const PLACES_PROPERTIES = [
   'uri', 'title', 'accessCount', 'time'
 ];
 
 function execute (queries, options) {
   return new Promise(resolve => {
     let root = historyService
         .executeQueries(queries, queries.length, options).root;
-    resolve(collect([], root));
+    // Let's extract an eventual uri wildcard, if both domain and uri are set.
+    // See utils.js::urlQueryParser() for more details.
+    // In case of multiple queries, we only retain the first found wildcard.
+    let uriWildcard = queries.reduce((prev, query) => {
+      if (query.uri && query.domain) {
+        if (!prev)
+          prev = query.uri.spec;
+        query.uri = null;
+      }
+      return prev;
+    }, "");
+    resolve(collect([], root, uriWildcard));
   });
 }
 
-function collect (acc, node) {
+function collect (acc, node, uriWildcard) {
   node.containerOpen = true;
   for (let i = 0; i < node.childCount; i++) {
     let child = node.getChild(i);
-    acc.push(child);
+
+    if (!uriWildcard || child.uri.startsWith(uriWildcard)) {
+      acc.push(child);
+    }
     if (child.type === child.RESULT_TYPE_FOLDER) {
       let container = child.QueryInterface(Ci.nsINavHistoryContainerResultNode);
-      collect(acc, container);
+      collect(acc, container, uriWildcard);
     }
   }
   node.containerOpen = false;
   return acc;
 }
 
 function query (queries, options) {
   return new Promise((resolve, reject) => {
--- a/addon-sdk/source/lib/sdk/places/utils.js
+++ b/addon-sdk/source/lib/sdk/places/utils.js
@@ -7,26 +7,28 @@
 module.metadata = {
   "stability": "experimental",
   "engines": {
     "Firefox": "*",
     "SeaMonkey": "*"
   }
 };
 
-const { Cc, Ci } = require('chrome');
+const { Cc, Ci, Cu } = require('chrome');
 const { Class } = require('../core/heritage');
 const { method } = require('../lang/functional');
 const { defer, promised, all } = require('../core/promise');
 const { send } = require('../addon/events');
 const { EventTarget } = require('../event/target');
 const { merge } = require('../util/object');
 const bmsrv = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                 getService(Ci.nsINavBookmarksService);
 
+Cu.importGlobalProperties(["URL"]);
+
 /*
  * TreeNodes are used to construct dependency trees
  * for BookmarkItems
  */
 var TreeNode = Class({
   initialize: function (value) {
     this.value = value;
     this.children = [];
@@ -123,29 +125,42 @@ exports.isRootGroup = isRootGroup;
 /*
  * Merges appropriate options into query based off of url
  * 4 scenarios:
  *
  * 'moz.com' // domain: moz.com, domainIsHost: true
  *    --> 'http://moz.com', 'http://moz.com/thunderbird'
  * '*.moz.com' // domain: moz.com, domainIsHost: false
  *    --> 'http://moz.com', 'http://moz.com/index', 'http://ff.moz.com/test'
- * 'http://moz.com' // url: http://moz.com/, urlIsPrefix: false
+ * 'http://moz.com' // uri: http://moz.com/
  *    --> 'http://moz.com/'
- * 'http://moz.com/*' // url: http://moz.com/, urlIsPrefix: true
+ * 'http://moz.com/*' // uri: http://moz.com/, domain: moz.com, domainIsHost: true
  *    --> 'http://moz.com/', 'http://moz.com/thunderbird'
  */
 
 function urlQueryParser (query, url) {
   if (!url) return;
   if (/^https?:\/\//.test(url)) {
     query.uri = url.charAt(url.length - 1) === '/' ? url : url + '/';
     if (/\*$/.test(url)) {
-      query.uri = url.replace(/\*$/, '');
-      query.uriIsPrefix = true;
+      // Wildcard searches on URIs are not supported, so try to extract a
+      // domain and filter the data later.
+      url = url.replace(/\*$/, '');
+      try {
+        query.domain = new URL(url).hostname;
+        query.domainIsHost = true;
+        // Unfortunately here we cannot use an expando to store the wildcard,
+        // cause the query is a wrapped native XPCOM object, so we reuse uri.
+        // We clearly don't want to query for both uri and domain, thus we'll
+        // have to handle this in host-query.js::execute()
+        query.uri = url;
+      } catch (ex) {
+        // Cannot extract an host cause it's not a valid uri, the query will
+        // just return nothing.
+      }
     }
   } else {
     if (/^\*/.test(url)) {
       query.domain = url.replace(/^\*\./, '');
       query.domainIsHost = false;
     } else {
       query.domain = url;
       query.domainIsHost = true;
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -448,18 +448,18 @@
       </menu>
       <menuitem id="menu_bookmarkAllTabs"
                 label="&addCurPagesCmd.label;"
                 class="show-only-for-keyboard"
                 command="Browser:BookmarkAllTabs"
                 key="bookmarkAllTabsKb"/>
       <menuseparator/>
       <menuitem label="&recentBookmarks.label;"
+                id="menu_recentBookmarks"
                 disabled="true"/>
-      <vbox id="menu_recentBookmarks"/>
       <menuseparator id="bookmarksToolbarSeparator"/>
       <menu id="bookmarksToolbarFolderMenu"
             class="menu-iconic bookmark-item"
             label="&personalbarCmd.label;"
             container="true">
         <menupopup id="bookmarksToolbarFolderPopup"
 #ifndef XP_MACOSX
                    placespopup="true"
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1336,28 +1336,29 @@ var BookmarkingUI = {
       extraClasses: {
         entry: "subviewbutton",
         footer: "panel-subview-footer"
       },
       insertionPoint: ".panel-subview-footer"
     });
   },
 
-  _updateRecentBookmarks: function(container, extraCSSClass = "") {
+  _updateRecentBookmarks: function(aHeaderItem, extraCSSClass = "") {
     const kMaxResults = 5;
 
     let options = PlacesUtils.history.getNewQueryOptions();
     options.excludeQueries = true;
     options.queryType = options.QUERY_TYPE_BOOKMARKS;
     options.sortingMode = options.SORT_BY_DATEADDED_DESCENDING;
     options.maxResults = kMaxResults;
     let query = PlacesUtils.history.getNewQuery();
 
-    while (container.firstChild) {
-      container.firstChild.remove();
+    while (aHeaderItem.nextSibling &&
+           aHeaderItem.nextSibling.localName == "menuitem") {
+      aHeaderItem.nextSibling.remove();
     }
 
     PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                        .asyncExecuteLegacyQueries([query], 1, options, {
       handleResult: function (aResultSet) {
         let onItemClick = function (aEvent) {
           let item = aEvent.target;
           openUILink(item.getAttribute("targetURI"), aEvent);
@@ -1380,17 +1381,17 @@ var BookmarkingUI = {
                                      extraCSSClass);
           item.addEventListener("click", onItemClick);
           if (icon) {
             let iconURL = "moz-anno:favicon:" + icon;
             item.setAttribute("image", iconURL);
           }
           fragment.appendChild(item);
         }
-        container.appendChild(fragment);
+        aHeaderItem.parentNode.insertBefore(fragment, aHeaderItem.nextSibling);
       },
       handleError: function (aError) {
         Cu.reportError("Error while attempting to show recent bookmarks: " + aError);
       },
       handleCompletion: function (aReason) {
       },
     });
   },
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -78,16 +78,17 @@ var gMultiProcessBrowser =
 var gAppInfo = Cc["@mozilla.org/xre/app-info;1"]
                   .getService(Ci.nsIXULAppInfo)
                   .QueryInterface(Ci.nsIXULRuntime);
 
 if (AppConstants.platform != "macosx") {
   var gEditUIVisible = true;
 }
 
+/*globals gBrowser, gNavToolbox, gURLBar, gNavigatorBundle*/
 [
   ["gBrowser",            "content"],
   ["gNavToolbox",         "navigator-toolbox"],
   ["gURLBar",             "urlbar"],
   ["gNavigatorBundle",    "bundle_browser"]
 ].forEach(function (elementGlobal) {
   var [name, id] = elementGlobal;
   window.__defineGetter__(name, function () {
@@ -152,19 +153,19 @@ XPCOMUtils.defineLazyGetter(this, "Popup
                                       document.getElementById("notification-popup-box"));
   } catch (ex) {
     Cu.reportError(ex);
     return null;
   }
 });
 
 XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
-  let tmp = {};
-  Cu.import("resource://devtools/client/shared/DeveloperToolbar.jsm", tmp);
-  return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
+  let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+  let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar");
+  return new DeveloperToolbar(window, document.getElementById("developer-toolbar"));
 });
 
 XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
   let tmp = {};
   Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp);
   return tmp.BrowserToolboxProcess;
 });
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -808,19 +808,19 @@
             <!-- NB: temporary solution for bug 985024, this should go away soon. -->
             <menuitem id="BMB_bookmarksShowAllTop"
                       class="menuitem-iconic subviewbutton"
                       label="&showAllBookmarks2.label;"
                       command="Browser:ShowAllBookmarks"
                       key="manBookmarkKb"/>
             <menuseparator/>
             <menuitem label="&recentBookmarks.label;"
+                      id="BMB_recentBookmarks"
                       disabled="true"
                       class="subviewbutton"/>
-            <vbox id="BMB_recentBookmarks"/>
             <menuseparator/>
             <menu id="BMB_bookmarksToolbar"
                   class="menu-iconic bookmark-item subviewbutton"
                   label="&personalbarCmd.label;"
                   container="true">
               <menupopup id="BMB_bookmarksToolbarPopup"
                          placespopup="true"
                          context="placesContext"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1858,18 +1858,17 @@
                           Services.prefs.getBoolPref("browser.tabs.animate");
             if (!animate) {
               t.setAttribute("fadein", "true");
               setTimeout(function (tabContainer) {
                 tabContainer._handleNewTab(t);
               }, 0, this.tabContainer);
             }
 
-            // invalidate caches
-            this._browsers = null;
+            // invalidate cache
             this._visibleTabs = null;
 
             this.tabContainer.appendChild(t);
 
             // If this new tab is owned by another, assert that relationship
             if (aOwner)
               t.owner = aOwner;
 
@@ -2374,20 +2373,16 @@
             // destroyed until the document goes away.  So we force a
             // cleanup ourselves.
             // This has to happen before we remove the child so that the
             // XBL implementation of nsIObserver still works.
             browser.destroy();
 
             var wasPinned = aTab.pinned;
 
-            // Invalidate browsers cache, as the tab is removed from the
-            // tab container.
-            this._browsers = null;
-
             // Remove the tab ...
             this.tabContainer.removeChild(aTab);
 
             // ... and fix up the _tPos properties immediately.
             for (let i = aTab._tPos; i < this.tabs.length; i++)
               this.tabs[i]._tPos = i;
 
             if (!this._windowIsClosing) {
@@ -2855,25 +2850,42 @@
           ]]>
         </setter>
       </property>
 
       <property name="selectedBrowser"
                 onget="return this.mCurrentBrowser;"
                 readonly="true"/>
 
-      <property name="browsers" readonly="true">
-       <getter>
-          <![CDATA[
-            return this._browsers ||
-                   (this._browsers = Array.map(this.tabs, tab => tab.linkedBrowser));
-          ]]>
-        </getter>
-      </property>
-      <field name="_browsers">null</field>
+      <field name="browsers" readonly="true">
+        <![CDATA[
+          // This defines a proxy which allows us to access browsers by
+          // index without actually creating a full array of browsers.
+          new Proxy([], {
+            has: (target, name) => {
+              if (typeof name == "string" && Number.isInteger(parseInt(name))) {
+                return (name in this.tabs);
+              }
+              return false;
+            },
+            get: (target, name) => {
+              if (name == "length") {
+                return this.tabs.length;
+              }
+              if (typeof name == "string" && Number.isInteger(parseInt(name))) {
+                if (!(name in this.tabs)) {
+                  return undefined;
+                }
+                return this.tabs[name].linkedBrowser;
+              }
+              return target[name];
+            }
+          });
+        ]]>
+      </field>
 
       <!-- Moves a tab to a new browser window, unless it's already the only tab
            in the current window, in which case this will do nothing. -->
       <method name="replaceTabWithWindow">
         <parameter name="aTab"/>
         <parameter name="aOptions"/>
         <body>
           <![CDATA[
@@ -2924,18 +2936,17 @@
           this._lastRelatedTab = null;
 
           let wasFocused = (document.activeElement == this.mCurrentTab);
 
           aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
           this.mCurrentTab._logicallySelected = false;
           this.mCurrentTab._visuallySelected = false;
 
-          // invalidate caches
-          this._browsers = null;
+          // invalidate cache
           this._visibleTabs = null;
 
           // use .item() instead of [] because dragging to the end of the strip goes out of
           // bounds: .item() returns null (so it acts like appendChild), but [] throws
           this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
 
           for (let i = 0; i < this.tabs.length; i++) {
             this.tabs[i]._tPos = i;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -131,17 +131,16 @@ skip-if = os == "linux" # Bug 924307
 skip-if = e10s # Bug 1093153 - no about:home support yet
 [browser_aboutHome_wrapsCorrectly.js]
 [browser_action_keyword.js]
 [browser_action_keyword_override.js]
 [browser_action_searchengine.js]
 [browser_action_searchengine_alias.js]
 [browser_addKeywordSearch.js]
 [browser_search_favicon.js]
-skip-if = e10s # Bug 1212647
 [browser_alltabslistener.js]
 [browser_audioTabIcon.js]
 [browser_autocomplete_a11y_label.js]
 skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir (works on its own)
 [browser_autocomplete_cursor.js]
 [browser_autocomplete_edit_completed.js]
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
@@ -285,18 +284,16 @@ tags = mcb
 skip-if = os == 'win' || e10s # Bug 1159268 - Need a content-process safe version of synthesizeWheel
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_bug1070778.js]
 [browser_accesskeys.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
 [browser_clipboard.js]
 [browser_contentAreaClick.js]
-[browser_contextSearchTabPosition.js]
-skip-if = os == "mac" || e10s # bug 967013; e10s: bug 1094761 - test hits the network in e10s, causing next test to crash
 [browser_ctrlTab.js]
 [browser_datachoices_notification.js]
 skip-if = !datareporting
 [browser_devedition.js]
 [browser_devices_get_user_media.js]
 skip-if = buildapp == 'mulet' || (os == "linux" && debug) # linux: bug 976544
 [browser_devices_get_user_media_about_urls.js]
 skip-if = e10s # Bug 1071623
--- a/browser/components/extensions/ext-bookmarks.js
+++ b/browser/components/extensions/ext-bookmarks.js
@@ -1,13 +1,13 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 var Bookmarks = PlacesUtils.bookmarks;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
@@ -106,17 +106,19 @@ extensions.registerSchemaAPI("bookmarks"
       getTree: function() {
         return getTree(Bookmarks.rootGuid, false);
       },
 
       getSubTree: function(id) {
         return getTree(id, false);
       },
 
-      // search
+      search: function(query) {
+        return Bookmarks.search(query).then(result => result.map(convert));
+      },
 
       create: function(bookmark) {
         let info = {
           title: bookmark.title || "",
         };
 
         // If url is NULL or missing, it will be a folder.
         if (bookmark.url !== null) {
@@ -134,17 +136,17 @@ extensions.registerSchemaAPI("bookmarks"
           info.parentGuid = bookmark.parentId;
         } else {
           info.parentGuid = Bookmarks.unfiledGuid;
         }
 
         try {
           return Bookmarks.insert(info).then(convert);
         } catch (e) {
-          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
+          return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
         }
       },
 
       move: function(id, destination) {
         let info = {
           guid: id,
         };
 
@@ -153,17 +155,17 @@ extensions.registerSchemaAPI("bookmarks"
         }
         if (destination.index !== null) {
           info.index = destination.index;
         }
 
         try {
           return Bookmarks.update(info).then(convert);
         } catch (e) {
-          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
+          return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
         }
       },
 
       update: function(id, changes) {
         let info = {
           guid: id,
         };
 
@@ -172,28 +174,28 @@ extensions.registerSchemaAPI("bookmarks"
         }
         if (changes.url !== null) {
           info.url = changes.url;
         }
 
         try {
           return Bookmarks.update(info).then(convert);
         } catch (e) {
-          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
+          return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
         }
       },
 
       remove: function(id) {
         let info = {
           guid: id,
         };
 
         // The API doesn't give you the old bookmark at the moment
         try {
           return Bookmarks.remove(info).then(result => {});
         } catch (e) {
-          return Promise.reject({ message: `Invalid bookmark: ${JSON.stringify(info)}` });
+          return Promise.reject({message: `Invalid bookmark: ${JSON.stringify(info)}`});
         }
       },
     },
   };
 });
 
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -39,17 +39,17 @@ function BrowserAction(options, extensio
     popup = extension.baseURI.resolve(popup);
   }
 
   this.defaults = {
     enabled: true,
     title: title || extension.name,
     badgeText: "",
     badgeBackgroundColor: null,
-    icon: IconDetails.normalize({ path: options.default_icon }, extension,
+    icon: IconDetails.normalize({path: options.default_icon}, extension,
                                 null, true),
     popup: popup,
   };
 
   this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                    extension);
 
   EventEmitter.decorate(this);
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -305,17 +305,17 @@ MenuItem.prototype = {
     this.children.splice(idx, 1);
     child.parent = null;
   },
 
   get root() {
     let extension = this.extension;
     if (!rootItems.has(extension)) {
       let root = new MenuItem(extension, this.context,
-                              { title: extension.name },
+                              {title: extension.name},
                               /* isRoot = */ true);
       rootItems.set(extension, root);
     }
 
     return rootItems.get(extension);
   },
 
   remove() {
--- a/browser/components/extensions/ext-desktop-runtime.js
+++ b/browser/components/extensions/ext-desktop-runtime.js
@@ -1,10 +1,10 @@
 "use strict";
 
 /* eslint-disable mozilla/balanced-listeners */
 extensions.on("uninstall", (msg, extension) => {
   if (extension.uninstallURL) {
     let browser = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
-    browser.addTab(extension.uninstallURL, { relatedToCurrent: true });
+    browser.addTab(extension.uninstallURL, {relatedToCurrent: true});
   }
 });
 
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -23,17 +23,17 @@ function PageAction(options, extension) 
   let popup = extension.localize(options.default_popup || "");
   if (popup) {
     popup = extension.baseURI.resolve(popup);
   }
 
   this.defaults = {
     show: false,
     title: title || extension.name,
-    icon: IconDetails.normalize({ path: options.default_icon }, extension,
+    icon: IconDetails.normalize({path: options.default_icon}, extension,
                                 null, true),
     popup: popup && extension.baseURI.resolve(popup),
   };
 
   this.tabContext = new TabContext(tab => Object.create(this.defaults),
                                    extension);
 
   this.tabContext.on("location-change", this.handleLocationChange.bind(this)); // eslint-disable-line mozilla/balanced-listeners
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -460,17 +460,17 @@ extensions.registerSchemaAPI("tabs", nul
             }
           }
         }
         return Promise.resolve(result);
       },
 
       captureVisibleTab: function(windowId, options) {
         if (!extension.hasPermission("<all_urls>")) {
-          return Promise.reject({ message: "The <all_urls> permission is required to use the captureVisibleTab API" });
+          return Promise.reject({message: "The <all_urls> permission is required to use the captureVisibleTab API"});
         }
 
         let window = windowId == null ?
           WindowManager.topWindow :
           WindowManager.getWindow(windowId);
 
         let browser = window.gBrowser.selectedBrowser;
         let recipient = {
@@ -519,31 +519,31 @@ extensions.registerSchemaAPI("tabs", nul
         }
 
         if (details.code !== null) {
           options[kind + "Code"] = details.code;
         }
         if (details.file !== null) {
           let url = context.uri.resolve(details.file);
           if (!extension.isExtensionURL(url)) {
-            return Promise.reject({ message: "Files to be injected must be within the extension" });
+            return Promise.reject({message: "Files to be injected must be within the extension"});
           }
           options[kind].push(url);
         }
         if (details.allFrames) {
           options.all_frames = details.allFrames;
         }
         if (details.matchAboutBlank) {
           options.match_about_blank = details.matchAboutBlank;
         }
         if (details.runAt !== null) {
           options.run_at = details.runAt;
         }
 
-        return context.sendMessage(mm, "Extension:Execute", { options }, recipient);
+        return context.sendMessage(mm, "Extension:Execute", {options}, recipient);
       },
 
       executeScript: function(tabId, details) {
         return self.tabs._execute(tabId, details, "js");
       },
 
       insertCSS: function(tabId, details) {
         return self.tabs._execute(tabId, details, "css");
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -230,17 +230,17 @@ class BasePopup {
       // We can't finish setting up the browser until the binding has fully
       // initialized. Waiting for the first load event guarantees that it has.
       let loadListener = event => {
         this.browser.removeEventListener("load", loadListener, true);
         resolve();
       };
       this.browser.addEventListener("load", loadListener, true);
     }).then(() => {
-      let { contentWindow } = this.browser;
+      let {contentWindow} = this.browser;
 
       contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIDOMWindowUtils)
                    .allowScriptsToClose();
 
       this.context = new ExtensionPage(this.extension, {
         type: "popup",
         contentWindow,
@@ -418,17 +418,17 @@ ExtensionTabManager.prototype = {
 
   hasTabPermission(tab) {
     return this.extension.hasPermission("tabs") || this.hasActiveTabPermission(tab);
   },
 
   convert(tab) {
     let window = tab.ownerDocument.defaultView;
 
-    let mutedInfo = { muted: tab.muted };
+    let mutedInfo = {muted: tab.muted};
     if (tab.muteReason === null) {
       mutedInfo.reason = "user";
     } else if (tab.muteReason) {
       mutedInfo.reason = "extension";
       mutedInfo.extensionId = tab.muteReason;
     }
 
     let result = {
--- a/browser/components/extensions/schemas/bookmarks.json
+++ b/browser/components/extensions/schemas/bookmarks.json
@@ -229,17 +229,16 @@
                 "items": { "$ref": "BookmarkTreeNode" }
               }
             ]
           }
         ]
       },
       {
         "name": "search",
-        "unsupported": true,
         "type": "function",
         "description": "Searches for BookmarkTreeNodes matching the given query. Queries specified with an object produce BookmarkTreeNodes matching all specified properties.",
         "async": "callback",
         "parameters": [
           {
             "name": "query",
             "description": "Either a string of words and quoted phrases that are matched against bookmark URLs and titles, or an object. If an object, the properties <code>query</code>, <code>url</code>, and <code>title</code> may be specified and bookmarks matching all specified properties will be produced.",
             "choices": [
@@ -253,16 +252,17 @@
                 "properties": {
                   "query": {
                     "type": "string",
                     "optional": true,
                     "description": "A string of words and quoted phrases that are matched against bookmark URLs and titles."
                   },
                   "url": {
                     "type": "string",
+                    "format": "url",
                     "optional": true,
                     "description": "The URL of the bookmark; matches verbatim. Note that folders have no URL."
                   },
                   "title": {
                     "type": "string",
                     "optional": true,
                     "description": "The title of the bookmark; matches verbatim."
                   }
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -8,20 +8,20 @@ function* runTests(options) {
     // promise that resolves to an object containing them.
     function getDetails(tabId) {
       return Promise.all([
         browser.browserAction.getTitle({tabId}),
         browser.browserAction.getPopup({tabId}),
         browser.browserAction.getBadgeText({tabId}),
         browser.browserAction.getBadgeBackgroundColor({tabId})]
       ).then(details => {
-        return Promise.resolve({ title: details[0],
-                                 popup: details[1],
-                                 badge: details[2],
-                                 badgeBackgroundColor: details[3] });
+        return Promise.resolve({title: details[0],
+                                popup: details[1],
+                                badge: details[2],
+                                badgeBackgroundColor: details[3]});
       });
     }
 
     function checkDetails(expecting, tabId) {
       return getDetails(tabId).then(details => {
         browser.test.assertEq(expecting.title, details.title,
                               "expected value from getTitle");
 
@@ -48,17 +48,17 @@ function* runTests(options) {
     // and passes control back to the outer test scope.
     function nextTest() {
       let test = tests.shift();
 
       test(expecting => {
         // Check that the API returns the expected values, and then
         // run the next test.
         new Promise(resolve => {
-          return browser.tabs.query({ active: true, currentWindow: true }, resolve);
+          return browser.tabs.query({active: true, currentWindow: true}, resolve);
         }).then(tabs => {
           return checkDetails(expecting, tabs[0].id);
         }).then(() => {
           // Check that the actual icon has the expected values, then
           // run the next test.
           browser.test.sendMessage("nextTest", expecting, tests.length);
         });
       });
@@ -67,17 +67,17 @@ function* runTests(options) {
     browser.test.onMessage.addListener((msg) => {
       if (msg != "runNextTest") {
         browser.test.fail("Expecting 'runNextTest' message");
       }
 
       nextTest();
     });
 
-    browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
+    browser.tabs.query({active: true, currentWindow: true}, resultTabs => {
       tabs[0] = resultTabs[0].id;
 
       nextTest();
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: options.manifest,
@@ -143,82 +143,82 @@ add_task(function* testTabSwitchContext(
         "default_popup": "default.html",
         "default_title": "Default Title",
       },
       "permissions": ["tabs"],
     },
 
     getTests(tabs, expectDefaults) {
       let details = [
-        { "icon": browser.runtime.getURL("default.png"),
-          "popup": browser.runtime.getURL("default.html"),
-          "title": "Default Title",
-          "badge": "",
-          "badgeBackgroundColor": null },
-        { "icon": browser.runtime.getURL("1.png"),
-          "popup": browser.runtime.getURL("default.html"),
-          "title": "Default Title",
-          "badge": "",
-          "badgeBackgroundColor": null },
-        { "icon": browser.runtime.getURL("2.png"),
-          "popup": browser.runtime.getURL("2.html"),
-          "title": "Title 2",
-          "badge": "2",
-          "badgeBackgroundColor": [0xff, 0, 0, 0xff],
-          "disabled": true },
-        { "icon": browser.runtime.getURL("1.png"),
-          "popup": browser.runtime.getURL("default-2.html"),
-          "title": "Default Title 2",
-          "badge": "d2",
-          "badgeBackgroundColor": [0, 0xff, 0, 0xff],
-          "disabled": true },
-        { "icon": browser.runtime.getURL("1.png"),
-          "popup": browser.runtime.getURL("default-2.html"),
-          "title": "Default Title 2",
-          "badge": "d2",
-          "badgeBackgroundColor": [0, 0xff, 0, 0xff],
-          "disabled": false },
-        { "icon": browser.runtime.getURL("default-2.png"),
-          "popup": browser.runtime.getURL("default-2.html"),
-          "title": "Default Title 2",
-          "badge": "d2",
-          "badgeBackgroundColor": [0, 0xff, 0, 0xff] },
+        {"icon": browser.runtime.getURL("default.png"),
+         "popup": browser.runtime.getURL("default.html"),
+         "title": "Default Title",
+         "badge": "",
+         "badgeBackgroundColor": null},
+        {"icon": browser.runtime.getURL("1.png"),
+         "popup": browser.runtime.getURL("default.html"),
+         "title": "Default Title",
+         "badge": "",
+         "badgeBackgroundColor": null},
+        {"icon": browser.runtime.getURL("2.png"),
+         "popup": browser.runtime.getURL("2.html"),
+         "title": "Title 2",
+         "badge": "2",
+         "badgeBackgroundColor": [0xff, 0, 0, 0xff],
+          "disabled": true},
+        {"icon": browser.runtime.getURL("1.png"),
+         "popup": browser.runtime.getURL("default-2.html"),
+         "title": "Default Title 2",
+         "badge": "d2",
+         "badgeBackgroundColor": [0, 0xff, 0, 0xff],
+          "disabled": true},
+        {"icon": browser.runtime.getURL("1.png"),
+         "popup": browser.runtime.getURL("default-2.html"),
+         "title": "Default Title 2",
+         "badge": "d2",
+         "badgeBackgroundColor": [0, 0xff, 0, 0xff],
+         "disabled": false},
+        {"icon": browser.runtime.getURL("default-2.png"),
+         "popup": browser.runtime.getURL("default-2.html"),
+         "title": "Default Title 2",
+         "badge": "d2",
+         "badgeBackgroundColor": [0, 0xff, 0, 0xff]},
       ];
 
       return [
         expect => {
           browser.test.log("Initial state, expect default properties.");
           expectDefaults(details[0]).then(() => {
             expect(details[0]);
           });
         },
         expect => {
           browser.test.log("Change the icon in the current tab. Expect default properties excluding the icon.");
-          browser.browserAction.setIcon({ tabId: tabs[0], path: "1.png" });
+          browser.browserAction.setIcon({tabId: tabs[0], path: "1.png"});
           expectDefaults(details[0]).then(() => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Create a new tab. Expect default properties.");
-          browser.tabs.create({ active: true, url: "about:blank?0" }, tab => {
+          browser.tabs.create({active: true, url: "about:blank?0"}, tab => {
             tabs.push(tab.id);
             expectDefaults(details[0]).then(() => {
               expect(details[0]);
             });
           });
         },
         expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
-          browser.browserAction.setIcon({ tabId, path: "2.png" });
-          browser.browserAction.setPopup({ tabId, popup: "2.html" });
-          browser.browserAction.setTitle({ tabId, title: "Title 2" });
-          browser.browserAction.setBadgeText({ tabId, text: "2" });
-          browser.browserAction.setBadgeBackgroundColor({ tabId, color: [0xff, 0, 0, 0xff] });
+          browser.browserAction.setIcon({tabId, path: "2.png"});
+          browser.browserAction.setPopup({tabId, popup: "2.html"});
+          browser.browserAction.setTitle({tabId, title: "Title 2"});
+          browser.browserAction.setBadgeText({tabId, text: "2"});
+          browser.browserAction.setBadgeBackgroundColor({tabId, color: [0xff, 0, 0, 0xff]});
           browser.browserAction.disable(tabId);
 
           expectDefaults(details[0]).then(() => {
             expect(details[2]);
           });
         },
         expect => {
           browser.test.log("Navigate to a new page. Expect no changes.");
@@ -227,60 +227,60 @@ add_task(function* testTabSwitchContext(
           // callback currently fires too early in e10s windows.
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == tabs[1] && changed.url) {
               browser.tabs.onUpdated.removeListener(listener);
               expect(details[2]);
             }
           });
 
-          browser.tabs.update(tabs[1], { url: "about:blank?1" });
+          browser.tabs.update(tabs[1], {url: "about:blank?1"});
         },
         expect => {
           browser.test.log("Switch back to the first tab. Expect previously set properties.");
-          browser.tabs.update(tabs[0], { active: true }, () => {
+          browser.tabs.update(tabs[0], {active: true}, () => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Change default values, expect those changes reflected.");
-          browser.browserAction.setIcon({ path: "default-2.png" });
-          browser.browserAction.setPopup({ popup: "default-2.html" });
-          browser.browserAction.setTitle({ title: "Default Title 2" });
-          browser.browserAction.setBadgeText({ text: "d2" });
-          browser.browserAction.setBadgeBackgroundColor({ color: [0, 0xff, 0, 0xff] });
+          browser.browserAction.setIcon({path: "default-2.png"});
+          browser.browserAction.setPopup({popup: "default-2.html"});
+          browser.browserAction.setTitle({title: "Default Title 2"});
+          browser.browserAction.setBadgeText({text: "d2"});
+          browser.browserAction.setBadgeBackgroundColor({color: [0, 0xff, 0, 0xff]});
           browser.browserAction.disable();
           expectDefaults(details[3]).then(() => {
             expect(details[3]);
           });
         },
         expect => {
           browser.test.log("Re-enable by default. Expect enabled.");
           browser.browserAction.enable();
           expectDefaults(details[4]).then(() => {
             expect(details[4]);
           });
         },
         expect => {
           browser.test.log("Switch back to tab 2. Expect former value, unaffected by changes to defaults in previous step.");
-          browser.tabs.update(tabs[1], { active: true }, () => {
+          browser.tabs.update(tabs[1], {active: true}, () => {
             expectDefaults(details[3]).then(() => {
               expect(details[2]);
             });
           });
         },
         expect => {
           browser.test.log("Delete tab, switch back to tab 1. Expect previous results again.");
           browser.tabs.remove(tabs[1], () => {
             expect(details[4]);
           });
         },
         expect => {
           browser.test.log("Create a new tab. Expect new default properties.");
-          browser.tabs.create({ active: true, url: "about:blank?2" }, tab => {
+          browser.tabs.create({active: true, url: "about:blank?2"}, tab => {
             tabs.push(tab.id);
             expect(details[5]);
           });
         },
         expect => {
           browser.test.log("Delete tab.");
           browser.tabs.remove(tabs[2], () => {
             expect(details[4]);
@@ -300,69 +300,69 @@ add_task(function* testDefaultTitle() {
         "default_icon": "icon.png",
       },
 
       "permissions": ["tabs"],
     },
 
     getTests(tabs, expectDefaults) {
       let details = [
-        { "title": "Foo Extension",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "Foo Title",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "Bar Title",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "",
-          "popup": "",
-          "badge": "",
-          "badgeBackgroundColor": null,
-          "icon": browser.runtime.getURL("icon.png") },
+        {"title": "Foo Extension",
+         "popup": "",
+         "badge": "",
+         "badgeBackgroundColor": null,
+         "icon": browser.runtime.getURL("icon.png")},
+        {"title": "Foo Title",
+         "popup": "",
+         "badge": "",
+         "badgeBackgroundColor": null,
+         "icon": browser.runtime.getURL("icon.png")},
+        {"title": "Bar Title",
+         "popup": "",
+         "badge": "",
+         "badgeBackgroundColor": null,
+         "icon": browser.runtime.getURL("icon.png")},
+        {"title": "",
+         "popup": "",
+         "badge": "",
+         "badgeBackgroundColor": null,
+         "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
         expect => {
           browser.test.log("Initial state. Expect extension title as default title.");
           expectDefaults(details[0]).then(() => {
             expect(details[0]);
           });
         },
         expect => {
           browser.test.log("Change the title. Expect new title.");
-          browser.browserAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
+          browser.browserAction.setTitle({tabId: tabs[0], title: "Foo Title"});
           expectDefaults(details[0]).then(() => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Change the default. Expect same properties.");
-          browser.browserAction.setTitle({ title: "Bar Title" });
+          browser.browserAction.setTitle({title: "Bar Title"});
           expectDefaults(details[2]).then(() => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Clear the title. Expect new default title.");
-          browser.browserAction.setTitle({ tabId: tabs[0], title: "" });
+          browser.browserAction.setTitle({tabId: tabs[0], title: ""});
           expectDefaults(details[2]).then(() => {
             expect(details[2]);
           });
         },
         expect => {
           browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
-          browser.browserAction.setTitle({ title: "" });
+          browser.browserAction.setTitle({title: ""});
           expectDefaults(details[3]).then(() => {
             expect(details[3]);
           });
         },
       ];
     },
   });
 });
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -26,110 +26,110 @@ add_task(function* testDetailsObjects() 
     let imageData = {
       red: getImageData("red"),
       green: getImageData("green"),
     };
 
     /* eslint-disable comma-dangle, indent */
     let iconDetails = [
       // Only paths.
-      { details: { "path": "a.png" },
+      {details: {"path": "a.png"},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": browser.runtime.getURL("data/a.png"), } },
-      { details: { "path": "/a.png" },
+          "2": browser.runtime.getURL("data/a.png")}},
+      {details: {"path": "/a.png"},
         resolutions: {
           "1": browser.runtime.getURL("a.png"),
-          "2": browser.runtime.getURL("a.png"), } },
-      { details: { "path": { "19": "a.png" } },
+          "2": browser.runtime.getURL("a.png")}},
+      {details: {"path": {"19": "a.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": browser.runtime.getURL("data/a.png"), } },
-      { details: { "path": { "38": "a.png" } },
+          "2": browser.runtime.getURL("data/a.png")}},
+      {details: {"path": {"38": "a.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": browser.runtime.getURL("data/a.png"), } },
-      { details: { "path": { "19": "a.png", "38": "a-x2.png" } },
+          "2": browser.runtime.getURL("data/a.png")}},
+      {details: {"path": {"19": "a.png", "38": "a-x2.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": browser.runtime.getURL("data/a-x2.png"), } },
+          "2": browser.runtime.getURL("data/a-x2.png")}},
 
       // Only ImageData objects.
-      { details: { "imageData": imageData.red.imageData },
+      {details: {"imageData": imageData.red.imageData},
         resolutions: {
           "1": imageData.red.url,
-          "2": imageData.red.url, } },
-      { details: { "imageData": { "19": imageData.red.imageData } },
+          "2": imageData.red.url}},
+      {details: {"imageData": {"19": imageData.red.imageData}},
         resolutions: {
           "1": imageData.red.url,
-          "2": imageData.red.url, } },
-      { details: { "imageData": { "38": imageData.red.imageData } },
+          "2": imageData.red.url}},
+      {details: {"imageData": {"38": imageData.red.imageData}},
         resolutions: {
           "1": imageData.red.url,
-          "2": imageData.red.url, } },
-      { details: { "imageData": {
+          "2": imageData.red.url}},
+      {details: {"imageData": {
           "19": imageData.red.imageData,
-          "38": imageData.green.imageData } },
+          "38": imageData.green.imageData}},
         resolutions: {
           "1": imageData.red.url,
-          "2": imageData.green.url, } },
+          "2": imageData.green.url}},
 
       // Mixed path and imageData objects.
       //
       // The behavior is currently undefined if both |path| and
       // |imageData| specify icons of the same size.
-      { details: {
-          "path": { "19": "a.png" },
-          "imageData": { "38": imageData.red.imageData } },
+      {details: {
+          "path": {"19": "a.png"},
+          "imageData": {"38": imageData.red.imageData}},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": imageData.red.url, } },
-      { details: {
-          "path": { "38": "a.png" },
-          "imageData": { "19": imageData.red.imageData } },
+          "2": imageData.red.url}},
+      {details: {
+          "path": {"38": "a.png"},
+          "imageData": {"19": imageData.red.imageData}},
         resolutions: {
           "1": imageData.red.url,
-          "2": browser.runtime.getURL("data/a.png"), } },
+          "2": browser.runtime.getURL("data/a.png")}},
 
       // A path or ImageData object by itself is treated as a 19px icon.
-      { details: {
+      {details: {
           "path": "a.png",
-          "imageData": { "38": imageData.red.imageData } },
+          "imageData": {"38": imageData.red.imageData}},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": imageData.red.url, } },
-      { details: {
-          "path": { "38": "a.png" },
-          "imageData": imageData.red.imageData, },
+          "2": imageData.red.url}},
+      {details: {
+          "path": {"38": "a.png"},
+          "imageData": imageData.red.imageData},
         resolutions: {
           "1": imageData.red.url,
-          "2": browser.runtime.getURL("data/a.png"), } },
+          "2": browser.runtime.getURL("data/a.png")}},
 
       // Various resolutions
-      { details: { "path": { "18": "a.png", "32": "a-x2.png" } },
+      {details: {"path": {"18": "a.png", "32": "a-x2.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/a.png"),
-          "2": browser.runtime.getURL("data/a-x2.png"), } },
-      { details: { "path": { "16": "16.png", "100": "100.png" } },
+          "2": browser.runtime.getURL("data/a-x2.png")}},
+      {details: {"path": {"16": "16.png", "100": "100.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/100.png"),
-          "2": browser.runtime.getURL("data/100.png"), } },
-      { details: { "path": { "2": "2.png"} },
+          "2": browser.runtime.getURL("data/100.png")}},
+      {details: {"path": {"2": "2.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/2.png"),
-          "2": browser.runtime.getURL("data/2.png"), } },
-      { details: { "path": {
+          "2": browser.runtime.getURL("data/2.png")}},
+      {details: {"path": {
         "6": "6.png",
         "18": "18.png",
         "32": "32.png",
         "48": "48.png",
-        "128": "128.png" } },
+        "128": "128.png"}},
         resolutions: {
           "1": browser.runtime.getURL("data/18.png"),
-          "2": browser.runtime.getURL("data/48.png"), } },
+          "2": browser.runtime.getURL("data/48.png")}},
     ];
 
     // Allow serializing ImageData objects for logging.
     ImageData.prototype.toJSON = () => "<ImageData>";
 
     let tabId;
 
     browser.test.onMessage.addListener((msg, test) => {
@@ -157,25 +157,25 @@ add_task(function* testDetailsObjects() 
     // but it can't pass us icon definitions with ImageData objects. This
     // shouldn't be a problem, since structured clones should handle ImageData
     // objects without issue. Unfortunately, |cloneInto| implements a slightly
     // different algorithm than we use in web APIs, and does not handle them
     // correctly.
     let tests = [];
     for (let [idx, icon] of iconDetails.entries()) {
       for (let res of Object.keys(icon.resolutions)) {
-        tests.push({ index: idx, resolution: Number(res) });
+        tests.push({index: idx, resolution: Number(res)});
       }
     }
 
     // Sort by resolution, so we don't needlessly switch back and forth
     // between each test.
     tests.sort(test => test.resolution);
 
-    browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+    browser.tabs.query({active: true, currentWindow: true}, tabs => {
       tabId = tabs[0].id;
       browser.pageAction.show(tabId);
 
       browser.test.sendMessage("ready", tests);
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
@@ -225,17 +225,17 @@ add_task(function* testDetailsObjects() 
 add_task(function* testInvalidIconSizes() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {},
       "page_action": {},
     },
 
     background: function() {
-      browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+      browser.tabs.query({active: true, currentWindow: true}, tabs => {
         let tabId = tabs[0].id;
 
         let promises = [];
         for (let api of ["pageAction", "browserAction"]) {
           // helper function to run setIcon and check if it fails
           let assertSetIconThrows = function(detail, error, message) {
             detail.tabId = tabId;
             promises.push(
@@ -250,26 +250,26 @@ add_task(function* testInvalidIconSizes(
           };
 
           let imageData = new ImageData(1, 1);
 
           // test invalid icon size inputs
           for (let type of ["path", "imageData"]) {
             let img = type == "imageData" ? imageData : "test.png";
 
-            assertSetIconThrows({ [type]: { "abcdef": img } });
-            assertSetIconThrows({ [type]: { "48px": img } });
-            assertSetIconThrows({ [type]: { "20.5": img } });
-            assertSetIconThrows({ [type]: { "5.0": img } });
-            assertSetIconThrows({ [type]: { "-300": img } });
-            assertSetIconThrows({ [type]: { "abc": img, "5": img }});
+            assertSetIconThrows({[type]: {"abcdef": img}});
+            assertSetIconThrows({[type]: {"48px": img}});
+            assertSetIconThrows({[type]: {"20.5": img}});
+            assertSetIconThrows({[type]: {"5.0": img}});
+            assertSetIconThrows({[type]: {"-300": img}});
+            assertSetIconThrows({[type]: {"abc": img, "5": img}});
           }
 
-          assertSetIconThrows({ imageData: { "abcdef": imageData }, path: {"5": "test.png"} });
-          assertSetIconThrows({ path: { "abcdef": "test.png" }, imageData: {"5": imageData} });
+          assertSetIconThrows({imageData: {"abcdef": imageData}, path: {"5": "test.png"}});
+          assertSetIconThrows({path: {"abcdef": "test.png"}, imageData: {"5": imageData}});
         }
 
         Promise.all(promises).then(() => {
           browser.test.notifyPass("setIcon with invalid icon size");
         });
       });
     }
   });
@@ -282,32 +282,32 @@ add_task(function* testInvalidIconSizes(
 
 // Test that default icon details in the manifest.json file are handled
 // correctly.
 add_task(function* testDefaultDetails() {
   // TODO: Test localized variants.
   let icons = [
     "foo/bar.png",
     "/foo/bar.png",
-    { "19": "foo/bar.png" },
-    { "38": "foo/bar.png" },
-    { "19": "foo/bar.png", "38": "baz/quux.png" },
+    {"19": "foo/bar.png"},
+    {"38": "foo/bar.png"},
+    {"19": "foo/bar.png", "38": "baz/quux.png"},
   ];
 
   let expectedURL = new RegExp(String.raw`^moz-extension://[^/]+/foo/bar\.png$`);
 
   for (let icon of icons) {
     let extension = ExtensionTestUtils.loadExtension({
       manifest: {
-        "browser_action": { "default_icon": icon },
-        "page_action": { "default_icon": icon },
+        "browser_action": {"default_icon": icon},
+        "page_action": {"default_icon": icon},
       },
 
       background: function() {
-        browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+        browser.tabs.query({active: true, currentWindow: true}, tabs => {
           let tabId = tabs[0].id;
 
           browser.pageAction.show(tabId);
           browser.test.sendMessage("ready");
         });
       }
     });
 
@@ -340,17 +340,17 @@ add_task(function* testSecureURLsDenied(
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {},
       "page_action": {},
     },
 
     background: function() {
-      browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+      browser.tabs.query({active: true, currentWindow: true}, tabs => {
         let tabId = tabs[0].id;
 
         let urls = ["chrome://browser/content/browser.xul",
                     "javascript:true"];
 
         let promises = [];
         for (let url of urls) {
           for (let api of ["pageAction", "browserAction"]) {
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup.js
@@ -25,44 +25,44 @@ function* testInArea(area) {
       },
 
       "data/background.html": `<script src="background.js"></script>`,
 
       "data/background.js": function() {
         let sendClick;
         let tests = [
           () => {
-            sendClick({ expectEvent: false, expectPopup: "a" });
+            sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
-            sendClick({ expectEvent: false, expectPopup: "a" });
+            sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
-            browser.browserAction.setPopup({ popup: "popup-b.html" });
-            sendClick({ expectEvent: false, expectPopup: "b" });
+            browser.browserAction.setPopup({popup: "popup-b.html"});
+            sendClick({expectEvent: false, expectPopup: "b"});
           },
           () => {
-            sendClick({ expectEvent: false, expectPopup: "b" });
+            sendClick({expectEvent: false, expectPopup: "b"});
           },
           () => {
-            browser.browserAction.setPopup({ popup: "" });
-            sendClick({ expectEvent: true, expectPopup: null });
+            browser.browserAction.setPopup({popup: ""});
+            sendClick({expectEvent: true, expectPopup: null});
           },
           () => {
-            sendClick({ expectEvent: true, expectPopup: null });
+            sendClick({expectEvent: true, expectPopup: null});
           },
           () => {
-            browser.browserAction.setPopup({ popup: "/popup-a.html" });
-            sendClick({ expectEvent: false, expectPopup: "a" });
+            browser.browserAction.setPopup({popup: "/popup-a.html"});
+            sendClick({expectEvent: false, expectPopup: "a"});
           },
         ];
 
         let expect = {};
-        sendClick = ({ expectEvent, expectPopup }) => {
-          expect = { event: expectEvent, popup: expectPopup };
+        sendClick = ({expectEvent, expectPopup}) => {
+          expect = {event: expectEvent, popup: expectPopup};
           browser.test.sendMessage("send-click");
         };
 
         browser.runtime.onMessage.addListener(msg => {
           if (expect.popup) {
             browser.test.assertEq(msg, `from-popup-${expect.popup}`,
                                   "expected popup opened");
           } else {
--- a/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
@@ -38,17 +38,17 @@ add_task(function* () {
 
           port_messages_received++;
           browser.test.assertEq(2, port_messages_received, "2 port messages received");
 
           browser.test.notifyPass("contentscript_connect.pass");
         });
       });
 
-      browser.tabs.executeScript({ file: "script.js" });
+      browser.tabs.executeScript({file: "script.js"});
     },
 
     files: {
       "script.js": function() {
         let port = browser.runtime.connect();
         port.postMessage("port message");
       },
     },
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus.js
@@ -17,49 +17,49 @@ add_task(function* () {
     },
 
     background: function() {
       // A generic onclick callback function.
       function genericOnClick(info) {
         browser.test.sendMessage("menuItemClick", JSON.stringify(info));
       }
 
-      browser.contextMenus.create({ contexts: ["all"], type: "separator" });
+      browser.contextMenus.create({contexts: ["all"], type: "separator"});
 
       let contexts = ["page", "selection", "image"];
       for (let i = 0; i < contexts.length; i++) {
         let context = contexts[i];
         let title = context;
-        browser.contextMenus.create({ title: title, contexts: [context], id: "ext-" + context,
-                                      onclick: genericOnClick });
+        browser.contextMenus.create({title: title, contexts: [context], id: "ext-" + context,
+                                     onclick: genericOnClick});
         if (context == "selection") {
           browser.contextMenus.update("ext-selection", {
             title: "selection is: '%s'",
             onclick: (info) => {
               browser.contextMenus.removeAll();
               genericOnClick(info);
             },
           });
         }
       }
 
-      let parent = browser.contextMenus.create({ title: "parent" });
+      let parent = browser.contextMenus.create({title: "parent"});
       browser.contextMenus.create(
-        { title: "child1", parentId: parent, onclick: genericOnClick });
+        {title: "child1", parentId: parent, onclick: genericOnClick});
       let child2 = browser.contextMenus.create(
-        { title: "child2", parentId: parent, onclick: genericOnClick });
+        {title: "child2", parentId: parent, onclick: genericOnClick});
 
-      let parentToDel = browser.contextMenus.create({ title: "parentToDel" });
+      let parentToDel = browser.contextMenus.create({title: "parentToDel"});
       browser.contextMenus.create(
-        { title: "child1", parentId: parentToDel, onclick: genericOnClick });
+        {title: "child1", parentId: parentToDel, onclick: genericOnClick});
       browser.contextMenus.create(
-        { title: "child2", parentId: parentToDel, onclick: genericOnClick });
+        {title: "child2", parentId: parentToDel, onclick: genericOnClick});
       browser.contextMenus.remove(parentToDel);
 
-      browser.contextMenus.update(parent, { parentId: child2 }).then(
+      browser.contextMenus.update(parent, {parentId: child2}).then(
         () => {
           browser.test.notifyFail();
         },
         () => {
           browser.test.notifyPass();
         });
     },
   });
@@ -76,17 +76,17 @@ add_task(function* () {
 
   yield extension.startup();
   yield extension.awaitFinish();
 
   // Bring up context menu
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
   let popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
   yield BrowserTestUtils.synthesizeMouseAtCenter("#img1",
-    { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+    {type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
   yield popupShownPromise;
 
   // Check some menu items
   let items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   is(items.length, 1, "top level item was found (context=image)");
   let topItem = items.item(0);
   let top = topItem.childNodes[0];
 
@@ -129,17 +129,17 @@ add_task(function* () {
     range.setStart(textNode, 0);
     range.setEnd(textNode, 100);
     selection.addRange(range);
   });
 
   // Bring up context menu again
   popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
   yield BrowserTestUtils.synthesizeMouse(null, 1, 1,
-    { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+    {type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
   yield popupShownPromise;
 
   items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   is(items.length, 1, "top level item was found (context=selection)");
   top = items.item(0).childNodes[0];
 
   // Check some menu items
   items = top.getElementsByAttribute("label", "selection is: 'just some text 123456789012345678901234567890...'");
@@ -158,17 +158,17 @@ add_task(function* () {
   top.openPopup(topItem, "end_before", 0, 0, true, false);
   EventUtils.synthesizeMouseAtCenter(selectionItem, {});
   clickInfo = yield extension.awaitMessage("menuItemClick");
   checkClickInfo(clickInfo);
   yield popupHiddenPromise;
 
   popupShownPromise = BrowserTestUtils.waitForEvent(contentAreaContextMenu, "popupshown");
   yield BrowserTestUtils.synthesizeMouseAtCenter("#img1",
-    { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+    {type: "contextmenu", button: 2}, gBrowser.selectedBrowser);
   yield popupShownPromise;
 
   items = contentAreaContextMenu.getElementsByAttribute("ext-type", "top-level-menu");
   is(items.length, 0, "top level item was not found (after removeAll()");
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab1);
--- a/browser/components/extensions/test/browser/browser_ext_lastError.js
+++ b/browser/components/extensions/test/browser/browser_ext_lastError.js
@@ -32,17 +32,17 @@ add_task(function* testLastError() {
 
   // Check that we have no unexpected console messages when lastError is
   // checked.
   for (let api of ["extension", "runtime"]) {
     let waitForConsole = new Promise(resolve => {
       SimpleTest.monitorConsole(resolve, [{message: /Invalid extension ID/, forbid: true}]);
     });
 
-    yield sendMessage({ checkLastError: api });
+    yield sendMessage({checkLastError: api});
 
     SimpleTest.endMonitorConsole();
     yield waitForConsole;
   }
 
   // Check that we do have a console message when lastError is not checked.
   let waitForConsole = new Promise(resolve => {
     SimpleTest.monitorConsole(resolve, [{message: /Unchecked lastError value: Error: Invalid extension ID/}]);
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -6,26 +6,26 @@ function* runTests(options) {
   function background(getTests) {
     let tabs;
     let tests;
 
     // Gets the current details of the page action, and returns a
     // promise that resolves to an object containing them.
     function getDetails() {
       return new Promise(resolve => {
-        return browser.tabs.query({ active: true, currentWindow: true }, resolve);
+        return browser.tabs.query({active: true, currentWindow: true}, resolve);
       }).then(([tab]) => {
         let tabId = tab.id;
         browser.test.log(`Get details: tab={id: ${tabId}, url: ${JSON.stringify(tab.url)}}`);
         return Promise.all([
           browser.pageAction.getTitle({tabId}),
           browser.pageAction.getPopup({tabId})]);
       }).then(details => {
-        return Promise.resolve({ title: details[0],
-                                 popup: details[1] });
+        return Promise.resolve({title: details[0],
+                                popup: details[1]});
       });
     }
 
 
     // Runs the next test in the `tests` array, checks the results,
     // and passes control back to the outer test scope.
     function nextTest() {
       let test = tests.shift();
@@ -54,17 +54,17 @@ function* runTests(options) {
         }
       });
     }
 
     function runTests() {
       tabs = [];
       tests = getTests(tabs);
 
-      browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
+      browser.tabs.query({active: true, currentWindow: true}, resultTabs => {
         tabs[0] = resultTabs[0].id;
 
         nextTest();
       });
     }
 
     browser.test.onMessage.addListener((msg) => {
       if (msg == "runTests") {
@@ -158,28 +158,28 @@ add_task(function* testTabSwitchContext(
         "default_title": "Default Title \u263a",
       },
 
       "permissions": ["tabs"],
     },
 
     getTests(tabs) {
       let details = [
-        { "icon": browser.runtime.getURL("default.png"),
-          "popup": browser.runtime.getURL("default.html"),
-          "title": "Default Title \u263a" },
-        { "icon": browser.runtime.getURL("1.png"),
-          "popup": browser.runtime.getURL("default.html"),
-          "title": "Default Title \u263a" },
-        { "icon": browser.runtime.getURL("2.png"),
-          "popup": browser.runtime.getURL("2.html"),
-          "title": "Title 2" },
-        { "icon": browser.runtime.getURL("2.png"),
-          "popup": browser.runtime.getURL("2.html"),
-          "title": "Default Title \u263a" },
+        {"icon": browser.runtime.getURL("default.png"),
+         "popup": browser.runtime.getURL("default.html"),
+         "title": "Default Title \u263a"},
+        {"icon": browser.runtime.getURL("1.png"),
+         "popup": browser.runtime.getURL("default.html"),
+         "title": "Default Title \u263a"},
+        {"icon": browser.runtime.getURL("2.png"),
+         "popup": browser.runtime.getURL("2.html"),
+         "title": "Title 2"},
+        {"icon": browser.runtime.getURL("2.png"),
+         "popup": browser.runtime.getURL("2.html"),
+         "title": "Default Title \u263a"},
       ];
 
       let promiseTabLoad = details => {
         return new Promise(resolve => {
           browser.tabs.onUpdated.addListener(function listener(tabId, changed) {
             if (tabId == details.id && changed.url == details.url) {
               browser.tabs.onUpdated.removeListener(listener);
               resolve();
@@ -196,75 +196,75 @@ add_task(function* testTabSwitchContext(
         },
         expect => {
           browser.test.log("Show the icon on the first tab, expect default properties.");
           browser.pageAction.show(tabs[0]);
           expect(details[0]);
         },
         expect => {
           browser.test.log("Change the icon. Expect default properties excluding the icon.");
-          browser.pageAction.setIcon({ tabId: tabs[0], path: "1.png" });
+          browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"});
           expect(details[1]);
         },
         expect => {
           browser.test.log("Create a new tab. No icon visible.");
-          browser.tabs.create({ active: true, url: "about:blank?0" }, tab => {
-            tabLoadPromise = promiseTabLoad({ url: "about:blank?0", id: tab.id });
+          browser.tabs.create({active: true, url: "about:blank?0"}, tab => {
+            tabLoadPromise = promiseTabLoad({url: "about:blank?0", id: tab.id});
             tabs.push(tab.id);
             expect(null);
           });
         },
         expect => {
           browser.test.log("Await tab load. No icon visible.");
           tabLoadPromise.then(() => {
             expect(null);
           });
         },
         expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
           browser.pageAction.show(tabId);
-          browser.pageAction.setIcon({ tabId, path: "2.png" });
-          browser.pageAction.setPopup({ tabId, popup: "2.html" });
-          browser.pageAction.setTitle({ tabId, title: "Title 2" });
+          browser.pageAction.setIcon({tabId, path: "2.png"});
+          browser.pageAction.setPopup({tabId, popup: "2.html"});
+          browser.pageAction.setTitle({tabId, title: "Title 2"});
 
           expect(details[2]);
         },
         expect => {
           browser.test.log("Clear the title. Expect default title.");
-          browser.pageAction.setTitle({ tabId: tabs[1], title: "" });
+          browser.pageAction.setTitle({tabId: tabs[1], title: ""});
 
           expect(details[3]);
         },
         expect => {
           browser.test.log("Navigate to a new page. Expect icon hidden.");
 
           // TODO: This listener should not be necessary, but the |tabs.update|
           // callback currently fires too early in e10s windows.
-          promiseTabLoad({ id: tabs[1], url: "about:blank?1" }).then(() => {
+          promiseTabLoad({id: tabs[1], url: "about:blank?1"}).then(() => {
             expect(null);
           });
 
-          browser.tabs.update(tabs[1], { url: "about:blank?1" });
+          browser.tabs.update(tabs[1], {url: "about:blank?1"});
         },
         expect => {
           browser.test.log("Show the icon. Expect default properties again.");
           browser.pageAction.show(tabs[1]);
           expect(details[0]);
         },
         expect => {
           browser.test.log("Switch back to the first tab. Expect previously set properties.");
-          browser.tabs.update(tabs[0], { active: true }, () => {
+          browser.tabs.update(tabs[0], {active: true}, () => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
           browser.pageAction.hide(tabs[1]);
-          browser.tabs.update(tabs[1], { active: true }, () => {
+          browser.tabs.update(tabs[1], {active: true}, () => {
             expect(null);
           });
         },
         expect => {
           browser.test.log("Switch back to tab 1. Expect previous results again.");
           browser.tabs.remove(tabs[1], () => {
             expect(details[1]);
           });
@@ -288,40 +288,40 @@ add_task(function* testDefaultTitle() {
         "default_icon": "icon.png",
       },
 
       "permissions": ["tabs"],
     },
 
     getTests(tabs) {
       let details = [
-        { "title": "Foo Extension",
-          "popup": "",
-          "icon": browser.runtime.getURL("icon.png") },
-        { "title": "Foo Title",
-          "popup": "",
-          "icon": browser.runtime.getURL("icon.png") },
+        {"title": "Foo Extension",
+         "popup": "",
+         "icon": browser.runtime.getURL("icon.png")},
+        {"title": "Foo Title",
+         "popup": "",
+         "icon": browser.runtime.getURL("icon.png")},
       ];
 
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
         expect => {
           browser.test.log("Show the icon on the first tab, expect extension title as default title.");
           browser.pageAction.show(tabs[0]);
           expect(details[0]);
         },
         expect => {
           browser.test.log("Change the title. Expect new title.");
-          browser.pageAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
+          browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"});
           expect(details[1]);
         },
         expect => {
           browser.test.log("Clear the title. Expect extension title.");
-          browser.pageAction.setTitle({ tabId: tabs[0], title: "" });
+          browser.pageAction.setTitle({tabId: tabs[0], title: ""});
           expect(details[0]);
         },
       ];
     },
   });
 });
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -29,44 +29,44 @@ add_task(function* testPageActionPopup()
       "data/background.html": scriptPage("background.js"),
 
       "data/background.js": function() {
         let tabId;
 
         let sendClick;
         let tests = [
           () => {
-            sendClick({ expectEvent: false, expectPopup: "a" });
+            sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
-            sendClick({ expectEvent: false, expectPopup: "a" });
+            sendClick({expectEvent: false, expectPopup: "a"});
           },
           () => {
-            browser.pageAction.setPopup({ tabId, popup: "popup-b.html" });
-            sendClick({ expectEvent: false, expectPopup: "b" });
+            browser.pageAction.setPopup({tabId, popup: "popup-b.html"});
+            sendClick({expectEvent: false, expectPopup: "b"});
           },
           () => {
-            sendClick({ expectEvent: false, expectPopup: "b" });
+            sendClick({expectEvent: false, expectPopup: "b"});
           },
           () => {
-            browser.pageAction.setPopup({ tabId, popup: "" });
-            sendClick({ expectEvent: true, expectPopup: null });
+            browser.pageAction.setPopup({tabId, popup: ""});
+            sendClick({expectEvent: true, expectPopup: null});
           },
           () => {
-            sendClick({ expectEvent: true, expectPopup: null });
+            sendClick({expectEvent: true, expectPopup: null});
           },
           () => {
-            browser.pageAction.setPopup({ tabId, popup: "/popup-a.html" });
-            sendClick({ expectEvent: false, expectPopup: "a" });
+            browser.pageAction.setPopup({tabId, popup: "/popup-a.html"});
+            sendClick({expectEvent: false, expectPopup: "a"});
           },
         ];
 
         let expect = {};
-        sendClick = ({ expectEvent, expectPopup }) => {
-          expect = { event: expectEvent, popup: expectPopup };
+        sendClick = ({expectEvent, expectPopup}) => {
+          expect = {event: expectEvent, popup: expectPopup};
           browser.test.sendMessage("send-click");
         };
 
         browser.runtime.onMessage.addListener(msg => {
           if (expect.popup) {
             browser.test.assertEq(msg, `from-popup-${expect.popup}`,
                                   "expected popup opened");
           } else {
@@ -96,17 +96,17 @@ add_task(function* testPageActionPopup()
           if (tests.length) {
             let test = tests.shift();
             test();
           } else {
             browser.test.notifyPass("pageaction-tests-done");
           }
         });
 
-        browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+        browser.tabs.query({active: true, currentWindow: true}, tabs => {
           tabId = tabs[0].id;
 
           browser.pageAction.show(tabId);
           browser.test.sendMessage("next-test");
         });
       },
     },
   });
@@ -160,17 +160,17 @@ add_task(function* testPageActionSecurit
       // if we don't call it.
       SimpleTest.waitForExplicitFinish();
 
       SimpleTest.monitorConsole(resolve, messages);
     });
 
     let extension = ExtensionTestUtils.loadExtension({
       manifest: {
-        [api]: { "default_popup": URL },
+        [api]: {"default_popup": URL},
       },
     });
 
     yield Assert.rejects(extension.startup(),
                          null,
                          "Manifest rejected");
 
     SimpleTest.endMonitorConsole();
--- a/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
@@ -22,25 +22,25 @@ add_task(function* testPageActionPopup()
 
       "popup-b.html": String.raw`<html><head><meta charset="utf-8"><script type="application/javascript">
         browser.test.sendMessage("from-popup-b");
       </script></head></html>`,
     },
 
     background: function() {
       let tabId;
-      browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+      browser.tabs.query({active: true, currentWindow: true}, tabs => {
         tabId = tabs[0].id;
         browser.pageAction.show(tabId);
         browser.test.sendMessage("ready");
       });
 
       browser.test.onMessage.addListener(() => {
-        browser.browserAction.setPopup({ popup: "/popup-a.html" });
-        browser.pageAction.setPopup({ tabId, popup: "popup-b.html" });
+        browser.browserAction.setPopup({popup: "/popup-a.html"});
+        browser.pageAction.setPopup({tabId, popup: "popup-b.html"});
 
         browser.test.sendMessage("ok");
       });
     },
   });
 
   let promiseConsoleMessage = pattern => new Promise(resolve => {
     Services.console.registerListener(function listener(msg) {
--- a/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_setUninstallURL.js
@@ -1,12 +1,12 @@
 "use strict";
 
-let { AddonManager } = Components.utils.import("resource://gre/modules/AddonManager.jsm", {});
-let { Extension } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
+let {AddonManager} = Components.utils.import("resource://gre/modules/AddonManager.jsm", {});
+let {Extension} = Components.utils.import("resource://gre/modules/Extension.jsm", {});
 
 function install(url) {
   return new Promise((resolve, reject) => {
     AddonManager.getInstallForURL(url, (install) => {
       install.addListener({
         onInstallEnded: (i, addon) => resolve(addon),
         onInstallFailed: () => reject(),
       });
@@ -73,34 +73,34 @@ add_task(function* test_setuninstallurl_
 });
 
 // Test the documented behavior of setUninstallURL() that passing an
 // empty string is equivalent to not setting an uninstall URL
 // (i.e., no new tab is opened upon uninstall)
 add_task(function* test_setuninstall_empty_url() {
   function backgroundScript() {
     browser.runtime.setUninstallURL("")
-      .then(() => browser.tabs.create({ url: "http://example.com/addon_loaded" }));
+      .then(() => browser.tabs.create({url: "http://example.com/addon_loaded"}));
   }
 
   let addon = yield makeAndInstallXPI("test_uinstallurl2@tests.mozilla.org",
                                       backgroundScript,
                                       "http://example.com/addon_loaded");
 
   addon.uninstall(true);
   info("uninstalled");
 
   // no need to explicitly check for the absence of a new tab,
   // BrowserTestUtils will eventually complain if one is opened.
 });
 
 add_task(function* test_setuninstallurl() {
   function backgroundScript() {
     browser.runtime.setUninstallURL("http://example.com/addon_uninstalled")
-      .then(() => browser.tabs.create({ url: "http://example.com/addon_loaded" }));
+      .then(() => browser.tabs.create({url: "http://example.com/addon_loaded"}));
   }
 
   let addon = yield makeAndInstallXPI("test_uinstallurl@tests.mozilla.org",
                                       backgroundScript,
                                       "http://example.com/addon_loaded");
 
   // look for a new tab with the uninstall url.
   let uninstallPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/addon_uninstalled");
--- a/browser/components/extensions/test/browser/browser_ext_tab_runtimeConnect.js
+++ b/browser/components/extensions/test/browser/browser_ext_tab_runtimeConnect.js
@@ -33,26 +33,26 @@ add_task(function* () {
             browser.test.assertTrue(!!msg.tabReceived, "'background to tab' reply port message received");
             browser.test.assertEq("background to tab port message", msg.tabReceived, "reply port content contains the message received");
 
             browser.test.notifyPass("tabRuntimeConnect.pass");
           }
         });
       });
 
-      browser.tabs.create({ url: "tab.html" },
+      browser.tabs.create({url: "tab.html"},
                           (tab) => { tabId = tab.id; });
     },
 
     files: {
       "tab.js": function() {
-        let port = browser.runtime.connect({ name: "tab-connection-name"});
+        let port = browser.runtime.connect({name: "tab-connection-name"});
         port.postMessage("tab to background port message");
         port.onMessage.addListener((msg) => {
-          port.postMessage({ tabReceived: msg });
+          port.postMessage({tabReceived: msg});
         });
       },
       "tab.html": `
         <!DOCTYPE html>
         <html>
           <head>
             <title>test tab extension page</title>
             <meta charset="utf-8">
--- a/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_audio.js
@@ -44,36 +44,36 @@ add_task(function* () {
         deferred[tabId] = {resolve, reject};
         browser.test.sendMessage("change-tab", tabId, attr, on);
       });
     }
 
 
     let windowId;
     let tabIds;
-    promiseTabs.query({ lastFocusedWindow: true }).then(tabs => {
+    promiseTabs.query({lastFocusedWindow: true}).then(tabs => {
       browser.test.assertEq(tabs.length, 3, "We have three tabs");
 
       for (let tab of tabs) {
         // Note: We want to check that these are actual boolean values, not
         // just that they evaluate as false.
         browser.test.assertEq(false, tab.mutedInfo.muted, "Tab is not muted");
         browser.test.assertEq(undefined, tab.mutedInfo.reason, "Tab has no muted info reason");
         browser.test.assertEq(false, tab.audible, "Tab is not audible");
       }
 
       windowId = tabs[0].windowId;
       tabIds = [tabs[1].id, tabs[2].id];
 
       browser.test.log("Test initial queries for muted and audible return no tabs");
       return Promise.all([
-        promiseTabs.query({ windowId, audible: false }),
-        promiseTabs.query({ windowId, audible: true }),
-        promiseTabs.query({ windowId, muted: true }),
-        promiseTabs.query({ windowId, muted: false }),
+        promiseTabs.query({windowId, audible: false}),
+        promiseTabs.query({windowId, audible: true}),
+        promiseTabs.query({windowId, muted: true}),
+        promiseTabs.query({windowId, muted: false}),
       ]);
     }).then(([silent, audible, muted, nonMuted]) => {
       browser.test.assertEq(3, silent.length, "Three silent tabs");
       browser.test.assertEq(0, audible.length, "No audible tabs");
 
       browser.test.assertEq(0, muted.length, "No muted tabs");
       browser.test.assertEq(3, nonMuted.length, "Three non-muted tabs");
 
@@ -90,20 +90,20 @@ add_task(function* () {
         browser.test.assertEq("user", obj.mutedInfo.reason, "Tab was muted by the user");
       }
 
       browser.test.assertEq(true, audible.changeInfo.audible, "Tab audible state changed");
       browser.test.assertEq(true, audible.tab.audible, "Tab is audible");
 
       browser.test.log("Re-check queries. Expect one audible and one muted tab");
       return Promise.all([
-        promiseTabs.query({ windowId, audible: false }),
-        promiseTabs.query({ windowId, audible: true }),
-        promiseTabs.query({ windowId, muted: true }),
-        promiseTabs.query({ windowId, muted: false }),
+        promiseTabs.query({windowId, audible: false}),
+        promiseTabs.query({windowId, audible: true}),
+        promiseTabs.query({windowId, muted: true}),
+        promiseTabs.query({windowId, muted: false}),
       ]);
     }).then(([silent, audible, muted, nonMuted]) => {
       browser.test.assertEq(2, silent.length, "Two silent tabs");
       browser.test.assertEq(1, audible.length, "One audible tab");
 
       browser.test.assertEq(1, muted.length, "One muted tab");
       browser.test.assertEq(2, nonMuted.length, "Two non-muted tabs");
 
@@ -111,18 +111,18 @@ add_task(function* () {
       browser.test.assertEq("user", muted[0].mutedInfo.reason, "Tab was muted by the user");
 
       browser.test.assertEq(true, audible[0].audible, "Tab is audible");
 
       browser.test.log("Toggle muted internally on two tabs, and check results");
       return Promise.all([
         promiseUpdated(tabIds[0], "mutedInfo"),
         promiseUpdated(tabIds[1], "mutedInfo"),
-        promiseTabs.update(tabIds[0], { muted: false }),
-        promiseTabs.update(tabIds[1], { muted: true }),
+        promiseTabs.update(tabIds[0], {muted: false}),
+        promiseTabs.update(tabIds[1], {muted: true}),
       ]);
     }).then(([unmuted, muted]) => {
       for (let obj of [unmuted.changeInfo, unmuted.tab]) {
         browser.test.assertEq(false, obj.mutedInfo.muted, "Tab is not muted");
       }
       for (let obj of [muted.changeInfo, muted.tab]) {
         browser.test.assertEq(true, obj.mutedInfo.muted, "Tab is muted");
       }
--- a/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_captureVisibleTab.js
@@ -34,38 +34,38 @@ function* runTest(options) {
         return new Promise(resolve => {
           browser.tabs[method](...args, resolve);
         });
       };
     });
 
     browser.test.log(`Test color ${options.color} at fullZoom=${options.fullZoom}`);
 
-    promiseTabs.query({ currentWindow: true, active: true }).then(([tab]) => {
+    promiseTabs.query({currentWindow: true, active: true}).then(([tab]) => {
       return Promise.all([
-        promiseTabs.captureVisibleTab(tab.windowId, { format: "jpeg", quality: 95 }),
-        promiseTabs.captureVisibleTab(tab.windowId, { format: "png", quality: 95 }),
-        promiseTabs.captureVisibleTab(tab.windowId, { quality: 95 }),
+        promiseTabs.captureVisibleTab(tab.windowId, {format: "jpeg", quality: 95}),
+        promiseTabs.captureVisibleTab(tab.windowId, {format: "png", quality: 95}),
+        promiseTabs.captureVisibleTab(tab.windowId, {quality: 95}),
         promiseTabs.captureVisibleTab(tab.windowId),
       ]).then(([jpeg, png, ...pngs]) => {
         browser.test.assertTrue(pngs.every(url => url == png), "All PNGs are identical");
 
         browser.test.assertTrue(jpeg.startsWith("data:image/jpeg;base64,"), "jpeg is JPEG");
         browser.test.assertTrue(png.startsWith("data:image/png;base64,"), "png is PNG");
 
         let promises = [jpeg, png].map(url => new Promise(resolve => {
           let img = new Image();
           img.src = url;
           img.onload = () => resolve(img);
         }));
         return Promise.all(promises);
       }).then(([jpeg, png]) => {
         let tabDims = `${tab.width}\u00d7${tab.height}`;
 
-        let images = { jpeg, png };
+        let images = {jpeg, png};
         for (let format of Object.keys(images)) {
           let img = images[format];
 
           let dims = `${img.width}\u00d7${img.height}`;
           browser.test.assertEq(tabDims, dims, `${format} dimensions are correct`);
 
           let canvas = document.createElement("canvas");
           canvas.width = img.width;
@@ -73,27 +73,27 @@ function* runTest(options) {
           canvas.mozOpaque = true;
 
           let ctx = canvas.getContext("2d");
           ctx.drawImage(img, 0, 0);
 
           // Check the colors of the first and last pixels of the image, to make
           // sure we capture the entire frame, and scale it correctly.
           let coords = [
-            { x: 0, y: 0,
-              color: options.color },
-            { x: img.width - 1,
-              y: img.height - 1,
-              color: options.color },
-            { x: img.width / 2 | 0,
-              y: img.height / 2 | 0,
-              color: options.neutral },
+            {x: 0, y: 0,
+             color: options.color},
+            {x: img.width - 1,
+             y: img.height - 1,
+             color: options.color},
+            {x: img.width / 2 | 0,
+             y: img.height / 2 | 0,
+             color: options.neutral},
           ];
 
-          for (let { x, y, color } of coords) {
+          for (let {x, y, color} of coords) {
             let imageData = ctx.getImageData(x, y, 1, 1).data;
 
             if (format == "png") {
               browser.test.assertEq(`rgba(${color},255)`, `rgba(${[...imageData]})`, `${format} image color is correct at (${x}, ${y})`);
             } else {
               // Allow for some deviation in JPEG version due to lossy compression.
               const SLOP = 2;
 
@@ -128,33 +128,33 @@ function* runTest(options) {
   yield extension.awaitFinish("captureVisibleTab");
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 }
 
 add_task(function* testCaptureVisibleTab() {
-  yield runTest({ color: [0, 0, 0], fullZoom: 1 });
+  yield runTest({color: [0, 0, 0], fullZoom: 1});
 
-  yield runTest({ color: [0, 0, 0], fullZoom: 2 });
+  yield runTest({color: [0, 0, 0], fullZoom: 2});
 
-  yield runTest({ color: [0, 0, 0], fullZoom: 0.5 });
+  yield runTest({color: [0, 0, 0], fullZoom: 0.5});
 
-  yield runTest({ color: [255, 255, 255], fullZoom: 1 });
+  yield runTest({color: [255, 255, 255], fullZoom: 1});
 });
 
 add_task(function* testCaptureVisibleTabPermissions() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function(x) {
-      browser.tabs.query({ currentWindow: true, active: true }, tab => {
+      browser.tabs.query({currentWindow: true, active: true}, tab => {
         browser.tabs.captureVisibleTab(tab.windowId).then(
           () => {
             browser.test.notifyFail("captureVisibleTabPermissions");
           },
           (e) => {
             browser.test.assertEq("The <all_urls> permission is required to use the captureVisibleTab API",
                                   e.message, "Expected permissions error message");
             browser.test.notifyPass("captureVisibleTabPermissions");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_create.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_create.js
@@ -14,17 +14,17 @@ add_task(function* () {
   registerCleanupFunction(() => {
     SpecialPowers.clearUserPref("browser.newtab.preload");
   });
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
 
-      "background": { "page": "bg/background.html" },
+      "background": {"page": "bg/background.html"},
     },
 
     files: {
       "bg/blank.html": `<html><head><meta charset="utf-8"></head></html>`,
 
       "bg/background.html": `<html><head>
         <meta charset="utf-8">
         <script src="background.js"></script>
@@ -40,58 +40,58 @@ add_task(function* () {
             windowId: activeWindow,
             active: true,
             pinned: false,
             url: "about:newtab",
           };
 
           let tests = [
             {
-              create: { url: "http://example.com/" },
-              result: { url: "http://example.com/" },
+              create: {url: "http://example.com/"},
+              result: {url: "http://example.com/"},
             },
             {
-              create: { url: "blank.html" },
-              result: { url: browser.runtime.getURL("bg/blank.html") },
+              create: {url: "blank.html"},
+              result: {url: browser.runtime.getURL("bg/blank.html")},
             },
             {
               create: {},
-              result: { url: "about:newtab" },
+              result: {url: "about:newtab"},
             },
             {
-              create: { active: false },
-              result: { active: false },
+              create: {active: false},
+              result: {active: false},
             },
             {
-              create: { active: true },
-              result: { active: true },
+              create: {active: true},
+              result: {active: true},
             },
             {
-              create: { pinned: true },
-              result: { pinned: true, index: 0 },
+              create: {pinned: true},
+              result: {pinned: true, index: 0},
             },
             {
-              create: { pinned: true, active: true },
-              result: { pinned: true, active: true, index: 0 },
+              create: {pinned: true, active: true},
+              result: {pinned: true, active: true, index: 0},
             },
             {
-              create: { pinned: true, active: false },
-              result: { pinned: true, active: false, index: 0 },
+              create: {pinned: true, active: false},
+              result: {pinned: true, active: false, index: 0},
             },
             {
-              create: { index: 1 },
-              result: { index: 1 },
+              create: {index: 1},
+              result: {index: 1},
             },
             {
-              create: { index: 1, active: false },
-              result: { index: 1, active: false },
+              create: {index: 1, active: false},
+              result: {index: 1, active: false},
             },
             {
-              create: { windowId: activeWindow },
-              result: { windowId: activeWindow },
+              create: {windowId: activeWindow},
+              result: {windowId: activeWindow},
             },
           ];
 
           function nextTest() {
             if (!tests.length) {
               browser.test.notifyPass("tabs.create");
               return;
             }
@@ -136,26 +136,26 @@ add_task(function* () {
               }
 
               return updatedPromise;
             }).then(url => {
               browser.test.assertEq(expected.url, url, `Expected value for tab.url`);
 
               return browser.tabs.remove(tabId);
             }).then(() => {
-              return browser.tabs.update(activeTab, { active: true });
+              return browser.tabs.update(activeTab, {active: true});
             }).then(() => {
               nextTest();
             });
           }
 
           nextTest();
         }
 
-        browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+        browser.tabs.query({active: true, currentWindow: true}, tabs => {
           activeTab = tabs[0].id;
           activeWindow = tabs[0].windowId;
 
           runTests();
         });
       },
     },
   });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -9,17 +9,17 @@ function* testHasNoPermission(params) {
     browser.runtime.onMessage.addListener((msg, sender) => {
       browser.test.assertEq(msg, "second script ran", "second script ran");
       browser.test.notifyPass("executeScript");
     });
 
     browser.test.onMessage.addListener(msg => {
       browser.test.assertEq(msg, "execute-script");
 
-      browser.tabs.query({ currentWindow: true }, tabs => {
+      browser.tabs.query({currentWindow: true}, tabs => {
         browser.tabs.executeScript({
           file: "script.js",
         });
 
         // Execute a script we know we have permissions for in the
         // second tab, in the hopes that it will execute after the
         // first one. This has intermittent failure written all over
         // it, but it's just about the best we can do until we
@@ -65,22 +65,22 @@ function* testHasNoPermission(params) {
 }
 
 add_task(function* testBadPermissions() {
   let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
   let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
 
   info("Test no special permissions");
   yield testHasNoPermission({
-    manifest: { "permissions": ["http://example.com/"] },
+    manifest: {"permissions": ["http://example.com/"]},
   });
 
   info("Test tabs permissions");
   yield testHasNoPermission({
-    manifest: { "permissions": ["http://example.com/", "tabs"] },
+    manifest: {"permissions": ["http://example.com/", "tabs"]},
   });
 
   info("Test active tab, browser action, no click");
   yield testHasNoPermission({
     manifest: {
       "permissions": ["http://example.com/", "activeTab"],
       "browser_action": {},
     },
@@ -89,31 +89,31 @@ add_task(function* testBadPermissions() 
   info("Test active tab, page action, no click");
   yield testHasNoPermission({
     manifest: {
       "permissions": ["http://example.com/", "activeTab"],
       "page_action": {},
     },
     contentSetup() {
       return new Promise(resolve => {
-        browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+        browser.tabs.query({active: true, currentWindow: true}, tabs => {
           browser.pageAction.show(tabs[0].id);
           resolve();
         });
       });
     },
   });
 
   yield BrowserTestUtils.removeTab(tab2);
   yield BrowserTestUtils.removeTab(tab1);
 });
 
 add_task(function* testBadURL() {
   function background() {
-    browser.tabs.query({ currentWindow: true }, tabs => {
+    browser.tabs.query({currentWindow: true}, tabs => {
       let promises = [
         new Promise(resolve => {
           browser.tabs.executeScript({
             file: "http://example.com/script.js",
           }, result => {
             browser.test.assertEq(undefined, result, "Result value");
 
             browser.test.assertTrue(browser.extension.lastError instanceof Error,
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
@@ -56,27 +56,27 @@ function* testHasPermission(params) {
   yield extension.unload();
 }
 
 add_task(function* testGoodPermissions() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
 
   info("Test explicit host permission");
   yield testHasPermission({
-    manifest: { "permissions": ["http://mochi.test/"] },
+    manifest: {"permissions": ["http://mochi.test/"]},
   });
 
   info("Test explicit host subdomain permission");
   yield testHasPermission({
-    manifest: { "permissions": ["http://*.mochi.test/"] },
+    manifest: {"permissions": ["http://*.mochi.test/"]},
   });
 
   info("Test explicit <all_urls> permission");
   yield testHasPermission({
-    manifest: { "permissions": ["<all_urls>"] },
+    manifest: {"permissions": ["<all_urls>"]},
   });
 
   info("Test activeTab permission with a browser action click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "browser_action": {},
     },
@@ -93,69 +93,69 @@ add_task(function* testGoodPermissions()
   info("Test activeTab permission with a page action click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": {},
     },
     contentSetup() {
       return new Promise(resolve => {
-        browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+        browser.tabs.query({active: true, currentWindow: true}, tabs => {
           browser.pageAction.show(tabs[0].id);
           resolve();
         });
       });
     },
     setup: clickPageAction,
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a browser action w/popup click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
-      "browser_action": { "default_popup": "_blank.html" },
+      "browser_action": {"default_popup": "_blank.html"},
     },
     setup: clickBrowserAction,
     tearDown: closeBrowserAction,
   });
 
   info("Test activeTab permission with a page action w/popup click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
-      "page_action": { "default_popup": "_blank.html" },
+      "page_action": {"default_popup": "_blank.html"},
     },
     contentSetup() {
       return new Promise(resolve => {
-        browser.tabs.query({ active: true, currentWindow: true }, tabs => {
+        browser.tabs.query({active: true, currentWindow: true}, tabs => {
           browser.pageAction.show(tabs[0].id);
           resolve();
         });
       });
     },
     setup: clickPageAction,
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a context menu click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab", "contextMenus"],
     },
     contentSetup() {
-      browser.contextMenus.create({ title: "activeTab", contexts: ["all"] });
+      browser.contextMenus.create({title: "activeTab", contexts: ["all"]});
       return Promise.resolve();
     },
     setup: function* (extension) {
       let contextMenu = document.getElementById("contentAreaContextMenu");
       let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
       let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
 
-      yield BrowserTestUtils.synthesizeMouseAtCenter("a[href]", { type: "contextmenu", button: 2 },
+      yield BrowserTestUtils.synthesizeMouseAtCenter("a[href]", {type: "contextmenu", button: 2},
                                                      gBrowser.selectedBrowser);
       yield awaitPopupShown;
 
       let item = contextMenu.querySelector("[label=activeTab]");
 
       yield EventUtils.synthesizeMouseAtCenter(item, {}, window);
 
       yield awaitPopupHidden;
--- a/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_getCurrent.js
@@ -2,41 +2,41 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* () {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
 
-      "browser_action": { "default_popup": "popup.html" },
+      "browser_action": {"default_popup": "popup.html"},
     },
 
     files: {
       "tab.js": function() {
         let url = document.location.href;
 
         browser.tabs.getCurrent(currentTab => {
           browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
 
           // Activate the tab.
-          browser.tabs.onActivated.addListener(function listener({ tabId }) {
+          browser.tabs.onActivated.addListener(function listener({tabId}) {
             if (tabId == currentTab.id) {
               browser.tabs.onActivated.removeListener(listener);
 
               browser.tabs.getCurrent(currentTab => {
                 browser.test.assertEq(currentTab.id, tabId, "in active background tab");
                 browser.test.assertEq(currentTab.url, url, "getCurrent in non-active background tab");
 
                 browser.test.sendMessage("tab-finished");
                 browser.tabs.remove(tabId);
               });
             }
           });
-          browser.tabs.update(currentTab.id, { active: true });
+          browser.tabs.update(currentTab.id, {active: true});
         });
       },
 
       "popup.js": function() {
         browser.tabs.getCurrent(tab => {
           browser.test.assertEq(tab, undefined, "getCurrent in popup script");
           browser.test.sendMessage("popup-finished");
         });
@@ -47,17 +47,17 @@ add_task(function* () {
     },
 
     background: function() {
       browser.tabs.getCurrent(tab => {
         browser.test.assertEq(tab, undefined, "getCurrent in background script");
         browser.test.sendMessage("background-finished");
       });
 
-      browser.tabs.create({ url: "tab.html", active: false });
+      browser.tabs.create({url: "tab.html", active: false});
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitMessage("background-finished");
   yield extension.awaitMessage("tab-finished");
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
@@ -57,17 +57,17 @@ add_task(function* testExecuteScript() {
     }
 
     function next() {
       if (!promises.length) {
         browser.test.notifyPass("insertCSS");
         return;
       }
 
-      let { promise, background, foreground } = promises.shift();
+      let {promise, background, foreground} = promises.shift();
       new Promise(promise).then(() => {
         browser.tabs.executeScript({
           code: `(${checkCSS})()`,
         }, result => {
           browser.test.assertEq(background, result[0], "Expected background color");
           browser.test.assertEq(foreground, result[1], "Expected foreground color");
           next();
         });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_move.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move.js
@@ -15,17 +15,17 @@ add_task(function* () {
 
     background: function() {
       browser.tabs.query({
         lastFocusedWindow: true,
       }, function(tabs) {
         let tab = tabs[0];
         browser.tabs.move(tab.id, {index: 0});
         browser.tabs.query(
-          { lastFocusedWindow: true },
+          {lastFocusedWindow: true},
           tabs => {
             browser.test.assertEq(tabs[0].url, tab.url, "should be first tab");
             browser.test.notifyPass("tabs.move.single");
           });
       });
     },
   });
 
@@ -35,22 +35,22 @@ add_task(function* () {
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
       browser.tabs.query(
-        { lastFocusedWindow: true },
+        {lastFocusedWindow: true},
         tabs => {
           tabs.sort(function(a, b) { return a.url > b.url; });
           browser.tabs.move(tabs.map(tab => tab.id), {index: 0});
           browser.tabs.query(
-            { lastFocusedWindow: true },
+            {lastFocusedWindow: true},
             tabs => {
               browser.test.assertEq(tabs[0].url, "about:blank", "should be first tab");
               browser.test.assertEq(tabs[1].url, "about:config", "should be second tab");
               browser.test.assertEq(tabs[2].url, "about:robots", "should be third tab");
               browser.test.notifyPass("tabs.move.multiple");
             });
         });
     },
@@ -62,23 +62,23 @@ add_task(function* () {
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
       browser.tabs.query(
-        { lastFocusedWindow: true },
+        {lastFocusedWindow: true},
         tabs => {
           let tab = tabs[0];
           // Assuming that tab.id of 12345 does not exist.
           browser.tabs.move([12345, tab.id], {index: 0});
           browser.tabs.query(
-            { lastFocusedWindow: true },
+            {lastFocusedWindow: true},
             tabs => {
               browser.test.assertEq(tabs[0].url, tab.url, "should be first tab");
               browser.test.notifyPass("tabs.move.invalid");
             });
         });
     },
   });
 
@@ -88,22 +88,22 @@ add_task(function* () {
 
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
       browser.tabs.query(
-        { lastFocusedWindow: true },
+        {lastFocusedWindow: true},
         tabs => {
           let tab = tabs[0];
           browser.tabs.move(tab.id, {index: -1});
           browser.tabs.query(
-            { lastFocusedWindow: true },
+            {lastFocusedWindow: true},
             tabs => {
               browser.test.assertEq(tabs[2].url, tab.url, "should be last tab");
               browser.test.notifyPass("tabs.move.last");
             });
         });
     },
   });
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_move_window.js
@@ -16,17 +16,17 @@ add_task(function* () {
       browser.tabs.query({
         url: "<all_urls>",
       }, function(tabs) {
         let destination = tabs[0];
         let source = tabs[1]; // skip over about:blank in window1
         browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
 
         browser.tabs.query(
-          { url: "<all_urls>" },
+          {url: "<all_urls>"},
           tabs => {
             browser.test.assertEq(tabs[0].url, "http://example.com/");
             browser.test.assertEq(tabs[0].windowId, destination.windowId);
             browser.test.notifyPass("tabs.move.window");
           });
       });
     },
   });
@@ -49,24 +49,24 @@ add_task(function* () {
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
       browser.tabs.query(
-        { url: "<all_urls>" },
+        {url: "<all_urls>"},
         tabs => {
           let destination = tabs[0];
           let source = tabs[1]; // remember, pinning moves it to the left.
           browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
 
           browser.tabs.query(
-            { url: "<all_urls>" },
+            {url: "<all_urls>"},
             tabs => {
               browser.test.assertEq(true, tabs[0].pinned);
               browser.test.notifyPass("tabs.move.pin");
             });
         });
     },
   });
 
@@ -89,23 +89,23 @@ add_task(function* () {
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
       browser.tabs.query(
-        { url: "<all_urls>" },
+        {url: "<all_urls>"},
         tabs => {
           let move1 = tabs[1];
           let move3 = tabs[3];
           browser.tabs.move([move1.id, move3.id], {index: 0});
           browser.tabs.query(
-            { url: "<all_urls>" },
+            {url: "<all_urls>"},
             tabs => {
               browser.test.assertEq(tabs[0].url, move1.url);
               browser.test.assertEq(tabs[2].url, move3.url);
               browser.test.notifyPass("tabs.move.multiple");
             });
         });
     },
   });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onUpdated.js
@@ -18,19 +18,19 @@ add_task(function* () {
         "run_at": "document_start",
       }],
     },
 
     background: function() {
       let pageURL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/context_tabs_onUpdated_page.html";
 
       let expectedSequence = [
-        { status: "loading" },
-        { status: "loading", url: pageURL },
-        { status: "complete" },
+        {status: "loading"},
+        {status: "loading", url: pageURL},
+        {status: "complete"},
       ];
       let collectedSequence = [];
 
       browser.tabs.onUpdated.addListener(function(tabId, updatedInfo) {
         collectedSequence.push(updatedInfo);
       });
 
       browser.runtime.onMessage.addListener(function() {
@@ -55,17 +55,17 @@ add_task(function* () {
               );
             }
           }
         }
 
         browser.test.notifyPass("tabs.onUpdated");
       });
 
-      browser.tabs.create({ url: pageURL });
+      browser.tabs.create({url: pageURL});
     },
     files: {
       "content-script.js": `
         window.addEventListener("message", function(evt) {
           if (evt.data == "frame-updated") {
             browser.runtime.sendMessage("load-completed");
           }
         }, true);
--- a/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_sendMessage.js
@@ -4,17 +4,17 @@
 
 add_task(function* tabsSendMessageNoExceptionOnNonExistentTab() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     background: function() {
-      browser.tabs.create({ url: "about:robots"}, tab => {
+      browser.tabs.create({url: "about:robots"}, tab => {
         let exception;
         try {
           browser.tabs.sendMessage(tab.id, "message");
           browser.tabs.sendMessage(tab.id + 100, "message");
         } catch (e) {
           exception = e;
         }
 
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
@@ -3,25 +3,25 @@
 "use strict";
 
 add_task(function* testWebNavigationGetNonExistentTab() {
   let extension = ExtensionTestUtils.loadExtension({
     background: "(" + function() {
       let results = [
         // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
         // starts from 1.
-        browser.webNavigation.getAllFrames({ tabId: 0 }).then(() => {
+        browser.webNavigation.getAllFrames({tabId: 0}).then(() => {
           browser.test.fail("getAllFrames Promise should be rejected on error");
         }, (error) => {
           browser.test.assertEq("No tab found with tabId: 0", error.message,
                                 "getAllFrames rejected Promise should pass the expected error");
         }),
         // There is no "tabId = 0" because the id assigned by TabManager (defined in ext-utils.js)
         // starts from 1, processId is currently marked as optional and it is ignored.
-        browser.webNavigation.getFrame({ tabId: 0, frameId: 15, processId: 20 }).then(() => {
+        browser.webNavigation.getFrame({tabId: 0, frameId: 15, processId: 20}).then(() => {
           browser.test.fail("getFrame Promise should be rejected on error");
         }, (error) => {
           browser.test.assertEq("No tab found with tabId: 0", error.message,
                                 "getFrame rejected Promise should pass the expected error");
         }),
       ];
 
       Promise.all(results).then(() => {
@@ -52,21 +52,21 @@ add_task(function* testWebNavigationFram
       browser.webNavigation.onCompleted.addListener((details) => {
         collectedDetails.push(details);
 
         if (details.frameId !== 0) {
           // wait for the top level iframe to be complete
           return;
         }
 
-        browser.webNavigation.getAllFrames({ tabId }).then((getAllFramesDetails) => {
+        browser.webNavigation.getAllFrames({tabId}).then((getAllFramesDetails) => {
           let getFramePromises = getAllFramesDetails.map((frameDetail) => {
-            let { frameId } = frameDetail;
+            let {frameId} = frameDetail;
             // processId is currently marked as optional and it is ignored.
-            return browser.webNavigation.getFrame({ tabId, frameId, processId: 0 });
+            return browser.webNavigation.getFrame({tabId, frameId, processId: 0});
           });
 
           Promise.all(getFramePromises).then((getFrameResults) => {
             browser.test.sendMessage("webNavigationFrames.done", {
               collectedDetails, getAllFramesDetails, getFrameResults,
             });
           }, () => {
             browser.test.assertTrue(false, "None of the getFrame promises should have been rejected");
@@ -76,29 +76,29 @@ add_task(function* testWebNavigationFram
           let nonExistentFrameId = Math.floor(Math.random() * 10000);
 
           // Increment the picked random nonExistentFrameId until it doesn't exists.
           while (getAllFramesDetails.filter((details) => details.frameId == nonExistentFrameId).length > 0) {
             nonExistentFrameId += 1;
           }
 
           // Check that getFrame Promise is rejected with the expected error message on nonexistent frameId.
-          browser.webNavigation.getFrame({ tabId, frameId: nonExistentFrameId, processId: 20 }).then(() => {
+          browser.webNavigation.getFrame({tabId, frameId: nonExistentFrameId, processId: 20}).then(() => {
             browser.test.fail("getFrame promise should be rejected for an unexistent frameId");
           }, (error) => {
             browser.test.assertEq(`No frame found with frameId: ${nonExistentFrameId}`, error.message,
                                   "getFrame promise should be rejected with the expected error message on unexistent frameId");
           }).then(() => {
             browser.tabs.remove(tabId);
             browser.test.sendMessage("webNavigationFrames.done");
           });
         });
       });
 
-      browser.tabs.create({ url: "tab.html" }, (tab) => {
+      browser.tabs.create({url: "tab.html"}, (tab) => {
         tabId = tab.id;
       });
     } + ")();",
     manifest: {
       permissions: ["webNavigation", "tabs"],
     },
     files: {
       "tab.html": `
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -16,16 +16,18 @@ support-files =
 [browser_426329.js]
 [browser_483086.js]
 [browser_addEngine.js]
 [browser_amazon.js]
 [browser_amazon_behavior.js]
 [browser_bing.js]
 [browser_bing_behavior.js]
 [browser_contextmenu.js]
+[browser_contextSearchTabPosition.js]
+skip-if = os == "mac" # bug 967013
 [browser_eBay.js]
 [browser_eBay_behavior.js]
 [browser_google.js]
 [browser_google_behavior.js]
 [browser_healthreport.js]
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffHeader.js]
rename from browser/base/content/test/general/browser_contextSearchTabPosition.js
rename to browser/components/search/test/browser_contextSearchTabPosition.js
--- a/browser/base/content/test/general/browser_contextSearchTabPosition.js
+++ b/browser/components/search/test/browser_contextSearchTabPosition.js
@@ -1,30 +1,28 @@
 /* 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/. */
 
 add_task(function* test() {
+  let engine = yield promiseNewEngine("testEngine.xml");
+  let histogramKey = "other-" + engine.name + ".contextmenu";
+  let numSearchesBefore = 0;
 
-  // Will need to be changed if Google isn't the default search engine.
-  // Note: geoSpecificDefaults are disabled for mochitests, so this is the
-  // non-US en-US default.
-  let histogramKey = "google.contextmenu";
-  let numSearchesBefore = 0;
   try {
     let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
     if (histogramKey in hs) {
       numSearchesBefore = hs[histogramKey].sum;
     }
   } catch (ex) {
     // No searches performed yet, not a problem, |numSearchesBefore| is 0.
   }
 
   let tabs = [];
-  let tabsLoadedDeferred = Promise.defer();
+  let tabsLoadedDeferred = new Deferred();
 
   function tabAdded(event) {
     let tab = event.target;
     tabs.push(tab);
 
     // We wait for the blank tab and the two context searches tabs to open.
     if (tabs.length == 3) {
       tabsLoadedDeferred.resolve();
@@ -49,8 +47,15 @@ add_task(function* test() {
   tabs.forEach(gBrowser.removeTab, gBrowser);
 
   // Make sure that the context searches are correctly recorded.
   let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
   Assert.ok(histogramKey in hs, "The histogram must contain the correct key");
   Assert.equal(hs[histogramKey].sum, numSearchesBefore + 2,
                "The histogram must contain the correct search count");
 });
+
+function Deferred() {
+  this.promise = new Promise((resolve, reject) => {
+    this.resolve = resolve;
+    this.reject = reject;
+  });
+}
--- a/browser/components/sessionstore/SessionSaver.jsm
+++ b/browser/components/sessionstore/SessionSaver.jsm
@@ -16,16 +16,18 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
   "resource:///modules/sessionstore/PrivacyFilter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RunState",
+  "resource:///modules/sessionstore/RunState.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   "resource:///modules/sessionstore/SessionFile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 // Minimal interval between two save operations (in milliseconds).
@@ -204,16 +206,29 @@ var SessionSaverInternal = {
           break;
         }
 
         delete state._closedWindows[i]._shouldRestore;
         state.windows.unshift(state._closedWindows.pop());
       }
     }
 
+    // If this is the final write on a clean shutdown, and the user changed
+    // their cookie preferences to "Keep until I close Firefox", then we
+    // should remove all cookies. Check "resume_session_once" so we keep
+    // cookies when restarting due to a Firefox update.
+    if (RunState.isClosing &&
+        Services.prefs.getIntPref("network.cookie.lifetimePolicy") ==
+          Services.cookies.ACCEPT_SESSION &&
+        !Services.prefs.getBoolPref("browser.sessionstore.resume_session_once")) {
+      for (let window of state.windows) {
+        delete window.cookies;
+      }
+    }
+
     stopWatchFinish("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
     return this._writeState(state);
   },
 
   /**
    * Saves the current session state. Collects data asynchronously and calls
    * _saveState() to collect data again (with a cache hit rate of hopefully
    * 100%) and write to disk afterwards.
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -1231,19 +1231,17 @@ var SessionStoreInternal = {
     aWindow.dispatchEvent(event);
 
     if (this.windowToFocus && this.windowToFocus == aWindow) {
       delete this.windowToFocus;
     }
 
     var tabbrowser = aWindow.gBrowser;
 
-    // The tabbrowser binding will go away once the window is closed,
-    // so we'll hold a reference to the browsers in the closure here.
-    let browsers = tabbrowser.browsers;
+    let browsers = Array.from(tabbrowser.browsers);
 
     TAB_EVENTS.forEach(function(aEvent) {
       tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
     }, this);
 
     aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
 
     let winData = this._windows[aWindow.__SSi];
@@ -1315,17 +1313,16 @@ var SessionStoreInternal = {
         this.maybeSaveClosedWindow(winData, isLastWindow);
       }
 
       TabStateFlusher.flushWindow(aWindow).then(() => {
         // At this point, aWindow is closed! You should probably not try to
         // access any DOM elements from aWindow within this callback unless
         // you're holding on to them in the closure.
 
-        // We can still access tabbrowser.browsers, thankfully.
         for (let browser of browsers) {
           if (this._closedWindowTabs.has(browser.permanentKey)) {
             let tabData = this._closedWindowTabs.get(browser.permanentKey);
             TabState.copyFromCache(browser, tabData);
             this._closedWindowTabs.delete(browser.permanentKey);
           }
         }
 
--- a/browser/components/test/browser.ini
+++ b/browser/components/test/browser.ini
@@ -1,4 +1,3 @@
 [DEFAULT]
 
 [browser_bug538331.js]
-skip-if = e10s # Bug ?????? - child process crash, but only when run as part of the suite (ie, probably not actually this tests fault!?)
--- a/browser/components/test/browser_bug538331.js
+++ b/browser/components/test/browser_bug538331.js
@@ -356,21 +356,18 @@ function testShowNotification()
                "provided by the update");
           }
         }
         // The last test opens an url and verifies the url from the updates.xml
         // is correct.
         if (i == (BG_NOTIFY_TESTS.length - 1)) {
           // Wait for any windows caught by the windowcatcher to close
           gWindowCatcher.finish(function () {
+            BrowserTestUtils.waitForNewTab(gBrowser).then(testNotificationURL);
             button.click();
-            gBrowser.selectedBrowser.addEventListener("load", function () {
-              gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-              testNotificationURL();
-            }, true);
           });
         } else {
           notifyBox.removeAllNotifications(true);
         }
       } else if (i == (BG_NOTIFY_TESTS.length - 1)) {
         // If updateBox is null the test has already reported errors so bail
         finish_test();
       }
@@ -384,17 +381,17 @@ function testShowNotification()
   }
 }
 
 // Test opening the url provided by the updates.xml in the last test
 function testNotificationURL()
 {
   ok(true, "Test testNotificationURL: clicking the notification button " +
            "opened the url specified by the update");
-  let href = gBrowser.selectedBrowser.contentWindow.location.href;
+  let href = gBrowser.currentURI.spec;
   let expectedURL = BG_NOTIFY_TESTS[BG_NOTIFY_TESTS.length - 1].notificationURL;
   is(href, expectedURL, "The url opened from the notification should be the " +
      "url provided by the update");
   gBrowser.removeCurrentTab();
   window.focus();
   finish_test();
 }
 
--- a/build.gradle
+++ b/build.gradle
@@ -4,26 +4,29 @@ allprojects {
     // Expose the per-object-directory configuration to all projects.
     ext {
         mozconfig = gradle.mozconfig
         topsrcdir = gradle.mozconfig.topsrcdir
         topobjdir = gradle.mozconfig.topobjdir
     }
 
     repositories {
-        jcenter()
+        maven {
+            url gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORY
+        }
     }
 }
 
 buildDir "${topobjdir}/gradle/build"
 
 buildscript {
     repositories {
-        jcenter()
-
+        maven {
+            url gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORY
+        }
     }
 
     dependencies {
         classpath 'com.android.tools.build:gradle:1.3.0'
         classpath('com.stanfy.spoon:spoon-gradle-plugin:1.0.4') {
             // Without these, we get errors linting.
             exclude module: 'guava'
         }
--- a/configure.in
+++ b/configure.in
@@ -5247,22 +5247,66 @@ if test -n "$MOZ_OMX_PLUGIN"; then
         AC_DEFINE(MOZ_OMX_PLUGIN)
     else
         dnl fail if we're not building on Gonk or Android
         AC_MSG_ERROR([OMX media plugin can only be built on B2G or Android])
     fi
 fi
 
 dnl ========================================================
-dnl = Enable building mobile/android with Gradle
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(gradle-mobile-android-builds,
-[  --enable-gradle-mobile-android-builds      Enable building mobile/android with Gradle],
-    MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=1,
-    MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=)
+dnl Gradle support
+dnl
+dnl If --with-gradle is specified, build mobile/android with Gradle.
+dnl
+dnl If no Gradle binary is specified, use the in tree Gradle wrapper.
+dnl The wrapper downloads and installs Gradle, which is good for local
+dnl developers but not good in automation.
+dnl ========================================================
+
+GRADLE=
+MOZ_ARG_WITH_STRING(gradle,
+[  --with-gradle=/path/to/bin/gradle
+                          Enable building mobile/android with Gradle (argument: location of binary or wrapper (gradle/gradlew))],
+    if test "$withval" = "no" ; then
+        dnl --without-gradle => use the wrapper in |mach gradle|, don't build
+        dnl with Gradle by default.
+        GRADLE=$srcdir/gradlew
+        MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=
+    elif test "$withval" = "yes" ; then
+        dnl --with-gradle => use the wrapper in |mach gradle|, build with
+        dnl Gradle by default.
+        GRADLE=$srcdir/gradlew
+        MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=1
+    else
+        dnl --with-gradle=/path/to/gradle => use the given binary in |mach
+        dnl gradle|, build with Gradle by default.
+        GRADLE=$withval
+        MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=1
+    fi
+    ,
+    dnl No --with{out}-gradle => use the wrapper in |mach gradle|, don't build
+    dnl with Gradle by default.
+    GRADLE=$srcdir/gradlew
+    MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE=
+    )
+
+if test "$OS_TARGET" = Android -a x"$MOZ_WIDGET_TOOLKIT" != x"gonk" ; then
+    if test -z "$GRADLE" -o ! -x "$GRADLE" ; then
+        AC_MSG_ERROR([The program gradlew/gradle was not found.  Use --with-gradle=/path/to/bin/gradle}])
+    fi
+fi
+AC_SUBST(GRADLE)
+
+dnl Path to Maven repository containing Gradle dependencies.  Automation will
+dnl set this to file:///path/to/local via the mozconfig.  Local developer
+dnl default is jcenter.
+if test -z "$GRADLE_MAVEN_REPOSITORY" ; then
+    GRADLE_MAVEN_REPOSITORY=https://jcenter.bintray.com/
+fi
+AC_SUBST(GRADLE_MAVEN_REPOSITORY)
 
 if test -n "$MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE"; then
     if test "$OS_TARGET" = "Android" -a x"$MOZ_WIDGET_TOOLKIT" != x"gonk"; then
         dnl Only allow building mobile/android with Gradle.
         AC_DEFINE(MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE)
     else
         dnl fail if we're not building mobile/android.
         AC_MSG_ERROR([Can only build mobile/android with Gradle])
--- a/devtools/bootstrap.js
+++ b/devtools/bootstrap.js
@@ -74,21 +74,87 @@ function reload(event) {
   }
   dump("Reload DevTools.  (reload-toolbox:"+reloadToolbox+")\n");
 
   // Invalidate xul cache in order to see changes made to chrome:// files
   Services.obs.notifyObservers(null, "startupcache-invalidate", null);
 
   // Ask the loader to update itself and reopen the toolbox if needed
   const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-  devtools.reload(reloadToolbox);
+  devtools.reload();
+
+  // Go over all top level windows to reload all devtools related things
+  let windowsEnum = Services.wm.getEnumerator(null);
+  while (windowsEnum.hasMoreElements()) {
+    let window = windowsEnum.getNext();
+    let windowtype = window.document.documentElement.getAttribute("windowtype");
+    if (windowtype == "navigator:browser" && window.gBrowser) {
+      // Enumerate tabs on firefox windows
+      for (let tab of window.gBrowser.tabs) {
+        let browser = tab.linkedBrowser;
+        let location = browser.documentURI.spec;
+        let mm = browser.messageManager;
+        // To reload JSON-View tabs and any devtools document
+        if (location.startsWith("about:debugging") ||
+            location.startsWith("chrome://devtools/")) {
+          browser.reload();
+        }
+        // We have to use a frame script to query "baseURI"
+        mm.loadFrameScript("data:text/javascript,new " + function () {
+          let isJSONView = content.document.baseURI.startsWith("resource://devtools/");
+          if (isJSONView) {
+            content.location.reload();
+          }
+        }, false);
+      }
 
-  // Also tells gDevTools to reload its dependencies
-  const {gDevTools} = devtools.require("devtools/client/framework/devtools");
-  gDevTools.reload();
+      // Manually reload gcli if it has been used
+      // Bug 1248348: Inject the developer toolbar dynamically within browser/
+      // so that we can easily remove/reinject it
+      const desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
+      if (desc && !desc.get) {
+        let wasVisible = window.DeveloperToolbar.visible;
+        window.DeveloperToolbar.hide()
+          .then(() => {
+            window.DeveloperToolbar.destroy();
+
+            let { DeveloperToolbar } = devtools.require("devtools/client/shared/developer-toolbar");
+            window.DeveloperToolbar = new DeveloperToolbar(window, window.document.getElementById("developer-toolbar"));
+            if (wasVisible) {
+              window.DeveloperToolbar.show();
+            }
+          });
+      }
+    } else if (windowtype === "devtools:webide") {
+      window.location.reload();
+    } else if (windowtype === "devtools:webconsole") {
+      // Browser console document can't just be reloaded.
+      // HUDService is going to close it on unload.
+      // Instead we have to manually toggle it.
+      let HUDService = devtools.require("devtools/client/webconsole/hudservice");
+      HUDService.toggleBrowserConsole()
+        .then(() => {
+          HUDService.toggleBrowserConsole();
+        });
+    }
+  }
+
+  if (reloadToolbox) {
+    // Reopen the toolbox automatically if we are reloading from toolbox shortcut
+    // and are on a browser window.
+    // Wait for a second before opening the toolbox to avoid races
+    // between the old and the new one.
+    let {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
+    setTimeout(() => {
+      let { TargetFactory } = devtools.require("devtools/client/framework/target");
+      let { gDevTools } = devtools.require("devtools/client/framework/devtools");
+      let target = TargetFactory.forTab(top.gBrowser.selectedTab);
+      gDevTools.showToolbox(target);
+    }, 1000);
+  }
 }
 
 let listener;
 function startup() {
   dump("DevTools addon started.\n");
   listener = new MultiWindowKeyListener({
     keyCode: Ci.nsIDOMKeyEvent.DOM_VK_R, ctrlKey: true, altKey: true,
     callback: reload
--- a/devtools/client/animationinspector/animation-inspector.xhtml
+++ b/devtools/client/animationinspector/animation-inspector.xhtml
@@ -5,17 +5,16 @@
 <!DOCTYPE html [
 <!ENTITY % animationinspectorDTD SYSTEM "chrome://devtools/locale/animationinspector.dtd" >
  %animationinspectorDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>&animationInspectorTitle;</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/animationinspector.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   </head>
   <body class="theme-sidebar devtools-monospace" role="application" empty="true">
     <div id="global-toolbar" class="theme-toolbar">
       <span class="label">&allAnimations;</span>
       <button id="toggle-all" standalone="true" class="devtools-button pause-button"></button>
     </div>
--- a/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
@@ -60,18 +60,20 @@ add_task(function*() {
      "The button can be paused after the scrubber has moved out of bounds");
   yield assertScrubberMoving(panel, false);
 
   // For a finite animation though, once the scrubber reaches the end of the
   // timeline, it should go back to paused mode.
   info("Select a finite animation, reload the page and wait for the " +
        "animation to complete");
   yield selectNode(".negative-delay", inspector);
+
+  let onScrubberStopped = waitForScrubberStopped(timeline);
   yield reloadTab(inspector);
-  yield waitForScrubberStopped(timeline);
+  yield onScrubberStopped;
 
   ok(btn.classList.contains("paused"),
      "The button is in paused state once finite animations are done");
   yield assertScrubberMoving(panel, false);
 
   info("Click again on the button to play the animation from the start again");
   yield clickTimelinePlayPauseButton(panel);
 
--- a/devtools/client/canvasdebugger/canvasdebugger.xul
+++ b/devtools/client/canvasdebugger/canvasdebugger.xul
@@ -1,15 +1,14 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/canvasdebugger.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % canvasDebuggerDTD SYSTEM "chrome://devtools/locale/canvasdebugger.dtd">
   %canvasDebuggerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
--- a/devtools/client/debugger/debugger.xul
+++ b/devtools/client/debugger/debugger.xul
@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="debugger.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/debugger.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % debuggerDTD SYSTEM "chrome://devtools/locale/debugger.dtd">
   %debuggerDTD;
 ]>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
--- a/devtools/client/eyedropper/eyedropper.xul
+++ b/devtools/client/eyedropper/eyedropper.xul
@@ -1,17 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!-- 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/. -->
 
 <!DOCTYPE window []>
 
-<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
 <?xml-stylesheet href="chrome://devtools/skin/eyedropper.css" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         no-theme="true">
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"/>
   <commandset id="eyedropper-commandset">
     <command id="eyedropper-cmd-close"
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -1,13 +1,14 @@
 /* -*- 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 const DBG_XUL = "chrome://devtools/content/framework/toolbox-process-window.xul";
 const CHROME_DEBUGGER_PROFILE_NAME = "chrome_debugger_profile";
 
 Cu.import("resource://gre/modules/Services.jsm");
@@ -193,16 +194,18 @@ BrowserToolboxProcess.prototype = {
     dumpn("Initializing chrome debugging process.");
     let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
     process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
 
     let xulURI = DBG_XUL;
 
     if (this._options.addonID) {
       xulURI += "?addonID=" + this._options.addonID;
+    } else if (this._options.testScript) {
+      xulURI += "?testScript=" + encodeURIComponent(this._options.testScript);
     }
 
     dumpn("Running chrome debugging process.");
     let args = ["-no-remote", "-foreground", "-profile", this._dbgProfilePath, "-chrome", xulURI];
 
     // During local development, incremental builds can trigger the main process
     // to clear its startup cache with the "flag file" .purgecaches, but this
     // file is removed during app startup time, so we aren't able to know if it
--- a/devtools/client/framework/attach-thread.js
+++ b/devtools/client/framework/attach-thread.js
@@ -1,8 +1,14 @@
+/* -*- 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/. */
+
 const {Cc, Ci, Cu} = require("chrome");
 const Services = Cu.import("resource://gre/modules/Services.jsm", {}).Services;
 const promise = require("promise");
 
 function l10n(name) {
   const bundle = Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
   try {
     return bundle.GetStringFromName(name);
--- a/devtools/client/framework/gDevTools.jsm
+++ b/devtools/client/framework/gDevTools.jsm
@@ -1,8 +1,10 @@
+/* -*- 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";
 
 /**
  * This JSM is here to keep some compatibility with existing add-ons.
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -1,8 +1,10 @@
+/* -*- 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 { Ci, Cu } = require("chrome");
 const promise = require("promise");
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -12,16 +12,17 @@ support-files =
   shared-redux-head.js
   helper_disable_cache.js
   doc_theme.css
   doc_viewsource.html
   browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
   browser_toolbox_options_enable_serviceworkers_testing.html
   serviceworker.js
 
+[browser_browser_toolbox.js]
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
 [browser_keybindings_03.js]
 [browser_new_activation_workflow.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/browser_browser_toolbox.js
@@ -0,0 +1,63 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// On debug test slave, it takes about 50s to run the test.
+requestLongerTimeout(4);
+
+add_task(function* runTest() {
+  yield new Promise(done => {
+    let options = {"set": [
+      ["devtools.debugger.prompt-connection", false],
+      ["devtools.debugger.remote-enabled", true],
+      ["devtools.chrome.enabled", true],
+      // Test-only pref to allow passing `testScript` argument to the browser
+      // toolbox
+      ["devtools.browser-toolbox.allow-unsafe-script", true],
+      // On debug test slave, it takes more than the default time (20s)
+      // to get a initialized console
+      ["devtools.debugger.remote-timeout", 120000]
+    ]};
+    SpecialPowers.pushPrefEnv(options, done);
+  });
+
+  // Wait for a notification sent by a script evaluated in the webconsole
+  // of the browser toolbox.
+  let onCustomMessage = new Promise(done => {
+    Services.obs.addObserver(function listener() {
+      Services.obs.removeObserver(listener, "browser-toolbox-console-works");
+      done();
+    }, "browser-toolbox-console-works", false);
+  });
+
+  let { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
+  let closePromise;
+  yield new Promise(onRun => {
+    let options = {
+      // Pass a test script evaluated in the browser toolbox window
+      // living in a distinct process. It has access to `toolbox` object
+      // in its global scope.
+      testScript: "new " + function () {
+        toolbox.selectTool("webconsole")
+          .then(() => toolbox.getPanel("webconsole"))
+          .then(() => {
+            let { jsterm } = toolbox.getPanel("webconsole").hud;
+            let js = "Services.obs.notifyObservers(null, 'browser-toolbox-console-works', null);";
+            return jsterm.execute(js);
+          })
+          .then(() => toolbox.destroy());
+      }
+    };
+    closePromise = new Promise(onClose => {
+      info("Opening the browser toolbox\n");
+      BrowserToolboxProcess.init(onClose, onRun, options);
+    });
+  });
+  ok(true, "Browser toolbox started\n");
+
+  yield onCustomMessage;
+  ok(true, "Received the custom message");
+
+  yield closePromise;
+  ok(true, "Browser toolbox process just closed");
+});
--- a/devtools/client/framework/test/browser_devtools_api.js
+++ b/devtools/client/framework/test/browser_devtools_api.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejections should be fixed.
 //
--- a/devtools/client/framework/test/browser_devtools_api_destroy.js
+++ b/devtools/client/framework/test/browser_devtools_api_destroy.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests devtools API
 
 function test() {
   addTab("about:blank").then(runTests);
 }
--- a/devtools/client/framework/test/browser_dynamic_tool_enabling.js
+++ b/devtools/client/framework/test/browser_dynamic_tool_enabling.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests that toggling prefs immediately (de)activates the relevant menuitem
 
 var gItemsToTest = {
   "menu_devToolbar": "devtools.toolbar.enabled",
   "menu_browserToolbox": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled"],
--- a/devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
+++ b/devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
@@ -1,10 +1,13 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 // Test that network requests originating from the toolbox don't get recorded in
 // the network panel.
 
 add_task(function*() {
   // TODO: This test tries to verify the normal behavior of the netmonitor and
   // therefore needs to avoid the explicit check for tests. Bug 1167188 will
--- a/devtools/client/framework/test/browser_keybindings_01.js
+++ b/devtools/client/framework/test/browser_keybindings_01.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the keybindings for opening and closing the inspector work as expected
 // Can probably make this a shared test that tests all of the tools global keybindings
 
 function test()
 {
   waitForExplicitFinish();
 
--- a/devtools/client/framework/test/browser_keybindings_02.js
+++ b/devtools/client/framework/test/browser_keybindings_02.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 "use strict";
 
 // Test that the toolbox keybindings still work after the host is changed.
 
 const URL = "data:text/html;charset=utf8,test page";
--- a/devtools/client/framework/test/browser_keybindings_03.js
+++ b/devtools/client/framework/test/browser_keybindings_03.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 "use strict";
 
 // Test that the toolbox 'switch to previous host' feature works.
 // Pressing ctrl/cmd+shift+d should switch to the last used host.
 
--- a/devtools/client/framework/test/browser_new_activation_workflow.js
+++ b/devtools/client/framework/test/browser_new_activation_workflow.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests devtools API
 
 var toolbox, target;
 
 var tempScope = {};
--- a/devtools/client/framework/test/browser_target_events.js
+++ b/devtools/client/framework/test/browser_target_events.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var target;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
--- a/devtools/client/framework/test/browser_target_remote.js
+++ b/devtools/client/framework/test/browser_target_remote.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Ensure target is closed if client is closed directly
 function test() {
   waitForExplicitFinish();
 
   getChromeActors((client, response) => {
     let options = {
       form: response,
--- a/devtools/client/framework/test/browser_target_support.js
+++ b/devtools/client/framework/test/browser_target_support.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test support methods on Target, such as `hasActor`, `getActorDescription`,
 // `actorHasMethod` and `getTrait`.
 
 var { Task } =
   Cu.import("resource://gre/modules/Task.jsm", {});
 var { WebAudioFront } =
   require("devtools/server/actors/webaudio");
--- a/devtools/client/framework/test/browser_toolbox_custom_host.js
+++ b/devtools/client/framework/test/browser_toolbox_custom_host.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   let {Toolbox} = require("devtools/client/framework/toolbox");
 
   let toolbox, iframe, target, tab;
 
   gBrowser.selectedTab = gBrowser.addTab();
   target = TargetFactory.forTab(gBrowser.selectedTab);
--- a/devtools/client/framework/test/browser_toolbox_dynamic_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_dynamic_registration.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var toolbox;
 
 function test()
 {
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
--- a/devtools/client/framework/test/browser_toolbox_getpanelwhenready.js
+++ b/devtools/client/framework/test/browser_toolbox_getpanelwhenready.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests that getPanelWhenReady returns the correct panel in promise
 // resolutions regardless of whether it has opened first.
 
 var toolbox = null;
 
--- a/devtools/client/framework/test/browser_toolbox_highlight.js
+++ b/devtools/client/framework/test/browser_toolbox_highlight.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 var toolbox = null;
 
 function test() {
--- a/devtools/client/framework/test/browser_toolbox_hosts.js
+++ b/devtools/client/framework/test/browser_toolbox_hosts.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 var {SIDE, BOTTOM, WINDOW} = Toolbox.HostType;
 var toolbox, target;
 
 const URL = "data:text/html;charset=utf8,test for opening toolbox in different hosts";
--- a/devtools/client/framework/test/browser_toolbox_hosts_size.js
+++ b/devtools/client/framework/test/browser_toolbox_hosts_size.js
@@ -1,12 +1,10 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests that getPanelWhenReady returns the correct panel in promise
 // resolutions regardless of whether it has opened first.
 
 const URL = "data:text/html;charset=utf8,test for host sizes";
 
--- a/devtools/client/framework/test/browser_toolbox_minimize.js
+++ b/devtools/client/framework/test/browser_toolbox_minimize.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 "use strict";
 
 // Test that when the toolbox is displayed in a bottom host, that host can be
 // minimized to just the tabbar height, and maximized again.
 // Also test that while minimized, switching to a tool, clicking on the
--- a/devtools/client/framework/test/browser_toolbox_options.js
+++ b/devtools/client/framework/test/browser_toolbox_options.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests that changing preferences in the options panel updates the prefs
 // and toggles appropriate things in the toolbox.
 
 var doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
 
--- a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 var doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
 
 function test() {
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
--- a/devtools/client/framework/test/browser_toolbox_options_disable_cache-01.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_cache-01.js
@@ -1,11 +1,13 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 requestLongerTimeout(2);
 
 // Tests that disabling the cache for a tab works as it should when toolboxes
 // are not toggled.
 loadHelperScript("helper_disable_cache.js");
 
--- a/devtools/client/framework/test/browser_toolbox_options_disable_cache-02.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_cache-02.js
@@ -1,11 +1,13 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 requestLongerTimeout(2);
 
 // Tests that disabling the cache for a tab works as it should when toolboxes
 // are toggled.
 loadHelperScript("helper_disable_cache.js");
 
--- a/devtools/client/framework/test/browser_toolbox_options_disable_js.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_js.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests that disabling JavaScript for a tab works as it should.
 
 const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_js.html";
 
 var doc;
--- a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
+++ b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Test that enabling Service Workers testing option enables the
 // mServiceWorkersTestingEnabled attribute added to nsPIDOMWindow.
 
 const COMMON_FRAME_SCRIPT_URL =
   "chrome://devtools/content/shared/frame-script-utils.js";
--- a/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
+++ b/devtools/client/framework/test/browser_toolbox_options_enable_serviceworkers_testing_frame_script.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // A helper frame-script for devtools/client/framework service worker tests.
 
 "use strict";
 
 addMessageListener("devtools:sw-test:register", function(msg) {
   content.navigator.serviceWorker.register("serviceworker.js")
     .then(swr => {
--- a/devtools/client/framework/test/browser_toolbox_raise.js
+++ b/devtools/client/framework/test/browser_toolbox_raise.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 var toolbox, target, tab1, tab2;
 
 function test() {
   gBrowser.selectedTab = tab1 = gBrowser.addTab();
   tab2 = gBrowser.addTab();
--- a/devtools/client/framework/test/browser_toolbox_ready.js
+++ b/devtools/client/framework/test/browser_toolbox_ready.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   const onLoad = Task.async(function *(evt) {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad);
 
--- a/devtools/client/framework/test/browser_toolbox_select_event.js
+++ b/devtools/client/framework/test/browser_toolbox_select_event.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 var toolbox;
 
 function test() {
   addTab("about:blank").then(function() {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
--- a/devtools/client/framework/test/browser_toolbox_selected_tool_unavailable.js
+++ b/devtools/client/framework/test/browser_toolbox_selected_tool_unavailable.js
@@ -1,10 +1,13 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 // Test that opening the toolbox doesn't throw when the previously selected
 // tool is not supported.
 
 const testToolDefinition = {
     id: "test-tool",
     isTargetSupported: () => true,
--- a/devtools/client/framework/test/browser_toolbox_sidebar.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar.js
@@ -1,17 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 function test() {
   const Cu = Components.utils;
   let {ToolSidebar} = require("devtools/client/framework/sidebar");
 
   const toolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
-                  "<?xml-stylesheet href='chrome://devtools/skin/common.css' type='text/css'?>" +
                   "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
                   "<hbox flex='1'><description flex='1'>foo</description><splitter class='devtools-side-splitter'/>" +
                   "<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'><tabs/><tabpanels flex='1'/></tabbox>" +
                   "</hbox>" +
                   "</window>";
 
   const tab1URL = "data:text/html;charset=utf8,<title>1</title><p>1</p>";
   const tab2URL = "data:text/html;charset=utf8,<title>2</title><p>2</p>";
--- a/devtools/client/framework/test/browser_toolbox_sidebar_events.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar_events.js
@@ -1,17 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 function test() {
   const Cu = Components.utils;
   const { ToolSidebar } = require("devtools/client/framework/sidebar");
 
   const toolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
-                  "<?xml-stylesheet href='chrome://devtools/skin/common.css' type='text/css'?>" +
                   "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
                   "<hbox flex='1'><description flex='1'>foo</description><splitter class='devtools-side-splitter'/>" +
                   "<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'><tabs/><tabpanels flex='1'/></tabbox>" +
                   "</hbox>" +
                   "</window>";
 
   const tab1URL = "data:text/html;charset=utf8,<title>1</title><p>1</p>";
 
--- a/devtools/client/framework/test/browser_toolbox_sidebar_existing_tabs.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar_existing_tabs.js
@@ -1,18 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 // Test that the sidebar widget auto-registers existing tabs.
 
 const {ToolSidebar} = require("devtools/client/framework/sidebar");
 
 const testToolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
-                "<?xml-stylesheet href='chrome://devtools/skin/common.css' type='text/css'?>" +
                 "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
                 "<hbox flex='1'><description flex='1'>test tool</description>" +
                 "<splitter class='devtools-side-splitter'/>" +
                 "<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'>" +
                 "<tabs><tab id='tab1' label='tab 1'></tab><tab id='tab2' label='tab 2'></tab></tabs>" +
                 "<tabpanels flex='1'><tabpanel id='tabpanel1'>tab 1</tabpanel><tabpanel id='tabpanel2'>tab 2</tabpanel></tabpanels>" +
                 "</tabbox></hbox></window>";
 
--- a/devtools/client/framework/test/browser_toolbox_sidebar_overflow_menu.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar_overflow_menu.js
@@ -1,10 +1,13 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 // Test that the sidebar widget correctly displays the "all tabs..." button
 // when the tabs overflow.
 
 const {ToolSidebar} = require("devtools/client/framework/sidebar");
 
 const testToolDefinition = {
--- a/devtools/client/framework/test/browser_toolbox_sidebar_tool.xul
+++ b/devtools/client/framework/test/browser_toolbox_sidebar_tool.xul
@@ -1,15 +1,14 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   <box flex="1" class="devtools-responsive-container theme-body">
     <vbox flex="1" class="devtools-main-content" id="content">test</vbox>
     <splitter class="devtools-side-splitter"/>
     <tabbox flex="1" id="sidebar" class="devtools-sidebar-tabs">
       <tabs/>
--- a/devtools/client/framework/test/browser_toolbox_split_console.js
+++ b/devtools/client/framework/test/browser_toolbox_split_console.js
@@ -1,10 +1,10 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 // Tests that these toolbox split console APIs work:
 //  * toolbox.useKeyWithSplitConsole()
 //  * toolbox.isSplitConsoleFocused
 
 let gToolbox = null;
--- a/devtools/client/framework/test/browser_toolbox_tabsswitch_shortcuts.js
+++ b/devtools/client/framework/test/browser_toolbox_tabsswitch_shortcuts.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 requestLongerTimeout(2);
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 add_task(function*() {
--- a/devtools/client/framework/test/browser_toolbox_textbox_context_menu.js
+++ b/devtools/client/framework/test/browser_toolbox_textbox_context_menu.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 const URL = "data:text/html;charset=utf8,test for textbox context menu";
 
 add_task(function*() {
   let toolbox = yield openNewTabAndToolbox(URL, "inspector");
   let textboxContextMenu = toolbox.textboxContextMenuPopup;
--- a/devtools/client/framework/test/browser_toolbox_theme_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_theme_registration.js
@@ -1,11 +1,12 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test for dynamically registering and unregistering themes
 const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/framework/test/";
 
 var toolbox;
 
 add_task(function* themeRegistration() {
   let tab = yield addTab("data:text/html,test");
--- a/devtools/client/framework/test/browser_toolbox_toggle.js
+++ b/devtools/client/framework/test/browser_toolbox_toggle.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test toggling the toolbox with ACCEL+SHIFT+I / ACCEL+ALT+I and F12 in docked
 // and detached (window) modes.
 
 const URL = "data:text/html;charset=utf-8,Toggling devtools using shortcuts";
 
--- a/devtools/client/framework/test/browser_toolbox_tool_ready.js
+++ b/devtools/client/framework/test/browser_toolbox_tool_ready.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Whitelisting this test.
  * As part of bug 1077403, the leaking uncaught rejection should be fixed.
  */
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Shader Editor is " +
--- a/devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
+++ b/devtools/client/framework/test/browser_toolbox_tool_remote_reopen.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Whitelisting this test.
  * As part of bug 1077403, the leaking uncaught rejection should be fixed.
  */
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Shader Editor is " +
--- a/devtools/client/framework/test/browser_toolbox_transport_events.js
+++ b/devtools/client/framework/test/browser_toolbox_transport_events.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 const { on, off } = require("sdk/event/core");
 const { DebuggerClient } = require("devtools/shared/client/main");
 
 function test() {
   gDevTools.on("toolbox-created", onToolboxCreated);
--- a/devtools/client/framework/test/browser_toolbox_view_source_01.js
+++ b/devtools/client/framework/test/browser_toolbox_view_source_01.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that Toolbox#viewSourceInDebugger works when debugger is not
  * yet opened.
  */
 
 var URL = `${URL_ROOT}doc_viewsource.html`;
 var JS_URL = `${URL_ROOT}code_math.js`;
--- a/devtools/client/framework/test/browser_toolbox_view_source_02.js
+++ b/devtools/client/framework/test/browser_toolbox_view_source_02.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that Toolbox#viewSourceInDebugger works when debugger is already loaded.
  */
 
 var URL = `${URL_ROOT}doc_viewsource.html`;
 var JS_URL = `${URL_ROOT}code_math.js`;
 
--- a/devtools/client/framework/test/browser_toolbox_view_source_03.js
+++ b/devtools/client/framework/test/browser_toolbox_view_source_03.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that Toolbox#viewSourceInStyleEditor works when style editor is not
  * yet opened.
  */
 
 var URL = `${URL_ROOT}doc_viewsource.html`;
 var CSS_URL = `${URL_ROOT}doc_theme.css`;
--- a/devtools/client/framework/test/browser_toolbox_view_source_04.js
+++ b/devtools/client/framework/test/browser_toolbox_view_source_04.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that Toolbox#viewSourceInScratchpad works.
  */
 
 var URL = `${URL_ROOT}doc_viewsource.html`;
 
 function *viewSource() {
--- a/devtools/client/framework/test/browser_toolbox_window_reload_target.js
+++ b/devtools/client/framework/test/browser_toolbox_window_reload_target.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 requestLongerTimeout(2);
 
 const TEST_URL = "data:text/html;charset=utf-8,"+
                  "<html><head><title>Test reload</title></head>"+
                  "<body><h1>Testing reload from devtools</h1></body></html>";
--- a/devtools/client/framework/test/browser_toolbox_window_shortcuts.js
+++ b/devtools/client/framework/test/browser_toolbox_window_shortcuts.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 "use strict";
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 var toolbox, toolIDs, idIndex, modifiedPrefs = [];
--- a/devtools/client/framework/test/browser_toolbox_window_title_changes.js
+++ b/devtools/client/framework/test/browser_toolbox_window_title_changes.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 function test() {
   const URL_1 = "data:text/plain;charset=UTF-8,abcde";
   const URL_2 = "data:text/plain;charset=UTF-8,12345";
--- a/devtools/client/framework/test/browser_toolbox_zoom.js
+++ b/devtools/client/framework/test/browser_toolbox_zoom.js
@@ -1,8 +1,10 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 var modifiers = {
   accelKey: true
 };
 
 var toolbox;
--- a/devtools/client/framework/test/browser_two_tabs.js
+++ b/devtools/client/framework/test/browser_two_tabs.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Check regression when opening two tabs
  */
 
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/main");
 
--- a/devtools/client/framework/test/code_math.js
+++ b/devtools/client/framework/test/code_math.js
@@ -1,4 +1,9 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 function add(a, b, k) {
   var result = a + b;
   return k(result);
 }
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
 
 function toggleAllTools(state) {
   for (let [, tool] of gDevTools._tools) {
     if (!tool.visibilityswitch) {
       continue;
--- a/devtools/client/framework/test/helper_disable_cache.js
+++ b/devtools/client/framework/test/helper_disable_cache.js
@@ -1,11 +1,13 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 "use strict";
 
 // Common code shared by browser_toolbox_options_disable_cache-*.js
 const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_cache.sjs";
 var tabs = [
 {
   title: "Tab 0",
   desc: "Toggles cache on.",
--- a/devtools/client/framework/test/serviceworker.js
+++ b/devtools/client/framework/test/serviceworker.js
@@ -1,1 +1,6 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+
 // empty service worker, always succeed!
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -1,11 +1,12 @@
-/* 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/. */
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
 
 "use strict";
 
 // This shared-head.js file is used for multiple mochitest test directories in
 // devtools.
 // It contains various common helper functions.
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC} = Components;
--- a/devtools/client/framework/test/shared-redux-head.js
+++ b/devtools/client/framework/test/shared-redux-head.js
@@ -1,10 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/ */
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 // Currently this file expects "promise" to be imported into scope.
 /* globals promise */
 
 // Common utility functions for working with Redux stores.  The file is meant
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ b/devtools/client/framework/toolbox-highlighter-utils.js
@@ -1,8 +1,10 @@
+/* -*- 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 {Cc, Ci, Cu} = require("chrome");
 const promise = require("promise");
--- a/devtools/client/framework/toolbox-hosts.js
+++ b/devtools/client/framework/toolbox-hosts.js
@@ -1,8 +1,10 @@
+/* -*- 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 {Cu} = require("chrome");
 const EventEmitter = require("devtools/shared/event-emitter");
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -1,8 +1,10 @@
+/* -*- 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 {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -1,11 +1,14 @@
+/* -*- 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";
 
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 var { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 // Require this module just to setup things like themes and tools
 // devtools-browser is special as it loads main module
 // To be cleaned up in bug 1247203.
@@ -106,19 +109,36 @@ function openToolbox({ form, chrome, isT
                           selectedTool,
                           Toolbox.HostType.CUSTOM,
                           options)
              .then(onNewToolbox);
   });
 }
 
 function onNewToolbox(toolbox) {
-   gToolbox = toolbox;
-   bindToolboxHandlers();
-   raise();
+  gToolbox = toolbox;
+  bindToolboxHandlers();
+  raise();
+  let testScript = getParameterByName("testScript");
+  if (testScript) {
+    // Only allow executing random chrome scripts when a special
+    // test-only pref is set
+    let prefName = "devtools.browser-toolbox.allow-unsafe-script";
+    if (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL &&
+        Services.prefs.getBoolPref(prefName) === true) {
+      evaluateTestScript(testScript, toolbox);
+    }
+  }
+}
+
+function evaluateTestScript(script, toolbox) {
+  let sandbox = Cu.Sandbox(window);
+  sandbox.window = window;
+  sandbox.toolbox = toolbox;
+  Cu.evalInSandbox(script, sandbox);
 }
 
 function bindToolboxHandlers() {
   gToolbox.once("destroyed", quitApp);
   window.addEventListener("unload", onUnload);
 
 #ifdef XP_MACOSX
   // Badge the dock icon to differentiate this process from the main application process.
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1,8 +1,10 @@
+/* -*- 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 MAX_ORDINAL = 99;
 const ZOOM_PREF = "devtools.toolbox.zoomValue";
@@ -23,33 +25,33 @@ var HUDService = require("devtools/clien
 var viewSource = require("devtools/client/shared/view-source");
 var { attachThread, detachThread } = require("./attach-thread");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 Cu.import("resource://devtools/client/shared/DOMHelpers.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
-loader.lazyImporter(this, "CommandUtils",
-  "resource://devtools/client/shared/DeveloperToolbar.jsm");
 loader.lazyGetter(this, "toolboxStrings", () => {
   const properties = "chrome://devtools/locale/toolbox.properties";
   const bundle = Services.strings.createBundle(properties);
   return (name, ...args) => {
     try {
       if (!args.length) {
         return bundle.GetStringFromName(name);
       }
       return bundle.formatStringFromName(name, args, args.length);
     } catch (ex) {
       Services.console.logStringMessage("Error reading '" + name + "'");
       return null;
     }
   };
 });
+loader.lazyRequireGetter(this, "CommandUtils",
+  "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "getHighlighterUtils",
   "devtools/client/framework/toolbox-highlighter-utils", true);
 loader.lazyRequireGetter(this, "Hosts",
   "devtools/client/framework/toolbox-hosts", true);
 loader.lazyRequireGetter(this, "Selection",
   "devtools/client/framework/selection", true);
 loader.lazyRequireGetter(this, "InspectorFront",
   "devtools/server/actors/inspector", true);
--- a/devtools/client/framework/toolbox.xul
+++ b/devtools/client/framework/toolbox.xul
@@ -1,14 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <!DOCTYPE window [
 <!ENTITY % toolboxDTD SYSTEM "chrome://devtools/locale/toolbox.dtd" >
 %toolboxDTD;
 <!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
 %editMenuStrings;
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -31,17 +31,17 @@ loader.lazyGetter(this, "strings", () =>
 });
 loader.lazyGetter(this, "toolboxStrings", () => {
   return Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
 });
 loader.lazyGetter(this, "clipboardHelper", () => {
   return Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
 });
 
-loader.lazyImporter(this, "CommandUtils", "resource://devtools/client/shared/DeveloperToolbar.jsm");
+loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 
 /**
  * Represents an open instance of the Inspector for a tab.
  * The inspector controls the breadcrumbs, the markup view, and the sidebar
  * (computed view, rule view, font view and layout view).
  *
  * Events:
  * - ready
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/inspector/inspector.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/inspector.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/rules.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/computed.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/fonts.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/layout.css" type="text/css"?>
 
 <!DOCTYPE window [
--- a/devtools/client/inspector/markup/markup.xhtml
+++ b/devtools/client/inspector/markup/markup.xhtml
@@ -4,17 +4,16 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet" href="chrome://devtools/content/inspector/markup/markup.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/skin/markup.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
 
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"/>
 
 </head>
 <body class="theme-body devtools-monospace" role="application">
 
 <!-- NOTE THAT WE MAKE EXTENSIVE USE OF HTML COMMENTS IN THIS FILE IN ORDER -->
--- a/devtools/client/memory/app.js
+++ b/devtools/client/memory/app.js
@@ -1,13 +1,14 @@
 /* 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/. */
 
 const { assert } = require("devtools/shared/DevToolsUtils");
+const { appinfo } = require("Services");
 const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { breakdowns, diffingState, viewState } = require("./constants");
 const { toggleRecordingAllocationStacks } = require("./actions/allocations");
 const { setBreakdownAndRefresh } = require("./actions/breakdown");
 const { setDominatorTreeBreakdownAndRefresh } = require("./actions/dominatorTreeBreakdown");
 const {
   selectSnapshotForDiffingAndRefresh,
@@ -48,30 +49,62 @@ const MemoryApp = createClass({
   displayName: "MemoryApp",
 
   propTypes: appModel,
 
   getDefaultProps() {
     return {};
   },
 
+  componentDidMount() {
+    // Attach the keydown listener directly to the window. When an element that
+    // has the focus (such as a tree node) is removed from the DOM, the focus
+    // falls back to the body.
+    window.addEventListener("keydown", this.onKeyDown);
+  },
+
+  componentWillUnmount() {
+    window.removeEventListener("keydown", this.onKeyDown);
+  },
+
   childContextTypes: {
     front: PropTypes.any,
     heapWorker: PropTypes.any,
     toolbox: PropTypes.any,
   },
 
   getChildContext() {
     return {
       front: this.props.front,
       heapWorker: this.props.heapWorker,
       toolbox: this.props.toolbox,
     };
   },
 
+  onKeyDown(e) {
+    let { snapshots, dispatch, heapWorker } = this.props;
+    const selectedSnapshot = snapshots.find(s => s.selected);
+    const selectedIndex = snapshots.indexOf(selectedSnapshot);
+
+    let isOSX = appinfo.OS == "Darwin";
+    let isAccelKey = (isOSX && e.metaKey) || (!isOSX && e.ctrlKey);
+    // On ACCEL+UP, select previous snapshot.
+    if (isAccelKey && e.key === "ArrowUp") {
+      let previousIndex = Math.max(0, selectedIndex - 1);
+      let previousSnapshotId = snapshots[previousIndex].id;
+      dispatch(selectSnapshotAndRefresh(heapWorker, previousSnapshotId));
+    }
+    // On ACCEL+DOWN, select next snapshot.
+    if (isAccelKey && e.key === "ArrowDown") {
+      let nextIndex = Math.min(snapshots.length - 1, selectedIndex + 1);
+      let nextSnapshotId = snapshots[nextIndex].id;
+      dispatch(selectSnapshotAndRefresh(heapWorker, nextSnapshotId));
+    }
+  },
+
   render() {
     let {
       dispatch,
       snapshots,
       front,
       heapWorker,
       breakdown,
       allocations,
--- a/devtools/client/memory/memory.xhtml
+++ b/devtools/client/memory/memory.xhtml
@@ -6,17 +6,16 @@
   %htmlDTD;
 ]>
 
 <!-- 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/. -->
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
-    <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/widgets.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/memory.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/components-frame.css" type="text/css"/>
 
     <script type="application/javascript;version=1.8"
             src="chrome://devtools/content/shared/theme-switching.js"/>
     <script type="application/javascript;version=1.8"
             src="initializer.js"></script>
--- a/devtools/client/memory/test/browser/browser.ini
+++ b/devtools/client/memory/test/browser/browser.ini
@@ -10,14 +10,15 @@ support-files =
     skip-if = debug # bug 1219554
 [browser_memory_breakdowns_01.js]
 [browser_memory_clear_snapshots.js]
 [browser_memory_diff_01.js]
 [browser_memory_dominator_trees_01.js]
 [browser_memory_dominator_trees_02.js]
 [browser_memory_filter_01.js]
 [browser_memory_keyboard.js]
+[browser_memory_keyboard-snapshot-list.js]
 [browser_memory_no_allocation_stacks.js]
 [browser_memory_no_auto_expand.js]
     skip-if = debug # bug 1219554
 [browser_memory_percents_01.js]
 [browser_memory_simple_01.js]
 [browser_memory_transferHeapSnapshot_e10s_01.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/memory/test/browser/browser_memory_keyboard-snapshot-list.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that using ACCEL+UP/DOWN, the user can navigate between snapshots.
+
+"use strict";
+
+const {
+  snapshotState
+} = require("devtools/client/memory/constants");
+const {
+  takeSnapshotAndCensus
+} = require("devtools/client/memory/actions/snapshot");
+
+const TEST_URL = "http://example.com/browser/devtools/client/memory/test/browser/doc_steady_allocation.html";
+
+this.test = makeMemoryTest(TEST_URL, function* ({ panel }) {
+  // Creating snapshots already takes ~25 seconds on linux 32 debug machines
+  // which makes the test very likely to go over the allowed timeout
+  requestLongerTimeout(2);
+
+  const heapWorker = panel.panelWin.gHeapAnalysesClient;
+  const front = panel.panelWin.gFront;
+  const store = panel.panelWin.gStore;
+  const { dispatch } = store;
+  const doc = panel.panelWin.document;
+
+  info("Take 3 snapshots");
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+  dispatch(takeSnapshotAndCensus(front, heapWorker));
+
+  yield waitUntilState(store, state =>
+    state.snapshots.length == 3 &&
+    state.snapshots.every(s => s.state === snapshotState.SAVED_CENSUS));
+  ok(true, "All snapshots are in SAVED_CENSUS state");
+
+  yield waitUntilSnapshotSelected(store, 2);
+  ok(true, "Third snapshot selected after creating all snapshots.");
+
+  info("Press ACCEL+UP key, expect second snapshot selected.");
+  EventUtils.synthesizeKey("VK_UP", { accelKey: true }, panel.panelWin);
+  yield waitUntilSnapshotSelected(store, 1);
+  ok(true, "Second snapshot selected after alt+UP.");
+
+  info("Press ACCEL+UP key, expect first snapshot selected.");
+  EventUtils.synthesizeKey("VK_UP", { accelKey: true }, panel.panelWin);
+  yield waitUntilSnapshotSelected(store, 0);
+  ok(true, "First snapshot is selected after ACCEL+UP");
+
+  info("Check ACCEL+UP is a noop when the first snapshot is selected.");
+  EventUtils.synthesizeKey("VK_UP", { accelKey: true }, panel.panelWin);
+  // We assume the snapshot selection should be synchronous here.
+  is(getSelectedSnapshotIndex(store), 0, "First snapshot is still selected");
+
+  info("Press ACCEL+DOWN key, expect second snapshot selected.");
+  EventUtils.synthesizeKey("VK_DOWN", { accelKey: true }, panel.panelWin);
+  yield waitUntilSnapshotSelected(store, 1);
+  ok(true, "Second snapshot is selected after ACCEL+DOWN");
+
+  info("Click on first node.");
+  let firstNode = doc.querySelector(".tree .heap-tree-item-name");
+  EventUtils.synthesizeMouseAtCenter(firstNode, {}, panel.panelWin);
+  yield waitUntilState(store, state => state.snapshots[1].census.focused ===
+      state.snapshots[1].census.report.children[0]
+  );
+  ok(true, "First root is selected after click.");
+
+  info("Press DOWN key, expect second root focused.");
+  EventUtils.synthesizeKey("VK_DOWN", {}, panel.panelWin);
+  yield waitUntilState(store, state => state.snapshots[1].census.focused ===
+      state.snapshots[1].census.report.children[1]
+  );
+  ok(true, "Second root is selected after pressing DOWN.");
+  is(getSelectedSnapshotIndex(store), 1, "Second snapshot is still selected");
+
+  info("Press UP key, expect second root focused.");
+  EventUtils.synthesizeKey("VK_UP", {}, panel.panelWin);
+  yield waitUntilState(store, state => state.snapshots[1].census.focused ===
+      state.snapshots[1].census.report.children[0]
+  );
+  ok(true, "First root is selected after pressing UP.");
+  is(getSelectedSnapshotIndex(store), 1, "Second snapshot is still selected");
+
+  info("Press ACCEL+DOWN key, expect third snapshot selected.");
+  EventUtils.synthesizeKey("VK_DOWN", { accelKey: true }, panel.panelWin);
+  yield waitUntilSnapshotSelected(store, 2);
+  ok(true, "ThirdĖ† snapshot is selected after ACCEL+DOWN");
+
+  info("Check ACCEL+DOWN is a noop when the last snapshot is selected.");
+  EventUtils.synthesizeKey("VK_DOWN", { accelKey: true }, panel.panelWin);
+  // We assume the snapshot selection should be synchronous here.
+  is(getSelectedSnapshotIndex(store), 2, "Third snapshot is still selected");
+});
--- a/devtools/client/memory/test/browser/head.js
+++ b/devtools/client/memory/test/browser/head.js
@@ -137,8 +137,31 @@ function setBreakdown (window, type) {
  * displayed.
  *
  * @param {Document} document
  */
 function getDisplayedSnapshotStatus(document) {
   const status = document.querySelector(".snapshot-status");
   return status ? status.textContent.trim() : null;
 }
+
+/**
+ * Get the index of the currently selected snapshot.
+ *
+ * @return {Number}
+ */
+function getSelectedSnapshotIndex(store) {
+  let snapshots = store.getState().snapshots;
+  let selectedSnapshot = snapshots.find(s => s.selected);
+  return snapshots.indexOf(selectedSnapshot);
+}
+
+/**
+ * Returns a promise that will resolve when the snapshot with provided index
+ * becomes selected.
+ *
+ * @return {Promise}
+ */
+function waitUntilSnapshotSelected(store, snapshotIndex) {
+  return waitUntilState(store, state =>
+    state.snapshots[snapshotIndex] &&
+    state.snapshots[snapshotIndex].selected === true);
+}
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/netmonitor/netmonitor.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/netmonitor.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % netmonitorDTD SYSTEM "chrome://devtools/locale/netmonitor.dtd">
   %netmonitorDTD;
   <!ENTITY % certManagerDTD SYSTEM "chrome://pippki/locale/certManager.dtd">
   %certManagerDTD;
 ]>
--- a/devtools/client/performance/performance.xul
+++ b/devtools/client/performance/performance.xul
@@ -1,15 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/performance.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/jit-optimizations.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/components-frame.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % performanceDTD SYSTEM "chrome://devtools/locale/performance.dtd">
   %performanceDTD;
 ]>
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -327,19 +327,18 @@ pref("devtools.editor.autocomplete", tru
 // Enable the Font Inspector
 pref("devtools.fontinspector.enabled", true);
 
 // Pref to store the browser version at the time of a telemetry ping for an
 // opened developer tool. This allows us to ping telemetry just once per browser
 // version for each user.
 pref("devtools.telemetry.tools.opened.version", "{}");
 
-// Enable the JSON View tool (an inspector for application/json documents) on
-// Nightly and Dev. Edition.
-#ifdef RELEASE_BUILD
-pref("devtools.jsonview.enabled", false);
+// Enable the JSON View tool (an inspector for application/json documents)
+#ifdef MOZ_DEV_EDITION
+  pref("devtools.jsonview.enabled", true);
 #else
-pref("devtools.jsonview.enabled", true);
+  pref("devtools.jsonview.enabled", false);
 #endif
 
 // Disable the HTML responsive design tool by default.  Currently disabled until
 // ready to replace the legacy XUL version.
 pref("devtools.responsive.html.enabled", false);
--- a/devtools/client/promisedebugger/promise-debugger.xhtml
+++ b/devtools/client/promisedebugger/promise-debugger.xhtml
@@ -7,17 +7,16 @@
   <!ENTITY % promisedebuggerDTD SYSTEM "chrome://devtools/locale/promisedebugger.dtd">
   %promisedebuggerDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>&title;</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-    <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://devtools/skin/promisedebugger.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   </head>
   <body class="devtools-monospace" role="application">
     <script type="application/javascript;version=1.8" src="promise-controller.js"></script>
     <script type="application/javascript;version=1.8" src="promise-panel.js"></script>
   </body>
 </html>
--- a/devtools/client/responsive.html/index.xhtml
+++ b/devtools/client/responsive.html/index.xhtml
@@ -2,18 +2,16 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     <link rel="stylesheet" type="text/css"
-          href="chrome://devtools/skin/common.css"/>
-    <link rel="stylesheet" type="text/css"
           href="resource://devtools/client/responsive.html/index.css"/>
     <script type="application/javascript;version=1.8"
             src="chrome://devtools/content/shared/theme-switching.js"></script>
     <script type="application/javascript;version=1.8"
             src="./index.js"></script>
   </head>
   <body class="theme-body" role="application">
     <div id="app"/>
--- a/devtools/client/scratchpad/scratchpad.xul
+++ b/devtools/client/scratchpad/scratchpad.xul
@@ -9,17 +9,16 @@
  %scratchpadDTD;
 <!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
 %editMenuStrings;
 <!ENTITY % sourceEditorStrings SYSTEM "chrome://devtools/locale/sourceeditor.dtd">
 %sourceEditorStrings;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
 <?xml-stylesheet href="chrome://devtools/skin/scratchpad.css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <window id="main-window"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="&window.title;"
         windowtype="devtools:scratchpad"
         macanimationtype="document"
--- a/devtools/client/shadereditor/shadereditor.xul
+++ b/devtools/client/shadereditor/shadereditor.xul
@@ -1,14 +1,13 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/shadereditor.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % debuggerDTD SYSTEM "chrome://devtools/locale/shadereditor.dtd">
   %debuggerDTD;
 ]>
 
--- a/devtools/client/shared/components/test/mochitest/test_tree_06.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_06.html
@@ -271,16 +271,45 @@ window.onload = Task.async(function* () 
       "--H:false",
       "--I:false",
       "-D:false",
       "--J:false",
       "M:false",
       "-N:false",
       "--O:false",
     ], "After the RIGHT, K should be focused.");
+
+    // Check that keys are ignored if any modifier is present.
+    let keysWithModifier = [
+      { key: "ArrowDown", altKey: true },
+      { key: "ArrowDown", ctrlKey: true },
+      { key: "ArrowDown", metaKey: true },
+      { key: "ArrowDown", shiftKey: true },
+    ];
+    for (let key of keysWithModifier) {
+      Simulate.keyDown(document.querySelector(".tree"), key);
+      yield forceRender(tree);
+      isRenderedTree(document.body.textContent, [
+        "A:false",
+        "-B:false",
+        "--E:false",
+        "---K:true",
+        "---L:false",
+        "--F:false",
+        "--G:false",
+        "-C:false",
+        "--H:false",
+        "--I:false",
+        "-D:false",
+        "--J:false",
+        "M:false",
+        "-N:false",
+        "--O:false",
+      ], "After DOWN + (alt|ctrl|meta|shift), K should remain focused.");
+    }
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
--- a/devtools/client/shared/components/tree.js
+++ b/devtools/client/shared/components/tree.js
@@ -400,16 +400,21 @@ const Tree = module.exports = createClas
    *
    * @param {Event} e
    */
   _onKeyDown(e) {
     if (this.props.focused == null) {
       return;
     }
 
+    // Allow parent nodes to use navigation arrows with modifiers.
+    if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
+      return;
+    }
+
     // Prevent scrolling when pressing navigation keys. Guard against mocked
     // events received when testing.
     if (e.nativeEvent && e.nativeEvent.preventDefault) {
       ViewHelpers.preventScrolling(e.nativeEvent);
     }
 
     switch (e.key) {
       case "ArrowUp":
rename from devtools/client/shared/DeveloperToolbar.jsm
rename to devtools/client/shared/developer-toolbar.js
--- a/devtools/client/shared/DeveloperToolbar.jsm
+++ b/devtools/client/shared/developer-toolbar.js
@@ -1,71 +1,39 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = [ "DeveloperToolbar", "CommandUtils" ];
+const { Cc, Ci, Cu } = require("chrome");
+const promise = require("promise");
+const Services = require("Services");
+const { TargetFactory } = require("devtools/client/framework/target");
+const Telemetry = require("devtools/client/shared/telemetry");
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const { TargetFactory } = require("devtools/client/framework/target");
-const promise = require("promise");
-
 const Node = Ci.nsIDOMNode;
 
-XPCOMUtils.defineLazyModuleGetter(this, "console",
-                                  "resource://gre/modules/Console.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
-                                  "resource://gre/modules/PluralForm.jsm");
+loader.lazyImporter(this, "console", "resource://gre/modules/Console.jsm");
+loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
+loader.lazyImporter(this, "EventEmitter", "resource://devtools/shared/event-emitter.js");
 
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
-                                  "resource://devtools/shared/event-emitter.js");
-
-XPCOMUtils.defineLazyGetter(this, "prefBranch", function() {
-  let prefService = Cc["@mozilla.org/preferences-service;1"]
-                    .getService(Ci.nsIPrefService);
-  return prefService.getBranch(null)
+loader.lazyGetter(this, "prefBranch", function() {
+  return Services.prefs.getBranch(null)
                     .QueryInterface(Ci.nsIPrefBranch2);
 });
-
-XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function () {
+loader.lazyGetter(this, "toolboxStrings", function () {
   return Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
 });
 
-const Telemetry = require("devtools/client/shared/telemetry");
-
-XPCOMUtils.defineLazyGetter(this, "gcliInit", function() {
-  try {
-    return require("devtools/shared/gcli/commands/index");
-  }
-  catch (ex) {
-    console.log(ex);
-  }
-});
-
-XPCOMUtils.defineLazyGetter(this, "util", () => {
-  return require("gcli/util/util");
-});
-
-Object.defineProperty(this, "ConsoleServiceListener", {
-  get: function() {
-    return require("devtools/shared/webconsole/utils").ConsoleServiceListener;
-  },
-  configurable: true,
-  enumerable: true
-});
+loader.lazyRequireGetter(this, "gcliInit", "devtools/shared/gcli/commands/index");
+loader.lazyRequireGetter(this, "util", "gcli/util/util");
+loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/shared/webconsole/utils", true);
 
 /**
  * A collection of utilities to help working with commands
  */
 var CommandUtils = {
   /**
    * Utility to ensure that things are loaded in the correct order
    */
@@ -228,41 +196,41 @@ var CommandUtils = {
       get document() {
         // throw new Error("environment.document is not available in runAt:client commands");
         return this.chromeWindow.gBrowser.contentDocumentAsCPOW;
       }
     };
   },
 };
 
-this.CommandUtils = CommandUtils;
+exports.CommandUtils = CommandUtils;
 
 /**
  * Due to a number of panel bugs we need a way to check if we are running on
  * Linux. See the comments for TooltipPanel and OutputPanel for further details.
  *
  * When bug 780102 is fixed all isLinux checks can be removed and we can revert
  * to using panels.
  */
-XPCOMUtils.defineLazyGetter(this, "isLinux", function() {
+loader.lazyGetter(this, "isLinux", function() {
   return OS == "Linux";
 });
 
-XPCOMUtils.defineLazyGetter(this, "OS", function() {
+loader.lazyGetter(this, "OS", function() {
   let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
   return os;
 });
 
 /**
  * A component to manage the global developer toolbar, which contains a GCLI
  * and buttons for various developer tools.
  * @param aChromeWindow The browser window to which this toolbar is attached
  * @param aToolbarElement See browser.xul:<toolbar id="developer-toolbar">
  */
-this.DeveloperToolbar = function DeveloperToolbar(aChromeWindow, aToolbarElement)
+function DeveloperToolbar(aChromeWindow, aToolbarElement)
 {
   this._chromeWindow = aChromeWindow;
 
   this.target = null; // Will be setup when show() is called
 
   this._element = aToolbarElement;
   this._element.hidden = true;
   this._doc = this._element.ownerDocument;
@@ -273,16 +241,17 @@ this.DeveloperToolbar = function Develop
   this._errorListeners = {};
   this._errorCounterButton = this._doc
                              .getElementById("developer-toolbar-toolbox-button");
   this._errorCounterButton._defaultTooltipText =
       this._errorCounterButton.getAttribute("tooltiptext");
 
   EventEmitter.decorate(this);
 }
+exports.DeveloperToolbar = DeveloperToolbar;
 
 /**
  * Inspector notifications dispatched through the nsIObserverService
  */
 const NOTIFICATIONS = {
   /** DeveloperToolbar.show() has been called, and we're working on it */
   LOAD: "developer-toolbar-load",
 
--- a/devtools/client/shared/inplace-editor.js
+++ b/devtools/client/shared/inplace-editor.js
@@ -381,16 +381,18 @@ InplaceEditor.prototype = {
       width += 15;
       this.input.style.height = this._measurement.offsetHeight + "px";
     }
 
     if (width === 0) {
       // If the editor is empty use a width corresponding to 1 character.
       this.input.style.width = "1ch";
     } else {
+      // Add 2 pixels to ensure the caret will be visible
+      width = width + 2;
       this.input.style.width = width + "px";
     }
   },
 
   /**
    * Get the width of a single character in the input to properly position the
    * autocompletion popup.
    */
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -16,17 +16,17 @@ DIRS += [
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
     'css-parsing-utils.js',
     'Curl.jsm',
     'demangle.js',
-    'DeveloperToolbar.jsm',
+    'developer-toolbar.js',
     'devices.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
     'file-watcher-worker.js',
     'file-watcher.js',
     'frame-script-utils.js',
     'getjson.js',
     'inplace-editor.js',
--- a/devtools/client/shared/test/browser_tableWidget_basic.js
+++ b/devtools/client/shared/test/browser_tableWidget_basic.js
@@ -1,17 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the table widget api works fine
 
 const TEST_URI = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
   "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
-  "<?xml-stylesheet href='chrome://devtools/skin/common.css'?>" +
   "<?xml-stylesheet href='chrome://devtools/skin/light-theme.css'?>" +
   "<?xml-stylesheet href='chrome://devtools/skin/widgets.css'?>" +
   "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
   " title='Table Widget' width='600' height='500'>" +
   "<box flex='1' class='theme-light'/></window>";
 const TEST_OPT = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 
 const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
--- a/devtools/client/shared/test/browser_tableWidget_keyboard_interaction.js
+++ b/devtools/client/shared/test/browser_tableWidget_keyboard_interaction.js
@@ -1,17 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that keyboard interaction works fine with the table widget
 
 const TEST_URI = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
   "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
-  "<?xml-stylesheet href='chrome://devtools/skin/common.css'?>" +
   "<?xml-stylesheet href='chrome://devtools/skin/light-theme.css'?>" +
   "<?xml-stylesheet href='chrome://devtools/skin/widgets.css'?>" +
   "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
   " title='Table Widget' width='600' height='500'>" +
   "<box flex='1' class='theme-light'/></window>";
 const TEST_OPT = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 
 const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
--- a/devtools/client/shared/test/browser_tableWidget_mouse_interaction.js
+++ b/devtools/client/shared/test/browser_tableWidget_mouse_interaction.js
@@ -1,17 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that mosue interaction works fine with the table widget
 
 const TEST_URI = "data:text/xml;charset=UTF-8,<?xml version='1.0'?>" +
   "<?xml-stylesheet href='chrome://global/skin/global.css'?>" +
-  "<?xml-stylesheet href='chrome://devtools/skin/common.css'?>" +
   "<?xml-stylesheet href='chrome://devtools/skin/light-theme.css'?>" +
   "<?xml-stylesheet href='chrome://devtools/skin/widgets.css'?>" +
   "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'" +
   " title='Table Widget' width='600' height='500'>" +
   "<box flex='1' class='theme-light'/></window>";
 const TEST_OPT = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 
 const {TableWidget} = require("devtools/client/shared/widgets/TableWidget");
--- a/devtools/client/shared/test/browser_treeWidget_basic.js
+++ b/devtools/client/shared/test/browser_treeWidget_basic.js
@@ -1,17 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the tree widget api works fine
 
-const TEST_URI = "data:text/html;charset=utf-8,<head><link rel='stylesheet' " +
-  "type='text/css' href='chrome://devtools/skin/common.css'><link " +
-  "rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
+const TEST_URI = "data:text/html;charset=utf-8,<head>" +
+  "<link rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
   "ets.css'></head><body><div></div><span></span></body>";
 const {TreeWidget} = require("devtools/client/shared/widgets/TreeWidget");
 
 add_task(function*() {
   yield addTab("about:blank");
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
 
   let tree = new TreeWidget(doc.querySelector("div"), {
--- a/devtools/client/shared/test/browser_treeWidget_keyboard_interaction.js
+++ b/devtools/client/shared/test/browser_treeWidget_keyboard_interaction.js
@@ -1,17 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that keyboard interaction works fine with the tree widget
 
-const TEST_URI = "data:text/html;charset=utf-8,<head><link rel='stylesheet' " +
-  "type='text/css' href='chrome://devtools/skin/common.css'><link " +
-  "rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
+const TEST_URI = "data:text/html;charset=utf-8,<head>" +
+  "<link rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
   "ets.css'></head><body><div></div><span></span></body>";
 const {TreeWidget} = require("devtools/client/shared/widgets/TreeWidget");
 const Promise = require("promise");
 
 add_task(function*() {
   yield addTab("about:blank");
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
 
--- a/devtools/client/shared/test/browser_treeWidget_mouse_interaction.js
+++ b/devtools/client/shared/test/browser_treeWidget_mouse_interaction.js
@@ -1,17 +1,16 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that mouse interaction works fine with tree widget
 
-const TEST_URI = "data:text/html;charset=utf-8,<head><link rel='stylesheet' " +
-  "type='text/css' href='chrome://devtools/skin/common.css'><link " +
-  "rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
+const TEST_URI = "data:text/html;charset=utf-8,<head>" +
+  "<link rel='stylesheet' type='text/css' href='chrome://devtools/skin/widg" +
   "ets.css'></head><body><div></div><span></span></body>";
 const {TreeWidget} = require("devtools/client/shared/widgets/TreeWidget");
 const Promise = require("promise");
 
 add_task(function*() {
   yield addTab("about:blank");
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
 
--- a/devtools/client/shared/test/doc_options-view.xul
+++ b/devtools/client/shared/test/doc_options-view.xul
@@ -1,14 +1,13 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <!DOCTYPE window []>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
     <popupset id="options-popupset">
         <menupopup id="options-menupopup" position="before_end">
--- a/devtools/client/shared/widgets/VariablesView.xul
+++ b/devtools/client/shared/widgets/VariablesView.xul
@@ -1,15 +1,14 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % viewDTD SYSTEM "chrome://devtools/locale/VariablesView.dtd">
   %viewDTD;
 ]>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="&PropertiesViewWindowTitle;">
 
--- a/devtools/client/shared/widgets/cubic-bezier-frame.xhtml
+++ b/devtools/client/shared/widgets/cubic-bezier-frame.xhtml
@@ -2,17 +2,16 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/content/shared/widgets/cubic-bezier.css" type="text/css"/>
   <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   <style>
     html, body {
       margin: 0;
       padding: 0;
       overflow: hidden;
       width: 500px;
--- a/devtools/client/shared/widgets/filter-frame.xhtml
+++ b/devtools/client/shared/widgets/filter-frame.xhtml
@@ -5,17 +5,16 @@
 <!DOCTYPE html [
   <!ENTITY % filterwidgetDTD SYSTEM "chrome://devtools/locale/filterwidget.dtd" >
   %filterwidgetDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     <link rel="stylesheet" href="chrome://devtools/content/shared/widgets/filter-widget.css" type="text/css"/>
-    <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"></script>
   </head>
   <body>
 
     <div id="container">
       <div class="filters-list">
         <div id="filters"></div>
         <div class="footer">
--- a/devtools/client/shared/widgets/graphs-frame.xhtml
+++ b/devtools/client/shared/widgets/graphs-frame.xhtml
@@ -2,17 +2,16 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/skin/widgets.css" ype="text/css"/>
   <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   <style>
     body {
       overflow: hidden;
       margin: 0;
       padding: 0;
       font-size: 0;
--- a/devtools/client/shared/widgets/mdn-docs-frame.xhtml
+++ b/devtools/client/shared/widgets/mdn-docs-frame.xhtml
@@ -2,17 +2,16 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/content/shared/widgets/mdn-docs.css" type="text/css"/>
   <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
 </head>
 <body class="theme-body">
 
   <div id = "container">
 
     <header>
--- a/devtools/client/shared/widgets/spectrum-frame.xhtml
+++ b/devtools/client/shared/widgets/spectrum-frame.xhtml
@@ -2,17 +2,16 @@
 <!-- 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/. -->
 <!DOCTYPE html>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/content/shared/widgets/spectrum.css" ype="text/css"/>
   <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   <style>
     body {
       margin: 0;
       padding: 0;
     }
   </style>
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -41,17 +41,16 @@ const L10N = Services.strings.createBund
 
 const { OS } = Services.appinfo;
 
 // CM_STYLES, CM_SCRIPTS and CM_IFRAME represent the HTML,
 // JavaScript and CSS that is injected into an iframe in
 // order to initialize a CodeMirror instance.
 
 const CM_STYLES = [
-  "chrome://devtools/skin/common.css",
   "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css",
   "chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css",
   "chrome://devtools/content/sourceeditor/codemirror/mozilla.css"
 ];
 
 const CM_SCRIPTS = [
   "chrome://devtools/content/shared/theme-switching.js",
   "chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.js",
--- a/devtools/client/storage/storage.xul
+++ b/devtools/client/storage/storage.xul
@@ -1,15 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/storage.css" type="text/css"?>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <script type="application/javascript;version=1.8"
--- a/devtools/client/styleeditor/styleeditor.xul
+++ b/devtools/client/styleeditor/styleeditor.xul
@@ -11,17 +11,16 @@
  %sourceEditorStrings;
 <!ENTITY % csscoverageDTD SYSTEM "chrome://devtools-shared/locale/csscoverage.dtd">
  %csscoverageDTD;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/splitview.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/splitview.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/styleeditor/styleeditor.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/styleeditor.css" type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <xul:window xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns="http://www.w3.org/1999/xhtml"
--- a/devtools/client/themes/dark-theme.css
+++ b/devtools/client/themes/dark-theme.css
@@ -1,14 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 @import url(variables.css);
+@import url(common.css);
 @import url(toolbars.css);
 
 body {
   margin: 0;
 }
 
 .theme-body {
   background: var(--theme-body-background);
--- a/devtools/client/themes/light-theme.css
+++ b/devtools/client/themes/light-theme.css
@@ -1,14 +1,15 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
 @import url(variables.css);
+@import url(common.css);
 @import url(toolbars.css);
 
 body {
   margin: 0;
 }
 
 .theme-body {
   background: var(--theme-body-background);
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -314,17 +314,17 @@
 
 .theme-light .ruleview-overridden {
   text-decoration-color: var(--theme-content-color3);
 }
 
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
   padding: 0;
-  margin: -1px;
+  margin: -1px -3px -1px -1px;
 }
 
 .ruleview-property {
   border-left: 3px solid transparent;
   clear: right;
 }
 
 .ruleview-propertycontainer  > * {
--- a/devtools/client/webaudioeditor/webaudioeditor.xul
+++ b/devtools/client/webaudioeditor/webaudioeditor.xul
@@ -1,15 +1,14 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/content/shared/widgets/widgets.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/webaudioeditor.css" type="text/css"?>
 <!DOCTYPE window [
   <!ENTITY % debuggerDTD SYSTEM "chrome://devtools/locale/webaudioeditor.dtd">
   %debuggerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -7,16 +7,17 @@
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
 var WebConsoleUtils = require("devtools/shared/webconsole/utils").Utils;
 var Heritage = require("sdk/core/heritage");
 var {TargetFactory} = require("devtools/client/framework/target");
 var {Tools} = require("devtools/client/definitions");
+const { Task } = require("resource://gre/modules/Task.jsm");
 var promise = require("promise");
 
 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
 loader.lazyRequireGetter(this, "WebConsoleFrame", "devtools/client/webconsole/webconsole", true);
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
@@ -582,29 +583,28 @@ WebConsole.prototype = {
     if (this.chromeUtilsWindow && this.mainPopupSet) {
       let popupset = this.mainPopupSet;
       let panels = popupset.querySelectorAll("panel[hudId=" + this.hudId + "]");
       for (let panel of panels) {
         panel.hidePopup();
       }
     }
 
-    let onDestroy = function WC_onDestroyUI() {
+    let onDestroy = Task.async(function*() {
       try {
-        let tabWindow = this.target.isLocalTab ? this.target.window : null;
-        tabWindow && tabWindow.focus();
+        yield this.target.activeTab.focus()
       }
       catch (ex) {
         // Tab focus can fail if the tab or target is closed.
       }
 
       let id = WebConsoleUtils.supportsString(this.hudId);
       Services.obs.notifyObservers(id, "web-console-destroyed", null);
       this._destroyer.resolve(null);
-    }.bind(this);
+    }.bind(this));
 
     if (this.ui) {
       this.ui.destroy().then(onDestroy);
     }
     else {
       onDestroy();
     }
 
--- a/devtools/client/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
+++ b/devtools/client/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
@@ -10,21 +10,21 @@
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/test-console.html";
 
 add_task(function*() {
   yield loadTab(TEST_URI);
   let hud = yield openConsole();
   hud.jsterm.clearOutput();
 
-  let console = content.console;
-
-  for (let i = 0; i < 50; i++) {
-    console.log("http://www.example.com/ " + i);
-  }
+  ContentTask.spawn(gBrowser.selectedBrowser, {}, function*() {
+    for (let i = 0; i < 50; i++) {
+      content.console.log("http://www.example.com/ " + i);
+    }
+  });
 
   yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "http://www.example.com/ 49",
       category: CATEGORY_WEBDEV,
       severity: SEVERITY_LOG,
     }],
--- a/devtools/client/webconsole/webconsole.xul
+++ b/devtools/client/webconsole/webconsole.xul
@@ -2,18 +2,16 @@
 <!-- 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/. -->
 <!DOCTYPE window [
 <!ENTITY % webConsoleDTD SYSTEM "chrome://devtools/locale/webConsole.dtd">
 %webConsoleDTD;
 ]>
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/common.css"
-                 type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/widgets.css"
                  type="text/css"?>
 <?xml-stylesheet href="chrome://devtools/skin/webconsole.css"
                  type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         id="devtools-webconsole"
         macanimationtype="document"
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -1395,16 +1395,26 @@ TabActor.prototype = {
     if (!this._detach()) {
       return { error: "wrongState" };
     }
 
     return { type: "detached" };
   },
 
   /**
+   * Bring the tab's window to front.
+   */
+  onFocus: function() {
+    if (this.window) {
+      this.window.focus();
+    }
+    return {};
+  },
+
+  /**
    * Reload the page in this tab.
    */
   onReload: function(aRequest) {
     let force = aRequest && aRequest.options && aRequest.options.force;
     // Wait a tick so that the response packet can be dispatched before the
     // subsequent navigation event packet.
     Services.tm.currentThread.dispatch(DevToolsUtils.makeInfallible(() => {
       // This won't work while the browser is shutting down and we don't really
@@ -1895,16 +1905,17 @@ TabActor.prototype = {
 };
 
 /**
  * The request types this actor can handle.
  */
 TabActor.prototype.requestTypes = {
   "attach": TabActor.prototype.onAttach,
   "detach": TabActor.prototype.onDetach,
+  "focus": TabActor.prototype.onFocus,
   "reload": TabActor.prototype.onReload,
   "navigateTo": TabActor.prototype.onNavigateTo,
   "reconfigure": TabActor.prototype.onReconfigure,
   "switchToFrame": TabActor.prototype.onSwitchToFrame,
   "listFrames": TabActor.prototype.onListFrames,
   "listWorkers": TabActor.prototype.onListWorkers
 };
 
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -417,44 +417,26 @@ DevToolsLoader.prototype = {
     } else {
       this.setProvider(new BuiltinProvider());
     }
   },
 
   /**
    * Reload the current provider.
    */
-  reload: function(showToolbox) {
+  reload: function() {
     var events = this.require("sdk/system/events");
     events.emit("startupcache-invalidate", {});
     events.emit("devtools-unloaded", {});
 
     this._provider.unload("reload");
     delete this._provider;
     delete this._mainid;
     this._chooseProvider();
     this.main("devtools/client/main");
-
-    let window = Services.wm.getMostRecentWindow(null);
-    let location = window.location.href;
-    if (location.includes("/browser.xul") && showToolbox) {
-      // Reopen the toolbox automatically if we are reloading from toolbox shortcut
-      // and are on a browser window.
-      // Wait for a second before opening the toolbox to avoid races
-      // between the old and the new one.
-      let {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
-      setTimeout(() => {
-        let { gBrowser } = window;
-        let target = this.TargetFactory.forTab(gBrowser.selectedTab);
-        const { gDevTools } = require("devtools/client/framework/devtools");
-        gDevTools.showToolbox(target);
-      }, 1000);
-    } else if (location.includes("/webide.xul")) {
-      window.location.reload();
-    }
   },
 
   /**
    * Sets whether the compartments loaded by this instance should be invisible
    * to the debugger.  Invisibility is needed for loaders that support debugging
    * of chrome code.  This is true of remote target environments, like Fennec or
    * B2G.  It is not the default case for desktop Firefox because we offer the
    * Browser Toolbox for chrome debugging there, which uses its own, separate
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -1311,16 +1311,23 @@ TabClient.prototype = {
     after: function (aResponse) {
       this.client.unregisterClient(this);
       return aResponse;
     },
     telemetry: "TABDETACH"
   }),
 
   /**
+   * Bring the window to the front.
+   */
+  focus: DebuggerClient.requester({
+    type: "focus"
+  }, {}),
+
+  /**
    * Reload the page in this tab.
    *
    * @param [optional] object options
    *        An object with a `force` property indicating whether or not
    *        this reload should skip the cache
    */
   reload: function(options = { force: false }) {
     return this._reload(options);
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -9,16 +9,17 @@
 #include <google/protobuf/io/gzip_stream.h>
 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 
 #include "js/Debug.h"
 #include "js/TypeDecls.h"
 #include "js/UbiNodeBreadthFirst.h"
 #include "js/UbiNodeCensus.h"
 #include "js/UbiNodeDominatorTree.h"
+#include "js/UbiNodeShortestPaths.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/devtools/AutoMemMap.h"
 #include "mozilla/devtools/CoreDump.pb.h"
 #include "mozilla/devtools/DeserializedNode.h"
 #include "mozilla/devtools/DominatorTree.h"
 #include "mozilla/devtools/FileDescriptorOutputStream.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
@@ -49,16 +50,17 @@ using namespace JS;
 using namespace dom;
 
 using ::google::protobuf::io::ArrayInputStream;
 using ::google::protobuf::io::CodedInputStream;
 using ::google::protobuf::io::GzipInputStream;
 using ::google::protobuf::io::ZeroCopyInputStream;
 
 using JS::ubi::AtomOrTwoByteChars;
+using JS::ubi::ShortestPaths;
 
 MallocSizeOf
 GetCurrentThreadDebuggerMallocSizeOf()
 {
   auto ccrt = CycleCollectedJSRuntime::Get();
   MOZ_ASSERT(ccrt);
   auto rt = ccrt->Runtime();
   MOZ_ASSERT(rt);
@@ -567,24 +569,160 @@ HeapSnapshot::ComputeDominatorTree(Error
     auto ccrt = CycleCollectedJSRuntime::Get();
     MOZ_ASSERT(ccrt);
     auto rt = ccrt->Runtime();
     MOZ_ASSERT(rt);
     JS::AutoCheckCannotGC nogc(rt);
     maybeTree = JS::ubi::DominatorTree::Create(rt, nogc, getRoot());
   }
 
-  if (maybeTree.isNothing()) {
+  if (NS_WARN_IF(maybeTree.isNothing())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
   return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
 }
 
+void
+HeapSnapshot::ComputeShortestPaths(JSContext*cx, uint64_t start,
+                                   const Sequence<uint64_t>& targets,
+                                   uint64_t maxNumPaths,
+                                   JS::MutableHandleObject results,
+                                   ErrorResult& rv)
+{
+  // First ensure that our inputs are valid.
+
+  if (NS_WARN_IF(maxNumPaths == 0)) {
+    rv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+
+  Maybe<JS::ubi::Node> startNode = getNodeById(start);
+  if (NS_WARN_IF(startNode.isNothing())) {
+    rv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+
+  if (NS_WARN_IF(targets.Length() == 0)) {
+    rv.Throw(NS_ERROR_INVALID_ARG);
+    return;
+  }
+
+  // Aggregate the targets into a set and make sure that they exist in the heap
+  // snapshot.
+
+  JS::ubi::NodeSet targetsSet;
+  if (NS_WARN_IF(!targetsSet.init())) {
+    rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  for (const auto& target : targets) {
+    Maybe<JS::ubi::Node> targetNode = getNodeById(target);
+    if (NS_WARN_IF(targetNode.isNothing())) {
+      rv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+
+    if (NS_WARN_IF(!targetsSet.put(*targetNode))) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+  }
+
+  // Walk the heap graph and find the shortest paths.
+
+  Maybe<ShortestPaths> maybeShortestPaths;
+  {
+    auto ccrt = CycleCollectedJSRuntime::Get();
+    MOZ_ASSERT(ccrt);
+    auto rt = ccrt->Runtime();
+    MOZ_ASSERT(rt);
+    JS::AutoCheckCannotGC nogc(rt);
+    maybeShortestPaths = ShortestPaths::Create(rt, nogc, maxNumPaths, *startNode,
+                                               Move(targetsSet));
+  }
+
+  if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
+    rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  auto& shortestPaths = *maybeShortestPaths;
+
+  // Convert the results into a Map object mapping target node IDs to arrays of
+  // paths found.
+
+  RootedObject resultsMap(cx, JS::NewMapObject(cx));
+  if (NS_WARN_IF(!resultsMap)) {
+    rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+
+  for (auto range = shortestPaths.eachTarget(); !range.empty(); range.popFront()) {
+    JS::RootedValue key(cx, JS::NumberValue(range.front().identifier()));
+    JS::AutoValueVector paths(cx);
+
+    bool ok = shortestPaths.forEachPath(range.front(), [&](JS::ubi::Path& path) {
+      JS::AutoValueVector pathValues(cx);
+
+      for (JS::ubi::BackEdge* edge : path) {
+        JS::RootedObject pathPart(cx, JS_NewPlainObject(cx));
+        if (!pathPart) {
+          return false;
+        }
+
+        JS::RootedValue predecessor(cx, NumberValue(edge->predecessor().identifier()));
+        if (!JS_DefineProperty(cx, pathPart, "predecessor", predecessor, JSPROP_ENUMERATE)) {
+          return false;
+        }
+
+        RootedValue edgeNameVal(cx, NullValue());
+        if (edge->name()) {
+          RootedString edgeName(cx, JS_AtomizeUCString(cx, edge->name().get()));
+          if (!edgeName) {
+            return false;
+          }
+          edgeNameVal = StringValue(edgeName);
+        }
+
+        if (!JS_DefineProperty(cx, pathPart, "edge", edgeNameVal, JSPROP_ENUMERATE)) {
+          return false;
+        }
+
+        if (!pathValues.append(ObjectValue(*pathPart))) {
+          return false;
+        }
+      }
+
+      RootedObject pathObj(cx, JS_NewArrayObject(cx, pathValues));
+      return pathObj && paths.append(ObjectValue(*pathObj));
+    });
+
+    if (NS_WARN_IF(!ok)) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    JS::RootedObject pathsArray(cx, JS_NewArrayObject(cx, paths));
+    if (NS_WARN_IF(!pathsArray)) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    JS::RootedValue pathsVal(cx, ObjectValue(*pathsArray));
+    if (NS_WARN_IF(!JS::MapSet(cx, resultsMap, key, pathsVal))) {
+      rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+  }
+
+  results.set(resultsMap);
+}
 
 /*** Saving Heap Snapshots ************************************************************************/
 
 // If we are only taking a snapshot of the heap affected by the given set of
 // globals, find the set of zones the globals are allocated within. Returns
 // false on OOM failure.
 static bool
 PopulateZonesWithGlobals(ZoneSet& zones, AutoObjectVector& globals)
--- a/devtools/shared/heapsnapshot/HeapSnapshot.h
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -159,16 +159,22 @@ public:
   void TakeCensus(JSContext* cx, JS::HandleObject options,
                   JS::MutableHandleValue rval, ErrorResult& rv);
 
   void DescribeNode(JSContext* cx, JS::HandleObject breakdown, uint64_t nodeId,
                     JS::MutableHandleValue rval, ErrorResult& rv);
 
   already_AddRefed<DominatorTree> ComputeDominatorTree(ErrorResult& rv);
 
+  void ComputeShortestPaths(JSContext*cx, uint64_t start,
+                            const dom::Sequence<uint64_t>& targets,
+                            uint64_t maxNumPaths,
+                            JS::MutableHandleObject results,
+                            ErrorResult& rv);
+
   dom::Nullable<uint64_t> GetCreationTime() {
     static const uint64_t maxTime = uint64_t(1) << 53;
     if (timestamp.isSome() && timestamp.ref() <= maxTime) {
       return dom::Nullable<uint64_t>(timestamp.ref());
     }
 
     return dom::Nullable<uint64_t>();
   }
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_01.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Sanity test that we can compute shortest paths.
+//
+// Because the actual heap graph is too unpredictable and likely to drastically
+// change as various implementation bits change, we don't test exact paths
+// here. See js/src/jsapi-tests/testUbiNode.cpp for such tests, where we can
+// control the specific graph shape and structure and so testing exact paths is
+// reliable.
+
+function run_test() {
+  const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+  const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+  const dominatorTree = snapshot.computeDominatorTree();
+  const dominatedByRoot = dominatorTree.getImmediatelyDominated(dominatorTree.root)
+                                       .slice(0, 10);
+  ok(dominatedByRoot);
+  ok(dominatedByRoot.length);
+
+  const targetSet = new Set(dominatedByRoot);
+
+  const shortestPaths = snapshot.computeShortestPaths(dominatorTree.root, dominatedByRoot, 2);
+  ok(shortestPaths);
+  ok(shortestPaths instanceof Map);
+  ok(shortestPaths.size === targetSet.size);
+
+  for (let [target, paths] of shortestPaths) {
+    ok(targetSet.has(target),
+       "We should only get paths for our targets");
+    targetSet.delete(target);
+
+    ok(paths.length > 0,
+       "We must have at least one path, since the target is dominated by the root");
+    ok(paths.length <= 2,
+       "Should not have recorded more paths than the max requested");
+
+    dumpn("---------------------");
+    dumpn("Shortest paths for 0x" + target.toString(16) + ":");
+    for (let path of paths) {
+      dumpn("    path =");
+      for (let part of path) {
+        dumpn("        predecessor: 0x" + part.predecessor.toString(16) +
+              "; edge: " + part.edge);
+      }
+    }
+    dumpn("---------------------");
+
+    for (let path of paths) {
+      ok(path.length > 0, "Cannot have zero length paths");
+      ok(path[0].predecessor === dominatorTree.root,
+         "The first predecessor is always our start node");
+
+      for (let part of path) {
+        ok(part.predecessor, "Each part of a path has a predecessor");
+        ok(!!snapshot.describeNode({ by: "count", count: true, bytes: true},
+                                   part.predecessor),
+           "The predecessor is in the heap snapshot");
+        ok("edge" in part, "Each part has an (potentially null) edge property");
+      }
+    }
+  }
+
+  ok(targetSet.size === 0,
+     "We found paths for all of our targets");
+
+  do_test_finished();
+}
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_02.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test computing shortest paths with invalid arguments.
+
+function run_test() {
+  const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+  const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+  const dominatorTree = snapshot.computeDominatorTree();
+  const target = dominatorTree.getImmediatelyDominated(dominatorTree.root).pop();
+  ok(target);
+
+  let threw = false;
+  try {
+    snapshot.computeShortestPaths(0, [target], 2);
+  } catch (_) {
+    threw = true;
+  }
+  ok(threw, "invalid start node should throw");
+
+  threw = false;
+  try {
+    snapshot.computeShortestPaths(dominatorTree.root, [0], 2);
+  } catch (_) {
+    threw = true;
+  }
+  ok(threw, "invalid target nodes should throw");
+
+  threw = false;
+  try {
+    snapshot.computeShortestPaths(dominatorTree.root, [], 2);
+  } catch (_) {
+    threw = true;
+  }
+  ok(threw, "empty target nodes should throw");
+
+  threw = false;
+  try {
+    snapshot.computeShortestPaths(dominatorTree.root, [target], 0);
+  } catch (_) {
+    threw = true;
+  }
+  ok(threw, "0 max paths should throw");
+
+  do_test_finished();
+}
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -61,16 +61,18 @@ support-files =
 [test_HeapAnalyses_takeCensus_03.js]
 [test_HeapAnalyses_takeCensus_04.js]
 [test_HeapAnalyses_takeCensus_05.js]
 [test_HeapAnalyses_takeCensus_06.js]
 [test_HeapAnalyses_takeCensus_07.js]
 [test_HeapSnapshot_creationTime_01.js]
 [test_HeapSnapshot_deepStack_01.js]
 [test_HeapSnapshot_describeNode_01.js]
+[test_HeapSnapshot_computeShortestPaths_01.js]
+[test_HeapSnapshot_computeShortestPaths_02.js]
 [test_HeapSnapshot_takeCensus_01.js]
 [test_HeapSnapshot_takeCensus_02.js]
 [test_HeapSnapshot_takeCensus_03.js]
 [test_HeapSnapshot_takeCensus_04.js]
 [test_HeapSnapshot_takeCensus_05.js]
 [test_HeapSnapshot_takeCensus_06.js]
 [test_HeapSnapshot_takeCensus_07.js]
 [test_HeapSnapshot_takeCensus_08.js]
--- a/dom/webidl/HeapSnapshot.webidl
+++ b/dom/webidl/HeapSnapshot.webidl
@@ -70,9 +70,36 @@ interface HeapSnapshot {
 
   /**
    * Compute the dominator tree for this heap snapshot.
    *
    * @see DominatorTree.webidl
    */
   [Throws]
   DominatorTree computeDominatorTree();
+
+  /**
+   * Find the shortest retaining paths from the node associated with the ID
+   * `start` to each node associated with the IDs in `targets`. Find at most
+   * `maxNumPaths` retaining paths for each target node.
+   *
+   * The return value is a Map object mapping from each target node ID to an
+   * array of retaining paths. The array may be empty if we did not find any
+   * retaining paths.
+   *
+   * A path is an array of objects of the form:
+   *
+   *     {
+   *         predecessor: <node ID>,
+   *         edge: <string or null>,
+   *     }
+   *
+   * The first `predecessor` will always be `start`. The last edge in the path
+   * leads to the `target` node that is mapped to the path; the `target` does
+   * not appear as a `predecessor` in the path.
+   *
+   * Throws when `start` or any of the elements of `targets` are not an ID of a
+   * node in the snapshot, or if we encounter an out of memory exception.
+   */
+  [Throws]
+  object computeShortestPaths(NodeId start, sequence<NodeId> targets,
+                              unsigned long long maxNumPaths);
 };
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -855,17 +855,17 @@ pref("general.useragent.updates.retry", 
 pref("browser.ui.linkify.phone", false);
 
 // Enables/disables Spatial Navigation
 pref("snav.enabled", true);
 
 // This url, if changed, MUST continue to point to an https url. Pulling arbitrary content to inject into
 // this page over http opens us up to a man-in-the-middle attack that we'd rather not face. If you are a downstream
 // repackager of this code using an alternate snippet url, please keep your users safe
-pref("browser.snippets.updateUrl", "https://snippets.mozilla.com/json/%SNIPPETS_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
+pref("browser.snippets.updateUrl", "https://snippets.cdn.mozilla.net/json/%SNIPPETS_VERSION%/%NAME%/%VERSION%/%APPBUILDID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
 
 // How frequently we check for new snippets, in seconds (1 day)
 pref("browser.snippets.updateInterval", 86400);
 
 // URL used to check for user's country code
 pref("browser.snippets.geoUrl", "https://geo.mozilla.org/country.json");
 
 // URL used to ping metrics with stats about which snippets have been shown
new file mode 100644
--- /dev/null
+++ b/mobile/android/config/mozconfigs/android-api-15-frontend/nightly
@@ -0,0 +1,39 @@
+# Many things aren't appropriate for a frontend-only build.
+MOZ_AUTOMATION_BUILD_SYMBOLS=0
+MOZ_AUTOMATION_INSTALLER=0
+MOZ_AUTOMATION_L10N_CHECK=0
+MOZ_AUTOMATION_PACKAGE=0
+MOZ_AUTOMATION_PACKAGE_TESTS=0
+MOZ_AUTOMATION_SDK=0
+MOZ_AUTOMATION_UPDATE_PACKAGING=0
+MOZ_AUTOMATION_UPLOAD=0
+MOZ_AUTOMATION_UPLOAD_SYMBOLS=0
+
+. "$topsrcdir/mobile/android/config/mozconfigs/common"
+
+ac_add_options --with-gradle="$topsrcdir/gradle/bin/gradle"
+export GRADLE_MAVEN_REPOSITORY="file://$topsrcdir/jcentral"
+
+ac_add_options --disable-compile-environment
+ac_add_options --disable-tests
+
+# From here on, like ../android-api-15/nightly.
+
+ac_add_options --enable-profiling
+
+# Android
+ac_add_options --with-android-min-sdk=15
+ac_add_options --target=arm-linux-androideabi
+
+ac_add_options --with-branding=mobile/android/branding/nightly
+
+# This will overwrite the default of stripping everything and keep the symbol table.
+# This is useful for profiling with eideticker. See bug 788680
+STRIP_FLAGS="--strip-debug"
+
+export MOZILLA_OFFICIAL=1
+export MOZ_TELEMETRY_REPORTING=1
+
+MOZ_ANDROID_GECKOLIBS_AAR=1
+
+. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
new file mode 100644
--- /dev/null
+++ b/mobile/android/config/tooltool-manifests/android-frontend/releng.manifest
@@ -0,0 +1,42 @@
+[
+{
+"size": 535625068,
+"visibility": "internal",
+"digest": "0627515046a23c1d109e2782865b1b3b546c1d552955e4156317f76cbb195eb630aa25feea3f4edd1c685f129da0c2a5169d4d6349c1c31d8a95158a4569a478",
+"algorithm": "sha512",
+"filename": "android-sdk-linux.tar.xz",
+"unpack": true
+},
+{
+"size": 167175,
+"visibility": "public",
+"digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
+"algorithm": "sha512",
+"filename": "sccache.tar.bz2",
+"unpack": true
+},
+{
+"size": 31013068,
+"visibility": "public",
+"digest": "e30a26f98a3448064857491aee1a7a26f98494f86a89113de9be17c37c8181ed60250706fed881ec1f035002fcdaf8b9b4a7d9ae70ce40acff2f1acfbb40f8d9",
+"algorithm": "sha512",
+"filename": "java_home-1.7.0-openjdk-1.7.0.85.x86_64.tar.xz",
+"unpack": true
+},
+{
+"algorithm": "sha512",
+"visibility": "public",
+"filename": "jcentral.tar.xz",
+"unpack": true,
+"digest": "b5d85a917785e1c034318f7495fef27a6274b04d8640245726b0cf1331b7ac374f5757868901c3fadd930bf10603173a706be653d769dde8ddfdb8673b143363",
+"size": 38596168
+},
+{
+"algorithm": "sha512",
+"visibility": "public",
+"filename": "gradle.tar.xz",
+"unpack": true,
+"digest": "ef1d0038da879cc6840fced87671f8f6a18c51375498804f64d21fa48d7089ded4da2be36bd06a1457083e9110e59c0884f1e074dc609d29617c131caea8f234",
+"size": 50542140
+}
+]
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -111,8 +111,14 @@ fi
 
 # Enable checking that add-ons are signed by the trusted root
 MOZ_ADDON_SIGNING=1
 
 # Enable the Switchboard A/B framework code.
 # Note: The framework is always included in the app. This flag controls
 # usage of the framework.
 MOZ_SWITCHBOARD=1
+
+# Enable DLC background service and stop shipping fonts in Nightly
+if test "$NIGHTLY_BUILD"; then
+  MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE=1
+  MOZ_ANDROID_EXCLUDE_FONTS=1
+fi
\ No newline at end of file
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -52,17 +52,17 @@ class MachCommands(MachCommandBase):
     @Command('gradle', category='devenv',
         description='Run gradle.',
         conditions=[conditions.is_android])
     @CommandArgument('args', nargs=argparse.REMAINDER)
     def gradle(self, args):
         # Avoid logging the command
         self.log_manager.terminal_handler.setLevel(logging.CRITICAL)
 
-        return self.run_process(['./gradlew'] + args,
+        return self.run_process([self.substs['GRADLE']] + args,
             pass_thru=True, # Allow user to run gradle interactively.
             ensure_exit_code=False, # Don't throw on non-zero exit code.
             cwd=mozpath.join(self.topsrcdir))
 
     @Command('gradle-install', category='devenv',
         conditions=[REMOVED])
     def gradle_install(self):
         pass
--- a/python/mach_commands.py
+++ b/python/mach_commands.py
@@ -214,19 +214,21 @@ class MachCommands(MachCommandBase):
         eslint for optimal use on Mozilla projects.
         """
         sys.path.append(os.path.dirname(__file__))
 
         npmPath = self.getNodeOrNpmPath("npm")
         if not npmPath:
             return 1
 
-        # Install eslint.
+        # Install eslint 1.10.3.
+        # Note that that's the version currently compatible with the mozilla
+        # eslint plugin.
         success = self.callProcess("eslint",
-                                   [npmPath, "install", "eslint", "-g"])
+                                   [npmPath, "install", "eslint@1.10.3", "-g"])
         if not success:
             return 1
 
         # Install eslint-plugin-mozilla.
         success = self.callProcess("eslint-plugin-mozilla",
                                    [npmPath, "link"],
                                    "testing/eslint-plugin-mozilla")
         if not success:
--- a/services/sync/tests/unit/head_appinfo.js
+++ b/services/sync/tests/unit/head_appinfo.js
@@ -13,35 +13,38 @@ gSyncProfile = do_get_profile();
 var fhs = Cc["@mozilla.org/satchel/form-history-startup;1"]
             .getService(Ci.nsIObserver);
 fhs.observe(null, "profile-after-change", null);
 
 // An app is going to have some prefs set which xpcshell tests don't.
 Services.prefs.setCharPref("identity.sync.tokenserver.uri", "http://token-server");
 
 // Make sure to provide the right OS so crypto loads the right binaries
-var OS = "XPCShell";
-if (mozinfo.os == "win")
-  OS = "WINNT";
-else if (mozinfo.os == "mac")
-  OS = "Darwin";
-else
-  OS = "Linux";
+function getOS() {
+  switch (mozinfo.os) {
+    case "win":
+      return "WINNT";
+    case "mac":
+      return "Darwin";
+    default:
+      return "Linux";
+  }
+}
 
 var XULAppInfo = {
   vendor: "Mozilla",
   name: "XPCShell",
   ID: "xpcshell@tests.mozilla.org",
   version: "1",
   appBuildID: "20100621",
   platformVersion: "",
   platformBuildID: "20100621",
   inSafeMode: false,
   logConsoleErrors: true,
-  OS: OS,
+  OS: getOS(),
   XPCOMABI: "noarch-spidermonkey",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]),
   invalidateCachesOnRestart: function invalidateCachesOnRestart() { }
 };
 
 var XULAppInfoFactory = {
   createInstance: function (outer, iid) {
     if (outer != null)
copy from testing/docker/desktop-build/Dockerfile
copy to testing/docker/android-gradle-build/Dockerfile
--- a/testing/docker/desktop-build/Dockerfile
+++ b/testing/docker/android-gradle-build/Dockerfile
@@ -1,29 +1,57 @@
-FROM          taskcluster/centos6-build-upd:0.1.3.20160122142300
-MAINTAINER    Dustin J. Mitchell <dustin@mozilla.com>
+FROM          centos:centos7
+MAINTAINER    Nick Alexander <nalexander@mozilla.com>
+
+# Reset user/workdir from parent image so we can install software.
+WORKDIR /
+USER root
+
+# Update base.
+RUN yum upgrade -y
+
+# Install JDK and Sonatype Nexus.  Cribbed directly from
+# https://github.com/sonatype/docker-nexus/blob/fffd2c61b2368292040910c055cf690c8e76a272/oss/Dockerfile.
 
-# Add build scripts; these are the entry points from the taskcluster worker, and
-# operate on environment variables
-ADD             bin /home/worker/bin
-RUN             chmod +x /home/worker/bin/*
+RUN yum install -y \
+  createrepo \
+  curl \
+  java-1.7.0-openjdk-devel \
+  java-1.7.0-openjdk \
+  sudo \
+  tar \
+  unzip \
+  wget \
+  zip \
+  && yum clean all
 
-# Generate machine uuid file
-RUN dbus-uuidgen --ensure=/var/lib/dbus/machine-id
+ENV NEXUS_VERSION 2.12.0-01
+
+RUN mkdir -p /opt/sonatype/nexus
 
-# Stubbed out credentials; mozharness looks for this file an issues a WARNING
-# if it's not found, which causes the build to fail.  Note that this needs to
-# be in the parent of the workspace directory and in the directory where
-# mozharness is run (not its --work-dir).  See Bug 1169652.
-ADD           oauth.txt /home/worker/
+WORKDIR /tmp
+RUN curl --fail --silent --location --retry 3 \
+    https://download.sonatype.com/nexus/oss/nexus-${NEXUS_VERSION}-bundle.tar.gz \
+    -o /tmp/nexus-${NEXUS_VERSION}-bundle.tar.gz
+RUN curl --fail --silent --location --retry 3 \
+    https://download.sonatype.com/nexus/oss/nexus-${NEXUS_VERSION}-bundle.tar.gz.sha1 \
+    -o /tmp/nexus-${NEXUS_VERSION}-bundle.tar.gz.sha1
+
+RUN echo $(cat nexus-${NEXUS_VERSION}-bundle.tar.gz.sha1) nexus-${NEXUS_VERSION}-bundle.tar.gz | sha1sum -c
 
-# stubbed out buildprops, which keeps mozharness from choking
-# Note that this needs to be in the parent of the workspace directory and in
-# the directory where mozharness is run (not its --work-dir)
-ADD           buildprops.json /home/worker/
+RUN tar zxf nexus-${NEXUS_VERSION}-bundle.tar.gz \
+  && mv /tmp/nexus-${NEXUS_VERSION}/* /opt/sonatype/nexus/ \
+  && rm -rf /tmp/nexus-${NEXUS_VERSION} \
+  && rm -rf /tmp/nexus-${NEXUS_VERSION}-bundle.tar.gz
 
-# install tooltool directly from github where tooltool_wrapper.sh et al. expect
-# to find it
-RUN wget -O /builds/tooltool.py https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py
-RUN chmod +x /builds/tooltool.py
+# Install tooltool directly from github.
+RUN mkdir /build
+ADD https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py /build/tooltool.py
+RUN chmod +rx /build/tooltool.py
 
-# Set a default command useful for debugging
-CMD ["/bin/bash", "--login"]
+# Add build scripts.
+ADD             build.sh /build/
+RUN             chmod +x /build/*
+
+ADD project /project
+
+# Invoke our build scripts by default, but allow other commands.
+CMD /build/build.sh
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/README.md
@@ -0,0 +1,2 @@
+This is a docker script for fetching Android Gradle dependenices for
+use in Mozilla's build clusters.
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/REGISTRY
@@ -0,0 +1,1 @@
+taskcluster
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/VERSION
@@ -0,0 +1,1 @@
+0.0.1
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/build.sh
@@ -0,0 +1,62 @@
+#!/bin/bash -vex
+
+set -x -e
+
+: WORKSPACE ${WORKSPACE:=/workspace}
+: GRADLE_VERSION ${GRADLE_VERSION:=2.7}
+
+set -v
+
+# Frowned upon, but simplest.
+RUN_AS_USER=root NEXUS_WORK=${WORKSPACE}/nexus /opt/sonatype/nexus/bin/nexus start
+
+# Wait "a while" for Nexus to actually start.  Don't fail if this fails.
+wget --quiet --retry-connrefused --waitretry=2 --tries=100 \
+  http://localhost:8081/nexus/service/local/status || true
+rm -rf status
+
+# Verify Nexus has actually started.  Fail if this fails.
+curl --fail --silent --location http://localhost:8081/nexus/service/local/status | grep '<state>STARTED</state>'
+
+export JAVA_HOME=/usr/lib/jvm/jre-1.7.0-openjdk
+
+pushd /project
+./gradlew tasks
+popd
+
+# Package everything up.
+pushd ${WORKSPACE}
+# Not yet.  See notes on tooltool below.
+# cp -R /root/.android-sdk android-sdk-linux
+# tar cJf android-sdk-linux.tar.xz android-sdk-linux
+
+cp -R /workspace/nexus/storage/central jcentral
+tar cJf jcentral.tar.xz jcentral
+
+# The Gradle wrapper will have downloaded and verified the hash of exactly one
+# Gradle distribution.  It will be located at
+# ~/.gradle/wrapper/dists/gradle-2.7-all/$PROJECT_HASH/gradle-2.7-all.zip.  We
+# want to remove the version from the internal directory for use via tooltool
+# in a mozconfig.
+cp ~/.gradle/wrapper/dists/gradle-${GRADLE_VERSION}-all/*/gradle-${GRADLE_VERSION}-all.zip gradle-${GRADLE_VERSION}-all.zip
+unzip -q gradle-${GRADLE_VERSION}-all.zip
+mv gradle-${GRADLE_VERSION} gradle
+tar cJf gradle.tar.xz gradle
+
+mkdir -p /artifacts
+# We can't redistribute the Android SDK publicly just yet.  We'll
+# upload to (internal) tooltool eventually.  mv
+# android-sdk-linux.tar.xz /artifacts
+mv jcentral.tar.xz /artifacts
+mv gradle.tar.xz /artifacts
+popd
+
+# Bug 1245170: at some point in the future, we'll be able to upload
+# things directly to tooltool.
+# pushd /artifacts
+# /build/tooltool.py add --visibility=public jcentral.tar.xz
+# /build/tooltool.py add --visibility=public gradle.tar.xz
+# /build/tooltool.py add --visibility=internal android-sdk-linux.tar.xz
+# /build/tooltool.py upload -v --url=http://relengapi/tooltool/ \
+#   --message="No message - Gradle and jcentral archives uploaded from taskcluster."
+# popd
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/project/README.md
@@ -0,0 +1,6 @@
+This Gradle project exists only to list dependencies used to build Firefox for
+Android.  Eventually, this definition will live in ``mobile/android``, or it
+will just *be* ``mobile/android/app/build.gradle``.
+
+Until we determine the best Docker and Taskcluster patterns for dependency
+gathering and toolchain producing like this, this can live here.
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/project/build.gradle
@@ -0,0 +1,73 @@
+buildscript {
+  repositories {
+    maven {
+        url 'http://localhost:8081/nexus/content/repositories/central/'
+    }
+
+    maven {
+        url 'https://jitpack.io'
+    }
+  }
+
+  dependencies {
+    classpath 'com.android.tools.build:gradle:1.3.0'
+    classpath('com.stanfy.spoon:spoon-gradle-plugin:1.0.4') {
+        // Without these, we get errors linting.
+        exclude module: 'guava'
+    }
+
+    // See
+    // https://github.com/JakeWharton/sdk-manager-plugin/issues/73#issuecomment-106747867
+    // and
+    // https://github.com/JakeWharton/sdk-manager-plugin/issues/99#issuecomment-166438751.
+    classpath 'com.github.JakeWharton:sdk-manager-plugin:220bf7a88a7072df3ed16dc8466fb144f2817070'
+  }
+}
+
+allprojects {
+    repositories {
+        maven {
+            url 'http://localhost:8081/nexus/content/repositories/central/'
+        }
+    }
+}
+
+apply plugin: 'android-sdk-manager'
+
+// Optionally, require an emulator.
+// sdkManager {
+//   emulatorVersion 'android-19'
+//   emulatorArchitecture 'armeabi-v7a' // Optional, defaults to ARM.
+// }
+
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "23.0.1"
+
+    defaultConfig {
+        targetSdkVersion 23
+        minSdkVersion 15
+    }
+}
+
+// These are extracted from mobile/android/**/*.gradle.
+dependencies {
+    compile 'com.android.support:appcompat-v7:23.0.1'
+    compile 'com.android.support:design:23.0.1'
+    compile 'com.android.support:mediarouter-v7:23.0.1'
+    compile 'com.android.support:recyclerview-v7:23.0.1'
+    compile 'com.android.support:support-v4:23.0.1'
+    compile 'com.google.android.gms:play-services-base:8.1.0'
+    compile 'com.google.android.gms:play-services-basement:8.1.0'
+    compile 'com.google.android.gms:play-services-cast:8.1.0'
+    compile 'com.google.android.gms:play-services-gcm:8.1.0'
+    compile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
+    compile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
+    testCompile 'junit:junit:4.12'
+    testCompile 'org.mockito:mockito-core:1.10.19'
+    testCompile 'org.robolectric:robolectric:3.0'
+    testCompile 'org.simpleframework:simple-http:6.0.1'
+    androidTestCompile 'com.jayway.android.robotium:robotium-solo:4.3.1'
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..05ef575b0cd0173fc735f2857ce4bd594ce4f6bd
GIT binary patch
literal 53637
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
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/project/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+#Tue Jan 26 20:42:06 PST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-all.zip
+distributionSha256Sum=2ba0aaa11a3e96ec0af31d532d808e1f09cc6dcad0954e637902a1ab544b9e60
new file mode 100755
--- /dev/null
+++ b/testing/docker/android-gradle-build/project/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/project/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
new file mode 100644
--- /dev/null
+++ b/testing/docker/android-gradle-build/project/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.mozilla.gecko">
+<!-- THIS IS NOT THE REAL MANIFEST!  This is for Gradle only.  See
+     AndroidManifest.xml.in. -->
+
+</manifest>
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/docs/components-imports.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-.. _components-imports:
-
-==================
-components-imports
-==================
-
-Rule Details
-------------
-
-Adds the filename of imported files e.g.
-``Cu.import("some/path/Blah.jsm")`` adds Blah to the global scope.
-
-The following patterns are supported:
-
--  ``Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");``
--  ``loader.lazyImporter(this, "name1");``
--  ``loader.lazyRequireGetter(this, "name2"``
--  ``loader.lazyServiceGetter(this, "name3"``
--  ``XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout", ...)``
--  ``loader.lazyGetter(this, "toolboxStrings"``
--  ``XPCOMUtils.defineLazyGetter(this, "clipboardHelper"``
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/docs/import-globals-from.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-.. _import-globals-from:
-
-===================
-import-globals-from
-===================
-
-Rule Details
-------------
-
-When the "import-globals-from <path>" comment is found in a file, then all
-globals from the file at <path> will be imported in the current scope.
-
-This is useful for tests that rely on globals defined in head.js files, or for
-scripts that are loaded as <script> tag in a window in rely on eachother's
-globals.
-
-If <path> is a relative path, then it must be relative to the file being
-checked by the rule.
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/docs/import-globals.rst
@@ -0,0 +1,20 @@
+.. _import-globals:
+
+==============
+import-globals
+==============
+
+Rule Details
+------------
+
+Parses a file for globals defined in various unique Mozilla ways.
+
+When a "import-globals-from <path>" comment is found in a file, then all globals
+from the file at <path> will be imported in the current scope. This will also
+operate recursively.
+
+This is useful for scripts that are loaded as <script> tag in a window and rely
+on each other's globals.
+
+If <path> is a relative path, then it must be relative to the file being
+checked by the rule.
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/docs/this-top-level-scope.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _this-top-level-scope:
-
-====================
-this-top-level-scope
-====================
-
-Rule Details
-------------
-
-Treat top-level assignments like ``this.mumble = value`` as declaring
-a global.
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/lib/globals.js
@@ -0,0 +1,187 @@
+/**
+ * @fileoverview functions for scanning an AST for globals including
+ *               traversing referenced scripts.
+ * 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 path = require("path");
+const fs = require("fs");
+const helpers = require("./helpers");
+const escope = require("escope");
+const estraverse = require("estraverse");
+
+/**
+ * Parses a list of "name:boolean_value" or/and "name" options divided by comma or
+ * whitespace.
+ *
+ * This function was copied from eslint.js
+ *
+ * @param {string} string The string to parse.
+ * @param {Comment} comment The comment node which has the string.
+ * @returns {Object} Result map object of names and boolean values
+ */
+function parseBooleanConfig(string, comment) {
+  let items = {};
+
+  // Collapse whitespace around : to make parsing easier
+  string = string.replace(/\s*:\s*/g, ":");
+  // Collapse whitespace around ,
+  string = string.replace(/\s*,\s*/g, ",");
+
+  string.split(/\s|,+/).forEach(function(name) {
+    if (!name) {
+      return;
+    }
+
+    let pos = name.indexOf(":");
+    let value = undefined;
+    if (pos !== -1) {
+      value = name.substring(pos + 1, name.length);
+      name = name.substring(0, pos);
+    }
+
+    items[name] = {
+      value: (value === "true"),
+      comment: comment
+    };
+  });
+
+  return items;
+}
+
+/**
+ * Global discovery can require parsing many files. This map of
+ * {String} => {Object} caches what globals were discovered for a file path.
+ */
+const globalCache = new Map();
+
+/**
+ * An object that returns found globals for given AST node types. Each prototype
+ * property should be named for a node type and accepts a node parameter and a
+ * parents parameter which is a list of the parent nodes of the current node.
+ * Each returns an array of globals found.
+ *
+ * @param  {String} path
+ *         The absolute path of the file being parsed.
+ */
+function GlobalsForNode(path) {
+  this.path = path;
+}
+
+GlobalsForNode.prototype = {
+  BlockComment(node, parents) {
+    let value = node.value.trim();
+    let match = /^import-globals-from\s+(.+)$/.exec(value);
+    if (!match) {
+      return [];
+    }
+
+    let filePath = match[1].trim();
+
+    if (!path.isAbsolute(filePath)) {
+      let dirName = path.dirname(this.path);
+      filePath = path.resolve(dirName, filePath);
+    }
+
+    return module.exports.getGlobalsForFile(filePath);
+  },
+
+  ExpressionStatement(node, parents) {
+    let isGlobal = helpers.getIsGlobalScope(parents);
+    let name = helpers.convertExpressionToGlobal(node, isGlobal);
+    return name ? [{ name, writable: true}] : [];
+  },
+};
+
+module.exports = {
+  /**
+   * Returns all globals for a given file. Recursively searches through
+   * import-globals-from directives and also includes globals defined by
+   * standard eslint directives.
+   *
+   * @param  {String} path
+   *         The absolute path of the file to be parsed.
+   */
+  getGlobalsForFile(path) {
+    if (globalCache.has(path)) {
+      return globalCache.get(path);
+    }
+
+    let content = fs.readFileSync(path, "utf8");
+
+    // Parse the content into an AST
+    let ast = helpers.getAST(content);
+
+    // Discover global declarations
+    let scopeManager = escope.analyze(ast);
+    let globalScope = scopeManager.acquire(ast);
+
+    let globals = Object.keys(globalScope.variables).map(v => ({
+      name: globalScope.variables[v].name,
+      writable: true,
+    }));
+
+    // Walk over the AST to find any of our custom globals
+    let handler = new GlobalsForNode(path);
+
+    helpers.walkAST(ast, (type, node, parents) => {
+      // We have to discover any globals that ESLint would have defined through
+      // comment directives
+      if (type == "BlockComment") {
+        let value = node.value.trim();
+        let match = /^globals?\s+(.+)$/.exec(value);
+        if (match) {
+          let values = parseBooleanConfig(match[1].trim(), node);
+          for (let name of Object.keys(values)) {
+            globals.push({
+              name,
+              writable: values[name].value
+            })
+          }
+        }
+      }
+
+      if (type in handler) {
+        let newGlobals = handler[type](node, parents);
+        globals.push.apply(globals, newGlobals);
+      }
+    });
+
+    globalCache.set(path, globals);
+
+    return globals;
+  },
+
+  /**
+   * Intended to be used as-is for an ESLint rule that parses for globals in
+   * the current file and recurses through import-globals-from directives.
+   *
+   * @param  {Object} context
+   *         The ESLint parsing context.
+   */
+  getESLintGlobalParser(context) {
+    let globalScope;
+
+    let parser = {
+      Program(node) {
+        globalScope = context.getScope();
+      }
+    };
+
+    // Install thin wrappers around GlobalsForNode
+    let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
+
+    for (let type of Object.keys(GlobalsForNode.prototype)) {
+      parser[type] = function(node) {
+        let globals = handler[type](node, context.getAncestors());
+        helpers.addGlobals(globals, globalScope);
+      }
+    }
+
+    return parser;
+  }
+};
--- a/testing/eslint-plugin-mozilla/lib/helpers.js
+++ b/testing/eslint-plugin-mozilla/lib/helpers.js
@@ -21,16 +21,17 @@ var definitions = [
   /^XPCOMUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
   /^XPCOMUtils\.defineLazyServiceGetter\(this, "(\w+)"/,
   /^XPCOMUtils\.defineConstant\(this, "(\w+)"/,
   /^DevToolsUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
   /^DevToolsUtils\.defineLazyGetter\(this, "(\w+)"/,
   /^Object\.defineProperty\(this, "(\w+)"/,
   /^Reflect\.defineProperty\(this, "(\w+)"/,
   /^this\.__defineGetter__\("(\w+)"/,
+  /^this\.(\w+) =/
 ];
 
 var imports = [
   /^(?:Cu|Components\.utils)\.import\(".*\/(.*?)\.jsm?"(?:, this)?\)/,
 ];
 
 module.exports = {
   /**
@@ -78,27 +79,91 @@ module.exports = {
       case "ObjectExpression":
         return "{}";
       case "ExpressionStatement":
         return this.getASTSource(node.expression) + ";";
       case "FunctionExpression":
         return "function() {}";
       case "ArrowFunctionExpression":
         return "() => {}";
+      case "AssignmentExpression":
+        return this.getASTSource(node.left) + " = " + this.getASTSource(node.right);
       default:
         throw new Error("getASTSource unsupported node type: " + node.type);
     }
   },
 
   /**
+   * This walks an AST in a manner similar to ESLint passing node and comment
+   * events to the listener. The listener is expected to be a simple function
+   * which accepts node type, node and parents arguments.
+   *
+   * @param  {Object} ast
+   *         The AST to walk.
+   * @param  {Function} listener
+   *         A callback function to call for the nodes. Passed three arguments,
+   *         event type, node and an array of parent nodes for the current node.
+   */
+  walkAST(ast, listener) {
+    let parents = [];
+
+    let seenComments = new Set();
+    function sendCommentEvents(comments) {
+      if (!comments) {
+        return;
+      }
+
+      for (let comment of comments) {
+        if (seenComments.has(comment)) {
+          return;
+        }
+        seenComments.add(comment);
+
+        listener(comment.type + "Comment", comment, parents);
+      }
+    }
+
+    estraverse.traverse(ast, {
+      enter(node, parent) {
+        // Comments are held in node.comments for empty programs
+        let leadingComments = node.leadingComments;
+        if (node.type === "Program" && node.body.length == 0) {
+          leadingComments = node.comments;
+        }
+
+        sendCommentEvents(leadingComments);
+        listener(node.type, node, parents);
+        sendCommentEvents(node.trailingComments);
+
+        parents.push(node);
+      },
+
+      leave(node, parent) {
+        // TODO send comment exit events
+        listener(node.type + ":exit", node, parents);
+
+        if (parents.length == 0) {
+          throw new Error("Left more nodes than entered.");
+        }
+        parents.pop();
+      }
+    });
+    if (parents.length) {
+      throw new Error("Entered more nodes than left.");
+    }
+  },
+
+  /**
    * Attempts to convert an ExpressionStatement to a likely global variable
    * definition.
    *
    * @param  {Object} node
    *         The AST node to convert.
+   * @param  {boolean} isGlobal
+   *         True if the current node is in the global scope
    *
    * @return {String or null}
    *         The variable name defined.
    */
   convertExpressionToGlobal: function(node, isGlobal) {
     try {
       var source = this.getASTSource(node);
     }
@@ -129,203 +194,71 @@ module.exports = {
         return match[1];
       }
     }
 
     return null;
   },
 
   /**
-   * Walks over an AST and calls a callback for every ExpressionStatement found.
-   *
-   * @param  {Object} ast
-   *         The AST to traverse.
-   *
-   * @return {Function}
-   *         The callback to call for each ExpressionStatement.
-   */
-  expressionTraverse: function(ast, callback) {
-    var helpers = this;
-    var parents = new Map();
-
-    // Walk the parents of a node to see if any are functions
-    function isGlobal(node) {
-      var parent = parents.get(node);
-      while (parent) {
-        if (parent.type == "FunctionExpression" ||
-            parent.type == "FunctionDeclaration") {
-          return false;
-        }
-        parent = parents.get(parent);
-      }
-      return true;
-    }
-
-    estraverse.traverse(ast, {
-      enter: function(node, parent) {
-        parents.set(node, parent);
-
-        if (node.type == "ExpressionStatement") {
-          callback(node, isGlobal(node));
-        }
-      }
-    });
-  },
-
-  /**
-   * Get an array of globals from an AST.
-   *
-   * @param  {Object} ast
-   *         The AST for which the globals are to be returned.
-   *
-   * @return {Array}
-   *         An array of variable names.
-   */
-  getGlobals: function(ast) {
-    var scopeManager = escope.analyze(ast);
-    var globalScope = scopeManager.acquire(ast);
-    var result = [];
-
-    for (var variable in globalScope.variables) {
-      var name = globalScope.variables[variable].name;
-      result.push(name);
-    }
-
-    var helpers = this;
-    this.expressionTraverse(ast, function(node, isGlobal) {
-      var name = helpers.convertExpressionToGlobal(node, isGlobal);
-
-      if (name) {
-        result.push(name);
-      }
-    });
-
-    return result;
-  },
-
-  /**
    * Add a variable to the current scope.
    * HACK: This relies on eslint internals so it could break at any time.
    *
    * @param {String} name
-   *        The variable name to add to the current scope.
-   * @param {ASTContext} context
-   *        The current context.
+   *        The variable name to add to the scope.
+   * @param {ASTScope} scope
+   *        The scope to add to.
+   * @param {boolean} writable
+   *        Whether the global can be overwritten.
    */
-  addVarToScope: function(name, context) {
-    var scope = context.getScope();
+  addVarToScope: function(name, scope, writable) {
+    // If the variable is already defined then skip it
+    if (scope.set && scope.set.has(name)) {
+      return;
+    }
+
+    writable = writable === undefined ? true : writable;
     var variables = scope.variables;
     var variable = new escope.Variable(name, scope);
 
     variable.eslintExplicitGlobal = false;
-    variable.writeable = true;
+    variable.writeable = writable;
     variables.push(variable);
 
     // Since eslint 1.10.3, scope variables are now duplicated in the scope.set
     // map, so we need to store them there too if it exists.
     // See https://groups.google.com/forum/#!msg/eslint/Y4_oHMWwP-o/5S57U8jXd8kJ
     if (scope.set) {
       scope.set.set(name, variable);
     }
   },
 
-  // Caches globals found in a file so we only have to parse a file once.
-  globalCache: new Map(),
-
   /**
-   * Finds all the globals defined in a given file.
-   *
-   * @param {String} fileName
-   *        The file to parse for globals.
-   */
-  getGlobalsForFile: function(fileName) {
-    // If the file can't be found, let the error go up to the caller so it can
-    // be logged as an error in the current file.
-    var content = fs.readFileSync(fileName, "utf8");
-
-    if (this.globalCache.has(fileName)) {
-      return this.globalCache.get(fileName);
-    }
-
-    // Parse the content and get the globals from the ast.
-    var ast = this.getAST(content);
-    var globalVars = this.getGlobals(ast);
-    this.globalCache.set(fileName, globalVars);
-
-    return globalVars;
-  },
-
-  /**
-   * Adds a set of globals to a context.
+   * Adds a set of globals to a scope.
    *
    * @param {Array} globalVars
    *        An array of global variable names.
-   * @param {ASTContext} context
-   *        The current context.
-   */
-  addGlobals: function(globalVars, context) {
-    for (var i = 0; i < globalVars.length; i++) {
-      var varName = globalVars[i];
-      this.addVarToScope(varName, context);
-    }
-  },
-
-  /**
-   * Process comments looking for import-globals-from statements.  Add globals
-   * from those files, if any.
-   *
-   * @param {String} currentFilePath
-   *        Absolute path to the file containing the comments.
-   * @param {Array} comments
-   *        The comments to be processed.
-   * @param {Object} node
-   *        The AST node for error reporting.
-   * @param {ASTContext} context
-   *        The current context.
+   * @param {ASTScope} scope
+   *        The scope.
    */
-  addGlobalsFromComments: function(currentFilePath, comments, node, context) {
-    comments.forEach(comment => {
-      var value = comment.value.trim();
-      var match = /^import-globals-from\s+(.*)$/.exec(value);
-
-      if (match) {
-        var filePath = match[1];
-
-        if (!path.isAbsolute(filePath)) {
-          var dirName = path.dirname(currentFilePath);
-          filePath = path.resolve(dirName, filePath);
-        }
-
-        try {
-          let globals = this.getGlobalsForFile(filePath);
-          this.addGlobals(globals, context);
-        } catch (e) {
-          context.report(
-            node,
-            "Could not load globals from file {{filePath}}: {{error}}",
-            {
-              filePath: filePath,
-              error: e
-            }
-          );
-        }
-      }
-    });
+  addGlobals: function(globalVars, scope) {
+    globalVars.forEach(v => this.addVarToScope(v.name, scope, v.writable));
   },
 
   /**
    * To allow espree to parse almost any JavaScript we need as many features as
    * possible turned on. This method returns that config.
    *
    * @return {Object}
    *         Espree compatible permissive config.
    */
   getPermissiveConfig: function() {
     return {
       comment: true,
+      attachComment: true,
       range: true,
       loc: true,
       tolerant: true,
       ecmaFeatures: {
         arrowFunctions: true,
         binaryLiterals: true,
         blockBindings: true,
         classes: true,
@@ -349,31 +282,62 @@ module.exports = {
         unicodeCodePointEscapes: true,
       }
     };
   },
 
   /**
    * Check whether the context is the global scope.
    *
-   * @param {ASTContext} context
-   *        The current context.
+   * @param {Array} ancestors
+   *        The parents of the current node.
    *
    * @return {Boolean}
    *         True or false
    */
-  getIsGlobalScope: function(context) {
-    var ancestors = context.getAncestors();
-    var parent = ancestors.pop();
+  getIsGlobalScope: function(ancestors) {
+    for (let parent of ancestors) {
+      if (parent.type == "FunctionExpression" ||
+          parent.type == "FunctionDeclaration") {
+        return false;
+      }
+    }
+    return true;
+  },
 
-    if (parent.type == "ExpressionStatement") {
-      parent = ancestors.pop();
-    }
+  /**
+   * Check whether we might be in a test head file.
+   *
+   * @param  {RuleContext} scope
+   *         You should pass this from within a rule
+   *         e.g. helpers.getIsHeadFile(this)
+   *
+   * @return {Boolean}
+   *         True or false
+   */
+  getIsHeadFile: function(scope) {
+    var pathAndFilename = scope.getFilename();
 
-    return parent.type == "Program";
+    return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
+  },
+
+  /**
+   * Check whether we might be in an xpcshell test.
+   *
+   * @param  {RuleContext} scope
+   *         You should pass this from within a rule
+   *         e.g. helpers.getIsXpcshellTest(this)
+   *
+   * @return {Boolean}
+   *         True or false
+   */
+  getIsXpcshellTest: function(scope) {
+    var pathAndFilename = scope.getFilename();
+
+    return /.*[\\/]test_.+\.js$/.test(pathAndFilename);
   },
 
   /**
    * Check whether we are in a browser mochitest.
    *
    * @param  {RuleContext} scope
    *         You should pass this from within a rule
    *         e.g. helpers.getIsBrowserMochitest(this)
@@ -395,17 +359,17 @@ module.exports = {
    *         e.g. helpers.getIsTest(this)
    *
    * @return {Boolean}
    *         True or false
    */
   getIsTest: function(scope) {
     var pathAndFilename = scope.getFilename();
 
-    if (/.*[\\/]test_.+\.js$/.test(pathAndFilename)) {
+    if (this.getIsXpcshellTest(scope)) {
       return true;
     }
 
     return this.getIsBrowserMochitest(scope);
   },
 
   /**
    * Gets the root directory of the repository by walking up directories until
--- a/testing/eslint-plugin-mozilla/lib/index.js
+++ b/testing/eslint-plugin-mozilla/lib/index.js
@@ -13,33 +13,29 @@
 //------------------------------------------------------------------------------
 
 module.exports = {
   processors: {
     ".xml": require("../lib/processors/xbl-bindings"),
   },
   rules: {
     "balanced-listeners": require("../lib/rules/balanced-listeners"),
-    "components-imports": require("../lib/rules/components-imports"),
-    "import-globals-from": require("../lib/rules/import-globals-from"),
+    "import-globals": require("../lib/rules/import-globals"),
     "import-headjs-globals": require("../lib/rules/import-headjs-globals"),
     "import-browserjs-globals": require("../lib/rules/import-browserjs-globals"),
     "mark-test-function-used": require("../lib/rules/mark-test-function-used"),
     "no-aArgs": require("../lib/rules/no-aArgs"),
     "no-cpows-in-tests": require("../lib/rules/no-cpows-in-tests"),
     "reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"),
-    "this-top-level-scope": require("../lib/rules/this-top-level-scope.js"),
     "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
   },
   rulesConfig: {
     "balanced-listeners": 0,
-    "components-imports": 0,
-    "import-globals-from": 0,
+    "import-globals": 0,
     "import-headjs-globals": 0,
     "import-browserjs-globals": 0,
     "mark-test-function-used": 0,
     "no-aArgs": 0,
     "no-cpows-in-tests": 0,
     "reject-importGlobalProperties": 0,
-    "this-top-level-scope": 0,
     "var-only-at-top-level": 0
   }
 };
--- a/testing/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
+++ b/testing/eslint-plugin-mozilla/lib/processors/xbl-bindings.js
@@ -31,134 +31,169 @@ function parseError(err) {
 let entityRegex = /&[\w][\w-\.]*;/g;
 
 // A simple sax listener that generates a tree of element information
 function XMLParser(parser) {
   this.parser = parser;
   parser.onopentag = this.onOpenTag.bind(this);
   parser.onclosetag = this.onCloseTag.bind(this);
   parser.ontext = this.onText.bind(this);
+  parser.onopencdata = this.onOpenCDATA.bind(this);
   parser.oncdata = this.onCDATA.bind(this);
+  parser.oncomment = this.onComment.bind(this);
 
   this.document = {
     local: "#document",
     uri: null,
     children: [],
+    comments: [],
   }
   this._currentNode = this.document;
 }
 
 XMLParser.prototype = {
   parser: null,
 
   onOpenTag: function(tag) {
     let node = {
       parentNode: this._currentNode,
       local: tag.local,
       namespace: tag.uri,
       attributes: {},
       children: [],
+      comments: [],
       textContent: "",
-      textStart: { line: this.parser.line, column: this.parser.column },
+      textLine: this.parser.line,
+      textColumn: this.parser.column,
+      textEndLine: this.parser.line
     }
 
     for (let attr of Object.keys(tag.attributes)) {
       if (tag.attributes[attr].uri == "") {
         node.attributes[attr] = tag.attributes[attr].value;
       }
     }
 
     this._currentNode.children.push(node);
     this._currentNode = node;
   },
 
   onCloseTag: function(tagname) {
+    this._currentNode.textEndLine = this.parser.line;
     this._currentNode = this._currentNode.parentNode;
   },
 
   addText: function(text) {
     this._currentNode.textContent += text;
   },
 
   onText: function(text) {
     // Replace entities with some valid JS token.
     this.addText(text.replace(entityRegex, "null"));
   },
 
-  onCDATA: function(text) {
+  onOpenCDATA: function() {
     // Turn the CDATA opening tag into whitespace for indent alignment
     this.addText(" ".repeat("<![CDATA[".length));
+  },
+
+  onCDATA: function(text) {
     this.addText(text);
+  },
+
+  onComment: function(text) {
+    this._currentNode.comments.push(text);
   }
 }
 
-// Strips the indentation from lines of text and adds a fixed two spaces indent
-function buildCodeBlock(tag, prefix, suffix, indent) {
-  prefix = prefix === undefined ? "" : prefix;
-  suffix = suffix === undefined ? "\n}" : suffix;
-  indent = indent === undefined ? 2 : indent;
+// -----------------------------------------------------------------------------
+// Processor Definition
+// -----------------------------------------------------------------------------
+
+const INDENT_LEVEL = 2;
+
+function indent(count) {
+  return " ".repeat(count * INDENT_LEVEL);
+}
+
+// Stores any XML parse error
+let xmlParseError = null;
 
-  let text = tag.textContent;
-  let line = tag.textStart.line;
-  let column = tag.textStart.column;
+// Stores the lines of JS code generated from the XBL
+let scriptLines = [];
+// Stores a map from the synthetic line number to the real line number
+// and column offset.
+let lineMap = [];
+
+function addSyntheticLine(line, linePos) {
+  lineMap[scriptLines.length] = { line: linePos, offset: null };
+  scriptLines.push(line);
+}
 
-  let lines = text.split("\n");
+/**
+ * Adds generated lines from an XBL node to the script to be passed back to eslint.
+ */
+function addNodeLines(node, reindent) {
+  let lines = node.textContent.split("\n");
+  let startLine = node.textLine;
+  let startColumn = node.textColumn;
+
+  // The case where there are no whitespace lines before the first text is
+  // treated differently for indentation
+  let indentFirst = false;
 
   // Strip off any preceeding whitespace only lines. These are often used to
   // format the XML and CDATA blocks.
   while (lines.length && lines[0].trim() == "") {
-    column = 0;
-    line++;
+    indentFirst = true;
+    startLine++;
     lines.shift();
   }
 
   // Strip off any whitespace lines at the end. These are often used to line
   // up the closing tags
   while (lines.length && lines[lines.length - 1].trim() == "") {
     lines.pop();
   }
 
-  // Indent the first line with the starting position of the text block
-  if (lines.length && column) {
-    lines[0] = " ".repeat(column) + lines[0];
+  if (!indentFirst) {
+    let firstLine = lines.shift();
+    firstLine = " ".repeat(reindent * INDENT_LEVEL) + firstLine;
+    // ESLint counts columns starting at 1 rather than 0
+    lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (startColumn - 1) };
+    scriptLines.push(firstLine);
+    startLine++;
   }
 
   // Find the preceeding whitespace for all lines that aren't entirely whitespace
   let indents = lines.filter(s => s.trim().length > 0)
                      .map(s => s.length - s.trimLeft().length);
   // Find the smallest indent level in use
   let minIndent = Math.min.apply(null, indents);
 
-  // Strip off the found indent level and prepend the new indent level, but only
-  // if the string isn't already empty.
-  let indentstr = " ".repeat(indent);
-  lines = lines.map(s => s.length ? indentstr + s.substring(minIndent) : s);
+  for (let line of lines) {
+    if (line.trim().length == 0) {
+      // Don't offset lines that are only whitespace, the only possible JS error
+      // is trailing whitespace and we want it to point at the right place
+      lineMap[scriptLines.length] = { line: startLine, offset: 0 };
+    } else {
+      line = " ".repeat(reindent * INDENT_LEVEL) + line.substring(minIndent);
+      lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (minIndent - 1) };
+    }
 
-  let block = {
-    script: prefix + lines.join("\n") + suffix + "\n",
-    line: line - prefix.split("\n").length,
-    indent: minIndent - indent
-  };
-  return block;
+    scriptLines.push(line);
+    startLine++;
+  }
 }
 
-// -----------------------------------------------------------------------------
-// Processor Definition
-// -----------------------------------------------------------------------------
-
-// Stores any XML parse error
-let xmlParseError = null;
-
-// Stores the code blocks
-let blocks = [];
-
 module.exports = {
   preprocess: function(text, filename) {
     xmlParseError = null;
-    blocks = [];
+    scriptLines = [];
+    lineMap = [];
 
     // Non-strict allows us to ignore many errors from entities and
     // preprocessing at the expense of failing to report some XML errors.
     // Unfortunately it also throws away the case of tagnames and attributes
     let parser = sax.parser(false, {
       lowercase: true,
       xmlns: true,
     });
@@ -176,104 +211,150 @@ module.exports = {
       return [];
     }
 
     let bindings = document.children[0];
     if (bindings.local != "bindings" || bindings.namespace != NS_XBL) {
       return [];
     }
 
-    let scripts = [];
+    for (let comment of document.comments) {
+      addSyntheticLine(`/*`, 0);
+      for (let line of comment.split("\n")) {
+        addSyntheticLine(`${line.trim()}`, 0);
+      }
+      addSyntheticLine(`*/`, 0);
+    }
+
+    addSyntheticLine(`var bindings = {`, bindings.textLine);
 
     for (let binding of bindings.children) {
       if (binding.local != "binding" || binding.namespace != NS_XBL) {
         continue;
       }
 
+      addSyntheticLine(indent(1) + `"${binding.attributes.id}": {`, binding.textLine);
+
       for (let part of binding.children) {
         if (part.namespace != NS_XBL) {
           continue;
         }
 
-        if (part.local != "implementation" && part.local != "handlers") {
+        if (part.local == "implementation") {
+          addSyntheticLine(indent(2) + `implementation: {`, part.textLine);
+        } else if (part.local == "handlers") {
+          addSyntheticLine(indent(2) + `handlers: [`, part.textLine);
+        } else {
           continue;
         }
 
         for (let item of part.children) {
           if (item.namespace != NS_XBL) {
             continue;
           }
 
           switch (item.local) {
             case "field": {
-              // Fields get converted into variable declarations
+              // Fields are something like lazy getter functions
 
               // Ignore empty fields
               if (item.textContent.trim().length == 0) {
                 continue;
               }
-              blocks.push(buildCodeBlock(item, "", "", 0));
+
+              addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, item.textLine);
+              // TODO This will probably break some style rules when. We need
+              // to inject this next to the first non-whitespace character
+              addSyntheticLine(indent(4) + `return`, item.textLine);
+              addNodeLines(item, 4);
+              addSyntheticLine(indent(3) + `},`, item.textEndLine);
               break;
             }
             case "constructor":
             case "destructor": {
               // Constructors and destructors become function declarations
-              blocks.push(buildCodeBlock(item, `function ${item.local}() {\n`));
+              addSyntheticLine(indent(3) + `${item.local}() {`, item.textLine);
+              addNodeLines(item, 4);
+              addSyntheticLine(indent(3) + `},`, item.textEndLine);
               break;
             }
             case "method": {
               // Methods become function declarations with the appropriate params
+
               let params = item.children.filter(n => n.local == "parameter" && n.namespace == NS_XBL)
                                         .map(n => n.attributes.name)
                                         .join(", ");
               let body = item.children.filter(n => n.local == "body" && n.namespace == NS_XBL)[0];
-              blocks.push(buildCodeBlock(body, `function ${item.attributes.name}(${params}) {\n`));
+
+              addSyntheticLine(indent(3) + `${item.attributes.name}(${params}) {`, item.textLine);
+              addNodeLines(body, 4);
+              addSyntheticLine(indent(3) + `},`, item.textEndLine);
               break;
             }
             case "property": {
               // Properties become one or two function declarations
               for (let propdef of item.children) {
                 if (propdef.namespace != NS_XBL) {
                   continue;
                 }
 
-                let params = propdef.local == "setter" ? "val" : "";
-                blocks.push(buildCodeBlock(propdef, `function ${item.attributes.name}_${propdef.local}(${params}) {\n`));
+                if (propdef.local == "setter") {
+                  addSyntheticLine(indent(3) + `set ${item.attributes.name}(val) {`, propdef.textLine);
+                } else if (propdef.local == "getter") {
+                  addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, propdef.textLine);
+                } else {
+                  continue;
+                }
+                addNodeLines(propdef, 4);
+                addSyntheticLine(indent(3) + `},`, propdef.textEndLine);
               }
               break;
             }
             case "handler": {
               // Handlers become a function declaration with an `event` parameter
-              blocks.push(buildCodeBlock(item, `function onevent(event) {\n`));
+              addSyntheticLine(indent(3) + `function(event) {`, item.textLine);
+              addNodeLines(item, 4);
+              addSyntheticLine(indent(3) + `},`, item.textEndLine);
               break;
             }
             default:
               continue;
           }
         }
+
+        addSyntheticLine(indent(2) + (part.local == "implementation" ? `},` : `],`), part.textEndLine);
       }
+      addSyntheticLine(indent(1) + `},`, binding.textEndLine);
     }
+    addSyntheticLine(`};`, bindings.textEndLine);
 
-    return blocks.map(b => b.script);
+    let script = scriptLines.join("\n") + "\n";
+    return [script];
   },
 
   postprocess: function(messages, filename) {
     // If there was an XML parse error then just return that
     if (xmlParseError) {
       return [xmlParseError];
     }
 
     // For every message from every script block update the line to point to the
     // correct place.
     let errors = [];
     for (let i = 0; i < messages.length; i++) {
-      let block = blocks[i];
+      for (let message of messages[i]) {
+        // ESLint indexes lines starting at 1 but our arrays start at 0
+        let mapped = lineMap[message.line - 1];
 
-      for (let message of messages[i]) {
-        message.line += block.line + 1;
-        message.column += block.indent;
+        message.line = mapped.line + 1;
+        if (mapped.offset) {
+          message.column -= mapped.offset;
+        } else {
+          message.column = NaN;
+        }
+
         errors.push(message);
       }
     }
 
     return errors;
   }
 };
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/lib/rules/components-imports.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @fileoverview Adds the filename of imported files e.g.
- * Cu.import("some/path/Blah.jsm") adds Blah to the current scope.
- *
- * 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";
-
-// -----------------------------------------------------------------------------
-// Rule Definition
-// -----------------------------------------------------------------------------
-
-var helpers = require("../helpers");
-
-module.exports = function(context) {
-  // ---------------------------------------------------------------------------
-  // Public
-  // ---------------------------------------------------------------------------
-
-  return {
-    ExpressionStatement: function(node) {
-      var scope = context.getScope();
-      var name = helpers.convertExpressionToGlobal(node, scope.type == "global");
-
-      if (name) {
-        helpers.addVarToScope(name, context);
-      }
-    }
-  };
-};
--- a/testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js
@@ -10,18 +10,22 @@
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 
 var fs = require("fs");
 var path = require("path");
 var helpers = require("../helpers");
+var globals = require("../globals");
 
 const SCRIPTS = [
+  //"browser/base/content/nsContextMenu.js",
+  "toolkit/content/contentAreaUtils.js",
+  "browser/components/places/content/editBookmarkOverlay.js",
   "toolkit/components/printing/content/printUtils.js",
   "toolkit/content/viewZoomOverlay.js",
   "browser/components/places/content/browserPlacesViews.js",
   "browser/base/content/browser.js",
   "browser/components/downloads/content/downloads.js",
   "browser/components/downloads/content/indicator.js",
   "browser/components/customizableui/content/panelUI.js",
   "toolkit/obsolete/content/inlineSpellCheckUI.js",
@@ -41,34 +45,34 @@ const SCRIPTS = [
   "browser/base/content/browser-safebrowsing.js",
   "browser/base/content/browser-sidebar.js",
   "browser/base/content/browser-social.js",
   "browser/base/content/browser-syncui.js",
   "browser/base/content/browser-tabsintitlebar.js",
   "browser/base/content/browser-thumbnails.js",
   "browser/base/content/browser-trackingprotection.js",
   "browser/base/content/browser-data-submission-info-bar.js",
-  "browser/base/content/browser-fxaccounts.js",
+  "browser/base/content/browser-fxaccounts.js"
 ];
 
 module.exports = function(context) {
   return {
     Program: function(node) {
-      if (!helpers.getIsBrowserMochitest(this)) {
+      if (!helpers.getIsBrowserMochitest(this) &&
+          !helpers.getIsHeadFile(this)) {
         return;
       }
 
       let root = helpers.getRootDir(context);
       for (let script of SCRIPTS) {
         let fileName = path.join(root, script);
         try {
-          let globals = helpers.getGlobalsForFile(fileName);
-          helpers.addGlobals(globals, context);
-        }
-        catch (e) {
+          let newGlobals = globals.getGlobalsForFile(fileName);
+          helpers.addGlobals(newGlobals, context.getScope());
+        } catch (e) {
           context.report(
             node,
             "Could not load globals from file {{filePath}}: {{error}}",
             {
               filePath: path.relative(root, fileName),
               error: e
             }
           );
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/lib/rules/import-globals-from.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @fileoverview When the "import-globals-from: <path>" comment is found in a
- * file, then all globals from the file at <path> will be imported in the
- * current scope.
- *
- * 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";
-
-// -----------------------------------------------------------------------------
-// Rule Definition
-// -----------------------------------------------------------------------------
-
-var fs = require("fs");
-var helpers = require("../helpers");
-var path = require("path");
-
-module.exports = function(context) {
-  // ---------------------------------------------------------------------------
-  // Public
-  // ---------------------------------------------------------------------------
-
-  return {
-    Program: function(node) {
-      var comments = context.getSourceCode().getAllComments();
-      var currentFilePath = helpers.getAbsoluteFilePath(context);
-      helpers.addGlobalsFromComments(currentFilePath, comments, node, context);
-    }
-  };
-};
new file mode 100644
--- /dev/null
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-globals.js
@@ -0,0 +1,15 @@
+/**
+ * @fileoverview Discovers all globals for the current file.
+ *
+ * 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";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = require("../globals").getESLintGlobalParser;
--- a/testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
@@ -11,38 +11,53 @@
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 
 var fs = require("fs");
 var path = require("path");
 var helpers = require("../helpers");
+var globals = require("../globals");
 
 module.exports = function(context) {
 
+  function importHead(path, node) {
+    try {
+      let stats = fs.statSync(path);
+      if (!stats.isFile()) {
+        return;
+      }
+    } catch (e) {
+      return;
+    }
+
+    let newGlobals = globals.getGlobalsForFile(path);
+    helpers.addGlobals(newGlobals, context.getScope());
+  }
+
   // ---------------------------------------------------------------------------
   // Public
   // ---------------------------------------------------------------------------
 
   return {
     Program: function(node) {
       if (!helpers.getIsTest(this)) {
         return;
       }
 
       var currentFilePath = helpers.getAbsoluteFilePath(context);
       var dirName = path.dirname(currentFilePath);
-      var fullHeadjsPath = path.resolve(dirName, "head.js");
-      if (!fs.existsSync(fullHeadjsPath)) {
+      importHead(path.resolve(dirName, "head.js"), node);
+
+      if (!helpers.getIsXpcshellTest(this)) {
         return;
       }
 
-      let globals = helpers.getGlobalsForFile(fullHeadjsPath);
-      helpers.addGlobals(globals, context);
-
-      // Also add any globals from import-globals-from comments
-      var content = fs.readFileSync(fullHeadjsPath, "utf8");
-      var comments = helpers.getAST(content).comments;
-      helpers.addGlobalsFromComments(fullHeadjsPath, comments, node, context);
+      let names = fs.readdirSync(dirName);
+      for (let name of names) {
+        if (name.startsWith("head_") && name.endsWith(".js")) {
+          importHead(path.resolve(dirName, name), node);
+        }
+      }
     }
   };
 };
--- a/testing/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js
@@ -51,17 +51,17 @@ module.exports = function(context) {
       // |cpows| reports one, don't report another below.
       var someCpowFound = cpows.some(function(cpow) {
         if (cpow.test(expression)) {
           showError(node, expression);
           return true;
         }
         return false;
       });
-      if (!someCpowFound && helpers.getIsGlobalScope(context)) {
+      if (!someCpowFound && helpers.getIsGlobalScope(context.getAncestors())) {
         if (/^content\./.test(expression)) {
           showError(node, expression);
           return;
         }
       }
     },
 
     Identifier: function(node) {
deleted file mode 100644
--- a/testing/eslint-plugin-mozilla/lib/rules/this-top-level-scope.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * @fileoverview Marks "this.var = x" as top-level definition of "var".
- *
- * 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";
-
-// -----------------------------------------------------------------------------
-// Rule Definition
-// -----------------------------------------------------------------------------
-
-var helpers = require("../helpers");
-
-module.exports = function(context) {
-
-  // ---------------------------------------------------------------------------
-  // Public
-  //  --------------------------------------------------------------------------
-
-  return {
-    "AssignmentExpression": function(node) {
-      if (helpers.getIsGlobalScope(context) &&
-          node.left.type === "MemberExpression" &&
-          node.left.object.type === "ThisExpression" &&
-          node.left.property.type === "Identifier") {
-        helpers.addGlobals([node.left.property.name], context);
-      }
-    }
-  };
-};
--- a/testing/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
+++ b/testing/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
@@ -18,17 +18,17 @@ var helpers = require("../helpers");
 module.exports = function(context) {
   // ---------------------------------------------------------------------------
   // Public
   //  --------------------------------------------------------------------------
 
   return {
     "VariableDeclaration": function(node) {
       if (node.kind === "var") {
-        if (helpers.getIsGlobalScope(context)) {
+        if (helpers.getIsGlobalScope(context.getAncestors())) {
           return;
         }
 
         context.report(node, "Unexpected var, use let or const instead.");
       }
     }
   };
 };
--- a/testing/mochitest/browser.eslintrc
+++ b/testing/mochitest/browser.eslintrc
@@ -9,21 +9,26 @@
     "browser": true,
   },
 
   // All globals made available in the test environment.
   "globals": {
     "add_task": false,
     "Assert": false,
     "BrowserTestUtils": false,
+    "content": false,
     "ContentTask": false,
+    "ContentTaskUtils": false,
     "EventUtils": false,
     "executeSoon": false,
+    "expectUncaughtException": false,
     "export_assertions": false,
+    "extractJarToTmp": false,
     "finish": false,
+    "getJar": false,
     "getRootDirectory": false,
     "getTestFilePath": false,
     "gTestPath": false,
     "info": false,
     "is": false,
     "isnot": false,
     "ok": false,
     "registerCleanupFunction": false,
@@ -32,15 +37,10 @@
     "SpecialPowers": false,
     "thisTestLeaksUncaughtRejectionsAndShouldBeFixed": false,
     "todo": false,
     "todo_is": false,
     "todo_isnot": false,
     "waitForClipboard": false,
     "waitForExplicitFinish": false,
     "waitForFocus": false,
-    "gBrowser": false,
-    "gNavToolbox": false,
-    "gURLBar": false,
-    "gNavigatorBundle": false,
-    "content": false,
   }
 }
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/builds/releng_sub_android_configs/64_api_15_frontend.py
@@ -0,0 +1,8 @@
+config = {
+    'base_name': 'Android armv7 API 15+ frontend %(branch)s',
+    'stage_platform': 'android-api-15-frontend',
+    'build_type': 'api-15-opt',
+    'src_mozconfig': 'mobile/android/config/mozconfigs/android-api-15-frontend/nightly',
+    'tooltool_manifest_src': 'mobile/android/config/tooltool-manifests/android-frontend/releng.manifest',
+    'multi_locale_config_platform': 'android',
+}
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -352,16 +352,17 @@ class BuildOptionParser(object):
         'stat-and-debug': 'builds/releng_sub_%s_configs/%s_stat_and_debug.py',
         'mulet': 'builds/releng_sub_%s_configs/%s_mulet.py',
         'code-coverage': 'builds/releng_sub_%s_configs/%s_code_coverage.py',
         'graphene': 'builds/releng_sub_%s_configs/%s_graphene.py',
         'horizon': 'builds/releng_sub_%s_configs/%s_horizon.py',
         'source': 'builds/releng_sub_%s_configs/%s_source.py',
         'api-9': 'builds/releng_sub_%s_configs/%s_api_9.py',
         'api-11': 'builds/releng_sub_%s_configs/%s_api_11.py',
+        'api-15-frontend': 'builds/releng_sub_%s_configs/%s_api_15_frontend.py',
         'api-15': 'builds/releng_sub_%s_configs/%s_api_15.py',
         'api-9-debug': 'builds/releng_sub_%s_configs/%s_api_9_debug.py',
         'api-11-debug': 'builds/releng_sub_%s_configs/%s_api_11_debug.py',
         'api-15-debug': 'builds/releng_sub_%s_configs/%s_api_15_debug.py',
         'x86': 'builds/releng_sub_%s_configs/%s_x86.py',
         'api-11-partner-sample1': 'builds/releng_sub_%s_configs/%s_api_11_partner_sample1.py',
         'api-15-partner-sample1': 'builds/releng_sub_%s_configs/%s_api_15_partner_sample1.py',
         'api-11-b2gdroid': 'builds/releng_sub_%s_configs/%s_api_11_b2gdroid.py',
new file mode 100644
--- /dev/null
+++ b/testing/taskcluster/tasks/android-gradle-build-dependencies.yml
@@ -0,0 +1,66 @@
+# A task to fetch Android Gradle dependencies from jcentral and package them
+# for consumption by tooltool users.  Normally invoked manually.
+---
+taskId: {{build_slugid}}
+
+task:
+  created: '{{now}}'
+  deadline: '{{#from_now}}24 hours{{/from_now}}'
+  metadata:
+    name: '[TC] - Android Gradle build dependencies'
+    description: 'Fetch and package Android Gradle build dependencies'
+    owner: nalexander@mozilla.com
+    source: http://todo.com/soon
+
+  tags:
+    createdForUser: {{owner}}
+
+  workerType: b2gtest
+  provisionerId: aws-provisioner-v1
+  schedulerId: task-graph-scheduler
+
+  routes:
+    - 'index.gecko.v1.{{project}}.revision.linux.{{head_rev}}.{{build_name}}.{{build_type}}'
+    - 'index.gecko.v1.{{project}}.latest.linux.{{build_name}}.{{build_type}}'
+
+  scopes:
+    - 'docker-worker:cache:level-{{level}}-{{project}}-tc-vcs'
+
+  payload:
+    image:
+      type: 'task-image'
+      path: 'public/image.tar'
+      taskId: '{{#task_id_for_image}}android-gradle-build{{/task_id_for_image}}'
+
+    command:
+      - bash
+      - /build/build.sh
+
+    maxRunTime: 1800
+
+    cache:
+      level-{{level}}-{{project}}-tc-vcs: '/home/worker/.tc-vcs'
+
+    artifacts:
+      'public/build':
+        type: directory
+        path: '/artifacts/'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+
+  extra:
+    build_product: '{{build_product}}'
+    build_name: '{{build_name}}'
+    build_type: '{{build_type}}'
+    index:
+      rank: {{pushlog_id}}
+    locations:
+        build: null
+        tests: null
+    treeherder:
+        machine:
+            platform: lint
+        groupSymbol: tc
+        symbol: AG
+    treeherderEnv:
+        - production
+        - staging
new file mode 100644
--- /dev/null
+++ b/testing/taskcluster/tasks/builds/android_api_15_frontend.yml
@@ -0,0 +1,60 @@
+$inherits:
+  from: 'tasks/builds/mobile_base.yml'
+  variables:
+    build_name: 'android-api-15-frontend'
+    build_type: 'opt'
+task:
+  metadata:
+      name: '[TC] Android armv7 API 15+ frontend'
+      description: 'Android armv7 API 15+ frontend'
+
+  workerType: android-api-15
+
+  routes:
+    - 'index.buildbot.branches.{{project}}.android-api-15-frontend'
+    - 'index.buildbot.revisions.{{head_rev}}.{{project}}.android-api-15-frontend'
+
+  scopes:
+    - 'docker-worker:cache:level-{{level}}-{{project}}-build-android-api-15-frontend-workspace'
+    - 'docker-worker:cache:tooltool-cache'
+    - 'docker-worker:relengapi-proxy:tooltool.download.internal'
+    - 'docker-worker:relengapi-proxy:tooltool.download.public'
+
+  payload:
+    cache:
+      level-{{level}}-{{project}}-build-android-api-15-frontend-workspace: '/home/worker/workspace'
+      tooltool-cache: '/home/worker/tooltool-cache'
+
+    features:
+      relengAPIProxy: true
+
+    env:
+      # inputs to mozharness
+      MOZHARNESS_SCRIPT: 'mozharness/scripts/fx_desktop_build.py'
+      # TODO: make these additional configuration files go away
+      MOZHARNESS_CONFIG: >
+          builds/releng_base_android_64_builds.py
+          disable_signing.py
+          platform_supports_post_upload_to_latest.py
+      MH_CUSTOM_BUILD_VARIANT_CFG: api-15-frontend
+      MH_BRANCH: {{project}}
+      MH_BUILD_POOL: taskcluster
+
+    maxRunTime: 36000
+
+    command: ["/bin/bash", "bin/build.sh"]
+
+  extra:
+    treeherderEnv:
+      - production
+      - staging
+    treeherder:
+      machine:
+        # see https://github.com/mozilla/treeherder/blob/master/ui/js/values.js
+        platform: android-4-0-armv7-api15-frontend
+    # Rather then enforcing particular conventions we require that all build
+    # tasks provide the "build" extra field to specify where the build and tests
+    # files are located.
+    locations:
+      build: 'public/build/target.linux-x86_64.tar.bz2'
+      tests: 'public/build/target.tests.zip'
--- a/testing/xpcshell/xpcshell.eslintrc
+++ b/testing/xpcshell/xpcshell.eslintrc
@@ -19,23 +19,28 @@
     "do_get_cwd": false,
     "do_get_file": false,
     "do_get_idle": false,
     "do_get_profile": false,
     "do_load_module": false,
     "do_parse_document": false,
     "do_print": false,
     "do_register_cleanup": false,
+    "do_report_unexpected_exception": false,
     "do_test_finished": false,
     "do_test_pending": false,
     "do_throw": false,
     "do_timeout": false,
     "equal": false,
     "load": false,
+    "mozinfo": false,
     "notDeepEqual": false,
     "notEqual": false,
     "notStrictEqual": false,
     "ok": false,
     "run_next_test": false,
     "run_test": false,
     "strictEqual": false,
+    "todo": false,
+    "todo_check_false": false,
+    "todo_check_true": false,
   }
 }
--- a/toolkit/.eslintrc
+++ b/toolkit/.eslintrc
@@ -187,10 +187,20 @@
     // ++ and -- should not need spacing
     // "space-unary-ops": [2, { "words": true, "nonwords": false }],
 
     // No comparisons to NaN
     "use-isnan": 2,
 
     // Only check typeof against valid results
     "valid-typeof": 2,
+  },
+  "env": {
+    "es6": true,
+    "browser": true,
+  },
+  "globals": {
+    "Components": false,
+    "dump": true,
+    "openDialog": false,
+    "sizeToContent": false,
   }
 }
--- a/toolkit/components/alerts/AlertNotification.cpp
+++ b/toolkit/components/alerts/AlertNotification.cpp
@@ -113,16 +113,26 @@ AlertNotification::GetData(nsAString& aD
 NS_IMETHODIMP
 AlertNotification::GetPrincipal(nsIPrincipal** aPrincipal)
 {
   NS_IF_ADDREF(*aPrincipal = mPrincipal);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+AlertNotification::GetURI(nsIURI** aURI)
+{
+  if (!nsAlertsUtils::IsActionablePrincipal(mPrincipal)) {
+    *aURI = nullptr;
+    return NS_OK;
+  }
+  return mPrincipal->GetURI(aURI);
+}
+
+NS_IMETHODIMP
 AlertNotification::GetInPrivateBrowsing(bool* aInPrivateBrowsing)
 {
   *aInPrivateBrowsing = mInPrivateBrowsing;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AlertNotification::GetActionable(bool* aActionable)
--- a/toolkit/components/alerts/nsAlertsService.cpp
+++ b/toolkit/components/alerts/nsAlertsService.cpp
@@ -17,20 +17,132 @@
 #include "nsXPCOM.h"
 #include "nsIServiceManager.h"
 #include "nsIDOMWindow.h"
 #include "nsPromiseFlatString.h"
 #include "nsToolkitCompsCID.h"
 
 #endif // !MOZ_WIDGET_ANDROID
 
+#ifdef MOZ_PLACES
+#include "mozIAsyncFavicons.h"
+#include "nsIFaviconService.h"
+#endif // MOZ_PLACES
+
 using namespace mozilla;
 
 using mozilla::dom::ContentChild;
 
+namespace {
+
+#ifdef MOZ_PLACES
+
+class IconCallback final : public nsIFaviconDataCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  IconCallback(nsIAlertsService* aBackend,
+               nsIAlertNotification* aAlert,
+               nsIObserver* aAlertListener)
+    : mBackend(aBackend)
+    , mAlert(aAlert)
+    , mAlertListener(aAlertListener)
+  {}
+
+  NS_IMETHOD
+  OnComplete(nsIURI *aIconURI, uint32_t aIconSize, const uint8_t *aIconData,
+             const nsACString &aMimeType) override
+  {
+    nsresult rv = NS_ERROR_FAILURE;
+    if (aIconSize > 0) {
+      nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(mBackend));
+      if (alertsIconData) {
+        rv = alertsIconData->ShowAlertWithIconData(mAlert, mAlertListener,
+                                                   aIconSize, aIconData);
+      }
+    } else if (aIconURI) {
+      nsCOMPtr<nsIAlertsIconURI> alertsIconURI(do_QueryInterface(mBackend));
+      if (alertsIconURI) {
+        rv = alertsIconURI->ShowAlertWithIconURI(mAlert, mAlertListener,
+                                                 aIconURI);
+      }
+    }
+    if (NS_FAILED(rv)) {
+      rv = mBackend->ShowAlert(mAlert, mAlertListener);
+    }
+    return rv;
+  }
+
+private:
+  virtual ~IconCallback() {}
+
+  nsCOMPtr<nsIAlertsService> mBackend;
+  nsCOMPtr<nsIAlertNotification> mAlert;
+  nsCOMPtr<nsIObserver> mAlertListener;
+};
+
+NS_IMPL_ISUPPORTS(IconCallback, nsIFaviconDataCallback)
+
+#endif // MOZ_PLACES
+
+#ifndef MOZ_WIDGET_ANDROID
+
+nsresult
+ShowWithIconBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
+                    nsIObserver* aAlertListener)
+{
+#ifdef MOZ_PLACES
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = aAlert->GetURI(getter_AddRefs(uri));
+  if (NS_FAILED(rv) || !uri) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Ensure the backend supports favicons.
+  nsCOMPtr<nsIAlertsIconData> alertsIconData(do_QueryInterface(aBackend));
+  nsCOMPtr<nsIAlertsIconURI> alertsIconURI;
+  if (!alertsIconData) {
+    alertsIconURI = do_QueryInterface(aBackend);
+  }
+  if (!alertsIconData && !alertsIconURI) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  nsCOMPtr<mozIAsyncFavicons> favicons(do_GetService(
+    "@mozilla.org/browser/favicon-service;1"));
+  NS_ENSURE_TRUE(favicons, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsIFaviconDataCallback> callback =
+    new IconCallback(aBackend, aAlert, aAlertListener);
+  if (alertsIconData) {
+    return favicons->GetFaviconDataForPage(uri, callback);
+  }
+  return favicons->GetFaviconURLForPage(uri, callback);
+#else
+  return NS_ERROR_NOT_IMPLEMENTED;
+#endif // !MOZ_PLACES
+}
+
+nsresult
+ShowWithBackend(nsIAlertsService* aBackend, nsIAlertNotification* aAlert,
+                nsIObserver* aAlertListener)
+{
+  nsresult rv = ShowWithIconBackend(aBackend, aAlert, aAlertListener);
+  if (NS_SUCCEEDED(rv)) {
+    return rv;
+  }
+  // If the backend doesn't support favicons, show the alert without one.
+  return aBackend->ShowAlert(aAlert, aAlertListener);
+}
+
+#endif // MOZ_WIDGET_ANDROID
+
+} // anonymous namespace
+
 NS_IMPL_ISUPPORTS(nsAlertsService, nsIAlertsService, nsIAlertsDoNotDisturb, nsIAlertsProgressListener)
 
 nsAlertsService::nsAlertsService() :
   mBackend(nullptr)
 {
 #ifndef MOZ_WIDGET_ANDROID
   mBackend = do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID);
 #endif // MOZ_WIDGET_ANDROID
@@ -61,17 +173,17 @@ bool nsAlertsService::ShouldShowAlert()
   }
 
   ::FreeLibrary(shellDLL);
 #endif
 
   return result;
 }
 
-NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle, 
+NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl, const nsAString & aAlertTitle,
                                                      const nsAString & aAlertText, bool aAlertTextClickable,
                                                      const nsAString & aAlertCookie,
                                                      nsIObserver * aAlertListener,
                                                      const nsAString & aAlertName,
                                                      const nsAString & aBidi,
                                                      const nsAString & aLang,
                                                      const nsAString & aData,
                                                      nsIPrincipal * aPrincipal,
@@ -130,17 +242,17 @@ NS_IMETHODIMP nsAlertsService::ShowAlert
   NS_ENSURE_SUCCESS(rv, rv);
 
   mozilla::AndroidBridge::Bridge()->ShowAlertNotification(imageUrl, title, text, cookie,
                                                           aAlertListener, name, principal);
   return NS_OK;
 #else
   // Check if there is an optional service that handles system-level notifications
   if (mBackend) {
-    rv = mBackend->ShowAlert(aAlert, aAlertListener);
+    rv = ShowWithBackend(mBackend, aAlert, aAlertListener);
     if (NS_SUCCEEDED(rv)) {
       return rv;
     }
     // If the system backend failed to show the alert, clear the backend and
     // retry with XUL notifications. Future alerts will always use XUL.
     mBackend = nullptr;
   }
 
@@ -149,17 +261,17 @@ NS_IMETHODIMP nsAlertsService::ShowAlert
     if (aAlertListener)
       aAlertListener->Observe(nullptr, "alertfinished", cookie.get());
     return NS_OK;
   }
 
   // Use XUL notifications as a fallback if above methods have failed.
   nsCOMPtr<nsIAlertsService> xulBackend(nsXULAlerts::GetInstance());
   NS_ENSURE_TRUE(xulBackend, NS_ERROR_FAILURE);
-  return xulBackend->ShowAlert(aAlert, aAlertListener);
+  return ShowWithBackend(xulBackend, aAlert, aAlertListener);
 #endif // !MOZ_WIDGET_ANDROID
 }
 
 NS_IMETHODIMP nsAlertsService::CloseAlert(const nsAString& aAlertName,
                                           nsIPrincipal* aPrincipal)
 {
   if (XRE_IsContentProcess()) {
     ContentChild* cpc = ContentChild::GetSingleton();
--- a/toolkit/components/alerts/nsIAlertsService.idl
+++ b/toolkit/components/alerts/nsIAlertsService.idl
@@ -2,22 +2,23 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 #include "nsIObserver.idl"
 
 interface nsIPrincipal;
+interface nsIURI;
 
 %{C++
 #define ALERT_NOTIFICATION_CONTRACTID "@mozilla.org/alert-notification;1"
 %}
 
-[scriptable, uuid(9e87fc34-8dbb-4b14-a724-b27be6822ec8)]
+[scriptable, uuid(1650a064-79d5-4eb6-8c9e-57dd6522b6ac)]
 interface nsIAlertNotification : nsISupports
 {
   /** Initializes an alert notification. */
   void init([optional] in AString name,
             [optional] in AString imageURL,
             [optional] in AString title,
             [optional] in AString text,
             [optional] in boolean textClickable,
@@ -81,16 +82,22 @@ interface nsIAlertNotification : nsISupp
 
   /**
    * The principal of the page that created the alert. Used for IPC security
    * checks, and to determine whether the alert is actionable.
    */
   readonly attribute nsIPrincipal principal;
 
   /**
+   * The URI of the page that created the alert. |null| if the alert is not
+   * actionable.
+   */
+  readonly attribute nsIURI URI;
+
+  /**
    * Controls the image loading behavior. If true, the image URL will be loaded
    * in private browsing mode.
    */
   readonly attribute boolean inPrivateBrowsing;
 
   /**
    * Indicates whether this alert should show the source string and action
    * buttons. False for system alerts (which can omit the principal), or
@@ -219,8 +226,35 @@ interface nsIAlertsProgressListener : ns
    * Called to cancel and hide the given notification previously displayed
    * with showAlertNotification().
    *
    * @param name         The name of the notification.
    */
   void onCancel(in AString name);
 };
 
+[scriptable, uuid(fc6d7f0a-0cf6-4268-8c71-ab640842b9b1)]
+interface nsIAlertsIconData : nsISupports
+{
+  /**
+   * Shows an alert with an icon. Web notifications use the favicon of the
+   * page that created the alert. If the favicon is not in the Places database,
+   * |iconSize| will be zero.
+  */
+  void showAlertWithIconData(in nsIAlertNotification alert,
+                             [optional] in nsIObserver alertListener,
+                             [optional] in uint32_t iconSize,
+                             [const, array, size_is(iconSize)] in uint8_t
+                                                               iconData);
+};
+
+[scriptable, uuid(f3c82915-bf60-41ea-91ce-6c46b22e381a)]
+interface nsIAlertsIconURI : nsISupports
+{
+  /**
+   * Shows an alert with an icon URI. Web notifications use |moz-anno:|
+   * URIs to reference favicons from Places. If the page doesn't have a
+   * favicon, |iconURI| will be |null|.
+   */
+  void showAlertWithIconURI(in nsIAlertNotification alert,
+                            [optional] in nsIObserver alertListener,
+                            [optional] in nsIURI iconURI);
+};
--- a/toolkit/components/extensions/.eslintrc
+++ b/toolkit/components/extensions/.eslintrc
@@ -24,35 +24,36 @@
     "Services": true,
     "TabManager": true,
     "XPCOMUtils": true,
   },
 
   "rules": {
     // Rules from the mozilla plugin
     "mozilla/balanced-listeners": 2,
-    "mozilla/components-imports": 1,
-    "mozilla/import-headjs-globals": 1,
     "mozilla/mark-test-function-used": 1,
     "mozilla/no-aArgs": 1,
     "mozilla/no-cpows-in-tests": 1,
     "mozilla/var-only-at-top-level": 1,
 
     // Braces only needed for multi-line arrow function blocks
     // "arrow-body-style": [2, "as-needed"],
 
     // Require spacing around =>
     "arrow-spacing": 2,
 
     // Always require spacing around a single line block
     "block-spacing": 1,
 
+    // Forbid spaces inside the curly brackets of object literals.
+    "object-curly-spacing": [2, "never"],
+
     // Enforce one true brace style (opening brace on the same line) and avoid
     // start and end braces on the same line.
-    "brace-style": [2, "1tbs", { "allowSingleLine": true }],
+    "brace-style": [2, "1tbs", {"allowSingleLine": true}],
 
     // No space before always a space after a comma
     "comma-spacing": [2, {"before": false, "after": true}],
 
     // Commas at the end of the line not the start
     "comma-style": 2,
 
     // Don't require spaces around computed properties
@@ -66,20 +67,20 @@
 
     // Always require a trailing EOL
     "eol-last": 2,
 
     // Require function* name()
     "generator-star-spacing": [2, {"before": false, "after": true}],
 
     // Two space indent
-    "indent": [2, 2, { "SwitchCase": 1 }],
+    "indent": [2, 2, {"SwitchCase": 1}],
 
     // Space after colon not before in property declarations
-    "key-spacing": [2, { "beforeColon": false, "afterColon": true, "mode": "minimum" }],
+    "key-spacing": [2, {"beforeColon": false, "afterColon": true, "mode": "minimum"}],
 
     // Unix linebreaks
     "linebreak-style": [2, "unix"],
 
     // Always require parenthesis for new calls
     "new-parens": 2,
 
     // Use [] instead of Array()
@@ -186,17 +187,17 @@
 
     // No unreachable statements
     "no-unreachable": 2,
 
     // No expressions where a statement is expected
     "no-unused-expressions": 2,
 
     // No declaring variables that are never used
-    "no-unused-vars": [2, { "args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$" }],
+    "no-unused-vars": [2, {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}],
 
     // No using variables before defined
     "no-use-before-define": 2,
 
     // No using with
     "no-with": 2,
 
     // Always require semicolon at end of statement
@@ -204,33 +205,33 @@
 
     // Require space after keywords
     "space-after-keywords": 2,
 
     // Require space before blocks
     "space-before-blocks": 2,
 
     // Never use spaces before function parentheses
-    "space-before-function-paren": [2, { "anonymous": "never", "named": "never" }],
+    "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
 
     // Require spaces before finally, catch, etc.
     "space-before-keywords": [2, "always"],
 
     // No space padding in parentheses
     "space-in-parens": [2, "never"],
 
     // Require spaces around operators, except for a|0.
     "space-infix-ops": [2, {"int32Hint": true}],
 
 
     // Require spaces after return, throw and case
     "space-return-throw-case": 2,
 
     // ++ and -- should not need spacing
-    "space-unary-ops": [1, { "nonwords": false }],
+    "space-unary-ops": [1, {"nonwords": false}],
 
     // No comparisons to NaN
     "use-isnan": 2,
 
     // Only check typeof against valid results
     "valid-typeof": 2,
 
     // Disallow using variables outside the blocks they are defined (especially
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -411,17 +411,17 @@ GlobalManager = {
             let promise;
             try {
               promise = findPath(path)[name](...args);
             } catch (e) {
               if (e instanceof context.cloneScope.Error) {
                 promise = Promise.reject(e);
               } else {
                 Cu.reportError(e);
-                promise = Promise.reject({ message: "An unexpected error occurred" });
+                promise = Promise.reject({message: "An unexpected error occurred"});
               }
             }
 
             return context.wrapPromise(promise || Promise.resolve(), callback);
           },
 
           getProperty(path, name) {
             return findPath(path)[name];
@@ -446,17 +446,17 @@ GlobalManager = {
 
       injectObject("browser", null);
       injectObject("chrome", () => {});
     };
 
     let id = ExtensionManagement.getAddonIdForWindow(contentWindow);
 
     // We don't inject privileged APIs into sub-frames of a UI page.
-    const { FULL_PRIVILEGES } = ExtensionManagement.API_LEVELS;
+    const {FULL_PRIVILEGES} = ExtensionManagement.API_LEVELS;
     if (ExtensionManagement.getAPILevelForWindow(contentWindow, id) !== FULL_PRIVILEGES) {
       return;
     }
 
     // We don't inject privileged APIs if the addonId is null
     // or doesn't exist.
     if (!this.extensionMap.has(id)) {
       return;
@@ -606,17 +606,17 @@ ExtensionData.prototype = {
 
       NetUtil.asyncFetch({uri, loadUsingSystemPrincipal: true}, (inputStream, status) => {
         if (!Components.isSuccessCode(status)) {
           reject(new Error(status));
           return;
         }
         try {
           let text = NetUtil.readInputStreamToString(inputStream, inputStream.available(),
-                                                     { charset: "utf-8" });
+                                                     {charset: "utf-8"});
           resolve(JSON.parse(text));
         } catch (e) {
           reject(e);
         }
       });
     });
   },
 
@@ -763,17 +763,17 @@ ExtensionData.prototype = {
   // If no locales are unavailable, resolves to |null|.
   initLocale: Task.async(function* (locale = this.defaultLocale) {
     if (locale == null) {
       return null;
     }
 
     let promises = [this.readLocaleFile(locale)];
 
-    let { defaultLocale } = this;
+    let {defaultLocale} = this;
     if (locale != defaultLocale && !this.localeData.has(defaultLocale)) {
       promises.push(this.readLocaleFile(defaultLocale));
     }
 
     let results = yield Promise.all(promises);
 
     this.localeData.selectedLocale = locale;
     return results[0];
@@ -1095,17 +1095,17 @@ Extension.prototype = extend(Object.crea
   // Reads the locale file for the given Gecko-compatible locale code, or if
   // no locale is given, the available locale closest to the UI locale.
   // Sets the currently selected locale on success.
   initLocale: Task.async(function* (locale = undefined) {
     if (locale === undefined) {
       let locales = yield this.promiseLocales();
 
       let localeList = Array.from(locales.keys(), locale => {
-        return { name: locale, locales: [locale] };
+        return {name: locale, locales: [locale]};
       });
 
       let match = Locale.findClosestLocale(localeList);
       locale = match ? match.name : this.defaultLocale;
     }
 
     return ExtensionData.prototype.initLocale.call(this, locale);
   }),
@@ -1188,17 +1188,17 @@ Extension.prototype = extend(Object.crea
     for (let obj of this.onShutdown) {
       obj.close();
     }
 
     Management.emit("shutdown", this);
 
     Services.ppmm.broadcastAsyncMessage("Extension:Shutdown", {id: this.id});
 
-    MessageChannel.abortResponses({ extensionId: this.id });
+    MessageChannel.abortResponses({extensionId: this.id});
 
     ExtensionManagement.shutdownExtension(this.uuid);
 
     this.cleanupGeneratedFile();
   },
 
   observe(subject, topic, data) {
     if (topic == "xpcom-shutdown") {
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -238,17 +238,17 @@ var ExtensionManager;
 
 // Scope in which extension content script code can run. It uses
 // Cu.Sandbox to run the code. There is a separate scope for each
 // frame.
 class ExtensionContext extends BaseContext {
   constructor(extensionId, contentWindow, contextOptions = {}) {
     super();
 
-    let { isExtensionPage } = contextOptions;
+    let {isExtensionPage} = contextOptions;
 
     this.isExtensionPage = isExtensionPage;
     this.extension = ExtensionManager.get(extensionId);
     this.extensionId = extensionId;
     this.contentWindow = contentWindow;
 
     let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
@@ -336,18 +336,18 @@ class ExtensionContext extends BaseConte
 
   close() {
     super.unload();
 
     // Overwrite the content script APIs with an empty object if the APIs objects are still
     // defined in the content window (See Bug 1214658 for rationale).
     if (this.isExtensionPage && !Cu.isDeadWrapper(this.contentWindow) &&
         Cu.waiveXrays(this.contentWindow).browser === this.chromeObj) {
-      Cu.createObjectIn(this.contentWindow, { defineAs: "browser" });
-      Cu.createObjectIn(this.contentWindow, { defineAs: "chrome" });
+      Cu.createObjectIn(this.contentWindow, {defineAs: "browser"});
+      Cu.createObjectIn(this.contentWindow, {defineAs: "chrome"});
     }
     Cu.nukeSandbox(this.sandbox);
     this.sandbox = null;
   }
 }
 
 function windowId(window) {
   return window.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -399,33 +399,33 @@ var DocumentManager = {
       // was called on (i.e., not frames for social or sidebars).
       let mm = getWindowMessageManager(window);
       if (!mm || !ExtensionContent.globals.has(mm)) {
         return;
       }
 
       // Enable the content script APIs should be available in subframes' window
       // if it is recognized as a valid addon id (see Bug 1214658 for rationale).
-      const { CONTENTSCRIPT_PRIVILEGES } = ExtensionManagement.API_LEVELS;
+      const {CONTENTSCRIPT_PRIVILEGES} = ExtensionManagement.API_LEVELS;