merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 17 Feb 2016 12:07:39 +0100
changeset 284520 c007ec81b75ae1c51da2c2d2bab21180290fab6b
parent 284519 15621f98b53b1994c7ae2e2703a6e50203c5304c (current diff)
parent 284379 db7c5f35321fae1b076a088553ec92a9b89d883b (diff)
child 284521 937e039f8b8c14ed1b8f6a4a045ce847dbf82f0d
child 284532 7ced13d644be8b05f0de4beeaaa3458910eb2ad6
child 284596 60f020c84b23b526517f01b1c24f92a236d2bb11
push id17648
push usercbook@mozilla.com
push dateWed, 17 Feb 2016 11:20:27 +0000
treeherderfx-team@937e039f8b8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
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;
       let extensionId = ExtensionManagement.getAddonIdForWindow(window);
 
       if (ExtensionManagement.getAPILevelForWindow(window, extensionId) == CONTENTSCRIPT_PRIVILEGES &&
           ExtensionManager.get(extensionId)) {
         DocumentManager.getExtensionPageContext(extensionId, window);
       }
 
       this.trigger("document_start", window);
       /* eslint-disable mozilla/balanced-listeners */
       window.addEventListener("DOMContentLoaded", this, true);
       window.addEventListener("load", this, true);
       /* eslint-enable mozilla/balanced-listeners */
     } else if (topic == "inner-window-destroyed") {
       let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
 
-      MessageChannel.abortResponses({ innerWindowID: windowId });
+      MessageChannel.abortResponses({innerWindowID: windowId});
 
       // Close any existent content-script context for the destroyed window.
       if (this.contentScriptWindows.has(windowId)) {
         let extensions = this.contentScriptWindows.get(windowId);
         for (let [, context] of extensions) {
           context.close();
         }
 
@@ -501,17 +501,17 @@ var DocumentManager = {
     return extensions.get(extensionId);
   },
 
   getExtensionPageContext(extensionId, window) {
     let winId = windowId(window);
 
     let context = this.extensionPageWindows.get(winId);
     if (!context) {
-      let context = new ExtensionContext(extensionId, window, { isExtensionPage: true });
+      let context = new ExtensionContext(extensionId, window, {isExtensionPage: true});
       this.extensionPageWindows.set(winId, context);
     }
 
     return context;
   },
 
   startupExtension(extensionId) {
     if (this.extensionCount == 0) {
@@ -548,17 +548,17 @@ var DocumentManager = {
     // Clean up iframe extension page contexts on extension shutdown.
     for (let [winId, context] of this.extensionPageWindows) {