Merge autoland to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 19 May 2017 11:37:57 -0400
changeset 409665 52b207e50972b74546b89775b6dd7c182ff6ff6a
parent 409513 1ea1b40c2679da341d50d27226c61033bac06711 (current diff)
parent 409664 74a3dc70ad7d6b9fe4f81e9f0a9910d6612e639b (diff)
child 409736 c800b6dfca673f8cb0f3cf41e4f495755d04bbb3
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to m-c. a=merge
browser/base/content/sync/aboutSyncTabs-bindings.xml
browser/base/content/sync/aboutSyncTabs.css
browser/base/content/sync/aboutSyncTabs.js
browser/base/content/sync/aboutSyncTabs.xul
browser/base/content/test/general/browser_tabopen_reflows.js
browser/base/content/test/general/browser_windowopen_reflows.js
browser/base/content/test/windows/.eslintrc.js
browser/base/content/test/windows/browser.ini
browser/base/content/test/windows/browser_toolbariconcolor_restyles.js
browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd
browser/themes/linux/aboutSyncTabs.css
browser/themes/osx/aboutSyncTabs.css
browser/themes/windows/aboutSyncTabs.css
layout/reftests/bugs/346189-1-ref.xul
layout/reftests/bugs/346189-1.xul
memory/mozjemalloc/ql.h
memory/mozjemalloc/qr.h
security/manager/ssl/nsCrypto.cpp
security/manager/ssl/nsCrypto.h
toolkit/components/places/tests/unit/test_getPlacesInfo.js
--- a/addon-sdk/source/python-lib/cuddlefish/prefs.py
+++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py
@@ -50,22 +50,23 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
     'media.gmp-manager.cert.requireBuiltIn' : False,
     'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager',
     'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml',
     'media.gmp-manager.updateEnabled': False,
     'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy',
     'browser.newtab.url' : 'about:blank',
     'browser.search.update': False,
     'browser.search.suggest.enabled' : False,
+    'browser.safebrowsing.downloads.remote.url': 'http://localhost/safebrowsing-dummy/downloads',
+    'browser.safebrowsing.malware.enabled' : False,
     'browser.safebrowsing.phishing.enabled' : False,
     'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.google4.updateURL': 'http://localhost/safebrowsing4-dummy/update',
     'browser.safebrowsing.provider.google4.gethashURL': 'http://localhost/safebrowsing4-dummy/gethash',
-    'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
     'browser.selfsupport.url': 'https://localhost/selfsupport-dummy',
     'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
 
     # Disable app update
     'app.update.enabled' : False,
     'app.update.staging.enabled': False,
 
@@ -113,16 +114,17 @@ DEFAULT_FIREFOX_PREFS = {
     'devtools.browsertoolbox.panel': 'jsdebugger',
     'devtools.chrome.enabled' : True,
 
     # From:
     # https://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388
     # Make url-classifier updates so rare that they won't affect tests.
     'urlclassifier.updateinterval' : 172800,
     # Point the url-classifier to a nonexistent local URL for fast failures.
+    'browser.safebrowsing.downloads.remote.url': 'http://localhost/safebrowsing-dummy/downloads',
     'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update',
     'browser.safebrowsing.provider.google4.gethashURL' : 'http://localhost/safebrowsing4-dummy/gethash',
     'browser.safebrowsing.provider.google4.updateURL' : 'http://localhost/safebrowsing4-dummy/update',
     'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
     'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update',
 }
 
--- a/addon-sdk/source/test/preferences/no-connections.json
+++ b/addon-sdk/source/test/preferences/no-connections.json
@@ -10,16 +10,18 @@
   "media.gmp-manager.cert.requireBuiltIn": false,
   "media.gmp-manager.url": "http://localhost/media-dummy/gmpmanager",
   "media.gmp-manager.url.override": "http://localhost/dummy-gmp-manager.xml",
   "media.gmp-manager.updateEnabled": false,
   "browser.aboutHomeSnippets.updateUrl": "https://localhost/snippet-dummy",
   "browser.newtab.url": "about:blank",
   "browser.search.update": false,
   "browser.search.suggest.enabled": false,
+  "browser.safebrowsing.downloads.remote.url": "http://localhost/safebrowsing-dummy/downloads",
+  "browser.safebrowsing.malware.enabled": false,
   "browser.safebrowsing.phishing.enabled": false,
   "browser.safebrowsing.provider.google.updateURL": "http://localhost/safebrowsing-dummy/update",
   "browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
   "browser.safebrowsing.provider.google.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
   "browser.safebrowsing.provider.google4.updateURL": "http://localhost/safebrowsing4-dummy/update",
   "browser.safebrowsing.provider.google4.gethashURL": "http://localhost/safebrowsing4-dummy/gethash",
   "browser.safebrowsing.provider.google4.reportURL": "http://localhost/safebrowsing4-dummy/malwarereport",
   "browser.selfsupport.url": "https://localhost/selfsupport-dummy",
--- a/browser/base/content/aboutDialog-appUpdater.js
+++ b/browser/base/content/aboutDialog-appUpdater.js
@@ -170,16 +170,24 @@ appUpdater.prototype =
    */
   selectPanel(aChildID) {
     let panel = document.getElementById(aChildID);
 
     let button = panel.querySelector("button");
     if (button) {
       if (aChildID == "downloadAndInstall") {
         let updateVersion = gAppUpdater.update.displayVersion;
+        // Include the build ID if this is an "a#" (nightly or aurora) build
+        if (/a\d+$/.test(updateVersion)) {
+          let buildID = gAppUpdater.update.buildID;
+          let year = buildID.slice(0, 4);
+          let month = buildID.slice(4, 6);
+          let day = buildID.slice(6, 8);
+          updateVersion += ` (${year}-${month}-${day})`;
+        }
         button.label = this.bundle.formatStringFromName("update.downloadAndInstallButton.label", [updateVersion], 1);
         button.accessKey = this.bundle.GetStringFromName("update.downloadAndInstallButton.accesskey");
       }
       this.updateDeck.selectedPanel = panel;
       if (!document.commandDispatcher.focusedElement || // don't steal the focus
           document.commandDispatcher.focusedElement.localName == "button") // except from the other buttons
         button.focus();
 
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -477,17 +477,17 @@ var PlacesCommandHook = {
       // Bug 1148838 - Make this code work for full page plugins.
       let description = null;
       let charset = null;
 
       let docInfo = await this._getPageDetails(aBrowser);
 
       try {
         info.title = docInfo.isErrorPage ?
-          (await PlacesUtils.promisePlaceInfo(aBrowser.currentURI)).title :
+          (await PlacesUtils.history.fetch(aBrowser.currentURI)).title :
           aBrowser.contentTitle;
         info.title = info.title || url.href;
         description = docInfo.description;
         charset = aBrowser.characterSet;
       } catch (e) {
         Components.utils.reportError(e);
       }
 
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -20,19 +20,16 @@ with Files("docs/**"):
     BUG_COMPONENT = ("Core", "Security")
 
 with Files("newtab/**"):
     BUG_COMPONENT = ("Firefox", "New Tab Page")
 
 with Files("pageinfo/**"):
     BUG_COMPONENT = ("Firefox", "Page Info Window")
 
-with Files("sync/**"):
-    BUG_COMPONENT = ("Firefox", "Sync")
-
 with Files("test/alerts/**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("test/appUpdate/**"):
     BUG_COMPONENT = ("Toolkit", "Application Update")
 
 with Files("test/captivePortal/**"):
     BUG_COMPONENT = ("Firefox", "General")
deleted file mode 100644
--- a/browser/base/content/sync/aboutSyncTabs-bindings.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?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/. -->
-
-<!-- import-globals-from aboutSyncTabs.js -->
-
-<bindings id="tabBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="tab-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <content>
-      <xul:hbox flex="1">
-        <xul:vbox pack="start">
-          <xul:image class="tabIcon"
-                     xbl:inherits="src=icon"/>
-        </xul:vbox>
-        <xul:vbox pack="start" flex="1">
-            <xul:label xbl:inherits="value=title,selected"
-                       crop="end" flex="1" class="title"/>
-            <xul:label xbl:inherits="value=url,selected"
-                       crop="end" flex="1" class="url"/>
-        </xul:vbox>
-      </xul:hbox>
-    </content>
-    <handlers>
-      <handler event="dblclick" button="0">
-        <![CDATA[
-          RemoteTabViewer.openSelected();
-        ]]>
-      </handler>
-    </handlers>
-  </binding>
-
-  <binding id="client-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <content>
-      <xul:hbox pack="start" align="center" onfocus="event.target.blur()" onselect="return false;">
-        <xul:image/>
-        <xul:label xbl:inherits="value=clientName"
-                   class="clientName"
-                   crop="center" flex="1"/>
-      </xul:hbox>
-    </content>
-  </binding>
-</bindings>
deleted file mode 100644
--- a/browser/base/content/sync/aboutSyncTabs.css
+++ /dev/null
@@ -1,11 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-richlistitem[type="tab"] {
-  -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
-}
-
-richlistitem[type="client"] {
-  -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
-}
deleted file mode 100644
--- a/browser/base/content/sync/aboutSyncTabs.js
+++ /dev/null
@@ -1,305 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* import-globals-from ../utilityOverlay.js */
-
-var Cu = Components.utils;
-
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-sync/main.js");
-Cu.import("resource:///modules/PlacesUIUtils.jsm");
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
-
-var RemoteTabViewer = {
-  _tabsList: null,
-
-  init() {
-    Services.obs.addObserver(this, "weave:service:login:finish");
-    Services.obs.addObserver(this, "weave:engine:sync:finish");
-
-    this._tabsList = document.getElementById("tabsList");
-
-    this.buildList(true);
-  },
-
-  uninit() {
-    Services.obs.removeObserver(this, "weave:service:login:finish");
-    Services.obs.removeObserver(this, "weave:engine:sync:finish");
-  },
-
-  createItem(attrs) {
-    let item = document.createElement("richlistitem");
-
-    // Copy the attributes from the argument into the item.
-    for (let attr in attrs) {
-      item.setAttribute(attr, attrs[attr]);
-    }
-
-    if (attrs["type"] == "tab") {
-      item.label = attrs.title != "" ? attrs.title : attrs.url;
-    }
-
-    return item;
-  },
-
-  filterTabs(event) {
-    let val = event.target.value.toLowerCase();
-    let numTabs = this._tabsList.getRowCount();
-    let clientTabs = 0;
-    let currentClient = null;
-
-    for (let i = 0; i < numTabs; i++) {
-      let item = this._tabsList.getItemAtIndex(i);
-      let hide = false;
-      if (item.getAttribute("type") == "tab") {
-        if (!item.getAttribute("url").toLowerCase().includes(val) &&
-            !item.getAttribute("title").toLowerCase().includes(val)) {
-          hide = true;
-        } else {
-          clientTabs++;
-        }
-      } else if (item.getAttribute("type") == "client") {
-        if (currentClient) {
-          if (clientTabs == 0) {
-            currentClient.hidden = true;
-          }
-        }
-        currentClient = item;
-        clientTabs = 0;
-      }
-      item.hidden = hide;
-    }
-    if (clientTabs == 0) {
-      currentClient.hidden = true;
-    }
-  },
-
-  openSelected() {
-    let items = this._tabsList.selectedItems;
-    let urls = [];
-    for (let i = 0; i < items.length; i++) {
-      if (items[i].getAttribute("type") == "tab") {
-        urls.push(items[i].getAttribute("url"));
-        let index = this._tabsList.getIndexOfItem(items[i]);
-        this._tabsList.removeItemAt(index);
-      }
-    }
-    if (urls.length) {
-      getTopWin().gBrowser.loadTabs(urls);
-      this._tabsList.clearSelection();
-    }
-  },
-
-  bookmarkSingleTab() {
-    let item = this._tabsList.selectedItems[0];
-    let uri = Weave.Utils.makeURI(item.getAttribute("url"));
-    let title = item.getAttribute("title");
-    PlacesUIUtils.showBookmarkDialog({ action: "add"
-                                     , type: "bookmark"
-                                     , uri
-                                     , title
-                                     , hiddenRows: [ "description"
-                                                   , "location"
-                                                   , "loadInSidebar"
-                                                   , "keyword" ]
-                                     }, window.top);
-  },
-
-  bookmarkSelectedTabs() {
-    let items = this._tabsList.selectedItems;
-    let URIs = [];
-    for (let i = 0; i < items.length; i++) {
-      if (items[i].getAttribute("type") == "tab") {
-        let uri = Weave.Utils.makeURI(items[i].getAttribute("url"));
-        if (!uri) {
-          continue;
-        }
-
-        URIs.push(uri);
-      }
-    }
-    if (URIs.length) {
-      PlacesUIUtils.showBookmarkDialog({ action: "add"
-                                       , type: "folder"
-                                       , URIList: URIs
-                                       , hiddenRows: [ "description" ]
-                                       }, window.top);
-    }
-  },
-
-  getIcon(iconUri, defaultIcon) {
-    try {
-      let iconURI = Weave.Utils.makeURI(iconUri);
-      return PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
-    } catch (ex) {
-      // Do nothing.
-    }
-
-    // Just give the provided default icon or the system's default.
-    return defaultIcon || PlacesUtils.favicons.defaultFavicon.spec;
-  },
-
-  _waitingForBuildList: false,
-
-  _buildListRequested: false,
-
-  buildList(forceSync) {
-    if (this._waitingForBuildList) {
-      this._buildListRequested = true;
-      return;
-    }
-
-    this._waitingForBuildList = true;
-    this._buildListRequested = false;
-
-    this._clearTabList();
-
-    if (Weave.Service.isLoggedIn) {
-      this._refetchTabs(forceSync);
-      this._generateWeaveTabList();
-    } else {
-      // XXXzpao We should say something about not being logged in & not having data
-      //        or tell the appropriate condition. (bug 583344)
-    }
-
-    this._waitingForBuildList = false;
-    if (this._buildListRequested) {
-      CommonUtils.nextTick(this.buildList, this);
-    }
-  },
-
-  _clearTabList() {
-    let list = this._tabsList;
-
-    // Clear out existing richlistitems.
-    let count = list.getRowCount();
-    if (count > 0) {
-      for (let i = count - 1; i >= 0; i--) {
-        list.removeItemAt(i);
-      }
-    }
-  },
-
-  _generateWeaveTabList() {
-    let engine = Weave.Service.engineManager.get("tabs");
-    let list = this._tabsList;
-
-    let seenURLs = new Set();
-    let localURLs = engine.getOpenURLs();
-
-    for (let [, client] of Object.entries(engine.getAllClients())) {
-      // Create the client node, but don't add it in-case we don't show any tabs
-      let appendClient = true;
-
-      client.tabs.forEach(function({title, urlHistory, icon}) {
-        let url = urlHistory[0];
-        if (!url || localURLs.has(url) || seenURLs.has(url)) {
-          return;
-        }
-        seenURLs.add(url);
-
-        if (appendClient) {
-          let attrs = {
-            type: "client",
-            clientName: client.clientName,
-            class: Weave.Service.clientsEngine.isMobile(client.id) ? "mobile" : "desktop"
-          };
-          let clientEnt = this.createItem(attrs);
-          list.appendChild(clientEnt);
-          appendClient = false;
-          clientEnt.disabled = true;
-        }
-        let attrs = {
-          type:  "tab",
-          title: title || url,
-          url,
-          icon:  this.getIcon(icon),
-        }
-        let tab = this.createItem(attrs);
-        list.appendChild(tab);
-      }, this);
-    }
-  },
-
-  adjustContextMenu(event) {
-    let mode = "all";
-    switch (this._tabsList.selectedItems.length) {
-      case 0:
-        break;
-      case 1:
-        mode = "single"
-        break;
-      default:
-        mode = "multiple";
-        break;
-    }
-
-    let menu = document.getElementById("tabListContext");
-    let el = menu.firstChild;
-    while (el) {
-      let showFor = el.getAttribute("showFor");
-      if (showFor) {
-        el.hidden = showFor != mode && showFor != "all";
-      }
-
-      el = el.nextSibling;
-    }
-  },
-
-  _refetchTabs(force) {
-    if (!force) {
-      // Don't bother refetching tabs if we already did so recently
-      let lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch", 0);
-
-      let now = Math.floor(Date.now() / 1000);
-      if (now - lastFetch < 30) {
-        return false;
-      }
-    }
-
-    // Ask Sync to just do the tabs engine if it can.
-    Weave.Service.sync(["tabs"]);
-    Services.prefs.setIntPref("services.sync.lastTabFetch",
-                              Math.floor(Date.now() / 1000));
-
-    return true;
-  },
-
-  observe(subject, topic, data) {
-    switch (topic) {
-      case "weave:service:login:finish":
-        // A login has finished, which means that a Sync is about to start and
-        // we will eventually get to the "tabs" engine - but try and force the
-        // tab engine to sync first by passing |true| for the forceSync param.
-        this.buildList(true);
-        break;
-      case "weave:engine:sync:finish":
-        if (data == "tabs") {
-          // The tabs engine just finished, so re-build the list without
-          // forcing a new sync of the tabs engine.
-          this.buildList(false);
-        }
-        break;
-    }
-  },
-
-  handleClick(event) {
-    if (event.target.getAttribute("type") != "tab") {
-      return;
-    }
-
-    if (event.button == 1) {
-      let url = event.target.getAttribute("url");
-      openUILink(url, event);
-      let index = this._tabsList.getIndexOfItem(event.target);
-      this._tabsList.removeItemAt(index);
-    }
-  }
-}
deleted file mode 100644
--- a/browser/base/content/sync/aboutSyncTabs.xul
+++ /dev/null
@@ -1,68 +0,0 @@
-<?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://browser/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
-
-<!DOCTYPE window [
-  <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
-  %aboutSyncTabsDTD;
-]>
-
-<window id="tabs-display"
-        onload="RemoteTabViewer.init()"
-        onunload="RemoteTabViewer.uninit()"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        title="&tabs.otherDevices.label;">
-  <script type="application/javascript" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
-  <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
-  <html:head>
-    <html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
-  </html:head>
-
-  <popupset id="contextmenus">
-    <menupopup id="tabListContext">
-      <menuitem label="&tabs.context.openTab.label;"
-                accesskey="&tabs.context.openTab.accesskey;"
-                oncommand="RemoteTabViewer.openSelected()"
-                showFor="single"/>
-      <menuitem label="&tabs.context.bookmarkSingleTab.label;"
-                accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
-                oncommand="RemoteTabViewer.bookmarkSingleTab(event)"
-                showFor="single"/>
-      <menuitem label="&tabs.context.openMultipleTabs.label;"
-                accesskey="&tabs.context.openMultipleTabs.accesskey;"
-                oncommand="RemoteTabViewer.openSelected()"
-                showFor="multiple"/>
-      <menuitem label="&tabs.context.bookmarkMultipleTabs.label;"
-                accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
-                oncommand="RemoteTabViewer.bookmarkSelectedTabs()"
-                showFor="multiple"/>
-      <menuseparator/>
-      <menuitem label="&tabs.context.refreshList.label;"
-                accesskey="&tabs.context.refreshList.accesskey;"
-                oncommand="RemoteTabViewer.buildList()"
-                showFor="all"/>
-    </menupopup>
-  </popupset>
-  <richlistbox context="tabListContext" id="tabsList" seltype="multiple"
-               align="center" flex="1"
-               onclick="RemoteTabViewer.handleClick(event)"
-               oncontextmenu="RemoteTabViewer.adjustContextMenu(event)">
-    <hbox id="headers" align="center">
-      <label id="tabsListHeading"
-             value="&tabs.otherDevices.label;"/>
-      <spacer flex="1"/>
-      <textbox type="search"
-               emptytext="&tabs.searchText.label;"
-               oncommand="RemoteTabViewer.filterTabs(event)"/>
-    </hbox>
-
-  </richlistbox>
-</window>
-
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -501,18 +501,16 @@ skip-if = buildapp == 'mulet' || (e10s &
 skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
                                        # Disabled on OS X because of bug 967917
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabfocus.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabkeynavigation.js]
 skip-if = (os == "mac" && !e10s) # Bug 1237713 - OSX eats keypresses for some reason
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_tabopen_reflows.js]
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_close_beforeunload.js]
 support-files =
   close_beforeunload_opens_second_tab.html
   close_beforeunload.html
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_isActive.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_tabs_owner.js]
@@ -579,19 +577,16 @@ skip-if = true # Bug 1005420 - fails int
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs_contextMenu.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug)
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_web_channel.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_windowopen_reflows.js]
-skip-if = os == "mac" # bug 1339317
-# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_zbug569342.js]
 skip-if = e10s || debug # Bug 1094240 - has findbar-related failures
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_registerProtocolHandler_notification.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_addCertException.js]
 # DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
 [browser_e10s_about_page_triggeringprincipal.js]
rename from browser/base/content/test/windows/.eslintrc.js
rename to browser/base/content/test/performance/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+support-files =
+  head.js
+[browser_tabclose_reflows.js]
+[browser_tabopen_reflows.js]
+[browser_toolbariconcolor_restyles.js]
+[browser_windowopen_reflows.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/browser_tabclose_reflows.js
@@ -0,0 +1,71 @@
+"use strict";
+
+/**
+ * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
+ * is a whitelist that should slowly go away as we improve the performance of
+ * the front-end. Instead of adding more reflows to the whitelist, you should
+ * be modifying your code to avoid the reflow.
+ *
+ * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
+ * for tips on how to do that.
+ */
+const EXPECTED_REFLOWS = [
+  [
+    "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
+  ],
+];
+
+if (gMultiProcessBrowser) {
+  EXPECTED_REFLOWS.push(
+    [
+      "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
+    ],
+  );
+}
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when closing new tabs.
+ */
+add_task(async function() {
+  // If we've got a preloaded browser, get rid of it so that it
+  // doesn't interfere with the test if it's loading. We have to
+  // do this before we disable preloading or changing the new tab
+  // URL, otherwise _getPreloadedBrowser will return null, despite
+  // the preloaded browser existing.
+  let preloaded = gBrowser._getPreloadedBrowser();
+  if (preloaded) {
+    preloaded.remove();
+  }
+
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.newtab.preload", false]],
+  });
+
+  let aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
+                             .getService(Ci.nsIAboutNewTabService);
+  aboutNewTabService.newTabURL = "about:blank";
+
+  registerCleanupFunction(() => {
+    aboutNewTabService.resetNewTabURL();
+  });
+
+  // Because the tab strip is a scrollable frame, we can't use the
+  // default dirtying function from withReflowObserver and reliably
+  // get reflows for the strip. Instead, we provide a node that's
+  // already in the scrollable frame to dirty - in this case, the
+  // original tab.
+  let origTab = gBrowser.selectedTab;
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  await BrowserTestUtils.waitForCondition(() => tab._fullyOpen);
+
+  // Add a reflow observer and open a new tab.
+  await withReflowObserver(async function() {
+    let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
+    gBrowser.removeTab(tab, { animate: true });
+    await BrowserTestUtils.waitForEvent(tab, "transitionend",
+        false, e => e.propertyName === "max-width");
+    await switchDone;
+  }, EXPECTED_REFLOWS, window, origTab);
+});
rename from browser/base/content/test/general/browser_tabopen_reflows.js
rename to browser/base/content/test/performance/browser_tabopen_reflows.js
--- a/browser/base/content/test/general/browser_tabopen_reflows.js
+++ b/browser/base/content/test/performance/browser_tabopen_reflows.js
@@ -1,145 +1,108 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-XPCOMUtils.defineLazyGetter(this, "docShell", () => {
-  return window.QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIWebNavigation)
-               .QueryInterface(Ci.nsIDocShell);
-});
+"use strict";
 
+/**
+ * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
+ * is a whitelist that should slowly go away as we improve the performance of
+ * the front-end. Instead of adding more reflows to the whitelist, you should
+ * be modifying your code to avoid the reflow.
+ *
+ * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
+ * for tips on how to do that.
+ */
 const EXPECTED_REFLOWS = [
-  // tabbrowser.adjustTabstrip() call after tabopen animation has finished
-  "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" +
-    "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
-    "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
-
-  // switching focus in updateCurrentBrowser() causes reflows
-  "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml|" +
-    "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
-    "onselect@chrome://browser/content/browser.xul|",
-
-  // switching focus in openLinkIn() causes reflows
-  "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
-    "BrowserOpenTab@chrome://browser/content/browser.js|",
+  // selection change notification may cause querying the focused editor content
+  // by IME and that will cause reflow.
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "openLinkIn@chrome://browser/content/utilityOverlay.js",
+    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
+    "BrowserOpenTab@chrome://browser/content/browser.js",
+  ],
 
   // selection change notification may cause querying the focused editor content
   // by IME and that will cause reflow.
-  "select@chrome://global/content/bindings/textbox.xml|" +
-    "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
-    "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
-    "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
-    "BrowserOpenTab@chrome://browser/content/browser.js|",
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "openLinkIn@chrome://browser/content/utilityOverlay.js",
+    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
+    "BrowserOpenTab@chrome://browser/content/browser.js",
+  ],
 
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "openLinkIn@chrome://browser/content/utilityOverlay.js",
+    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
+    "BrowserOpenTab@chrome://browser/content/browser.js",
+  ],
+
+  [
+    "openLinkIn@chrome://browser/content/utilityOverlay.js",
+    "openUILinkIn@chrome://browser/content/utilityOverlay.js",
+    "BrowserOpenTab@chrome://browser/content/browser.js",
+  ],
 ];
 
-const PREF_PRELOAD = "browser.newtab.preload";
-const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
+if (!gMultiProcessBrowser) {
+  EXPECTED_REFLOWS.push(
+    [
+      "_adjustFocusAfterTabSwitch@chrome://browser/content/tabbrowser.xml",
+      "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml",
+      "onselect@chrome://browser/content/browser.xul",
+    ],
+  );
+}
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new tabs.
  */
 add_task(async function() {
-  let DirectoryLinksProvider = Cu.import("resource:///modules/DirectoryLinksProvider.jsm", {}).DirectoryLinksProvider;
-  let NewTabUtils = Cu.import("resource://gre/modules/NewTabUtils.jsm", {}).NewTabUtils;
-  let Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
-
-  // resolves promise when directory links are downloaded and written to disk
-  function watchLinksChangeOnce() {
-    return new Promise(resolve => {
-      let observer = {
-        onManyLinksChanged: () => {
-          DirectoryLinksProvider.removeObserver(observer);
-          NewTabUtils.links.populateCache(() => {
-            NewTabUtils.allPages.update();
-            resolve();
-          }, true);
-        }
-      };
-      observer.onDownloadFail = observer.onManyLinksChanged;
-      DirectoryLinksProvider.addObserver(observer);
-    });
+  // If we've got a preloaded browser, get rid of it so that it
+  // doesn't interfere with the test if it's loading. We have to
+  // do this before we disable preloading or changing the new tab
+  // URL, otherwise _getPreloadedBrowser will return null, despite
+  // the preloaded browser existing.
+  let preloaded = gBrowser._getPreloadedBrowser();
+  if (preloaded) {
+    preloaded.remove();
   }
 
-  let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref(PREF_PRELOAD);
-    Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
-    return watchLinksChangeOnce();
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.newtab.preload", false]],
   });
 
-  Services.prefs.setBoolPref(PREF_PRELOAD, false);
-  // set directory source to dummy/empty links
-  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, 'data:application/json,{"test":1}');
-
-  // run tests when directory source change completes
-  await watchLinksChangeOnce();
+  let aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"]
+                             .getService(Ci.nsIAboutNewTabService);
+  aboutNewTabService.newTabURL = "about:blank";
 
-  // Perform a click in the top left of content to ensure the mouse isn't
-  // hovering over any of the tiles
-  let target = gBrowser.selectedBrowser;
-  let rect = target.getBoundingClientRect();
-  let left = rect.left + 1;
-  let top = rect.top + 1;
+  registerCleanupFunction(() => {
+    aboutNewTabService.resetNewTabURL();
+  });
 
-  let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIDOMWindowUtils);
-  utils.sendMouseEvent("mousedown", left, top, 0, 1, 0, false, 0, 0);
-  utils.sendMouseEvent("mouseup", left, top, 0, 1, 0, false, 0, 0);
+  // Because the tab strip is a scrollable frame, we can't use the
+  // default dirtying function from withReflowObserver and reliably
+  // get reflows for the strip. Instead, we provide a node that's
+  // already in the scrollable frame to dirty - in this case, the
+  // original tab.
+  let origTab = gBrowser.selectedTab;
 
   // Add a reflow observer and open a new tab.
-  docShell.addWeakReflowObserver(observer);
-  BrowserOpenTab();
+  await withReflowObserver(async function() {
+    let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
+    BrowserOpenTab();
+    await BrowserTestUtils.waitForEvent(gBrowser.selectedTab, "transitionend",
+        false, e => e.propertyName === "max-width");
+    await switchDone;
+  }, EXPECTED_REFLOWS, window, origTab);
 
-  // Wait until the tabopen animation has finished.
-  await waitForTransitionEnd();
-
-  // Remove reflow observer and clean up.
-  docShell.removeWeakReflowObserver(observer);
-  gBrowser.removeCurrentTab();
+  let switchDone = BrowserTestUtils.waitForEvent(window, "TabSwitchDone");
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+  await switchDone;
 });
 
-var observer = {
-  reflow(start, end) {
-    // Gather information about the current code path.
-    let path = (new Error().stack).split("\n").slice(1).map(line => {
-      return line.replace(/:\d+:\d+$/, "");
-    }).join("|");
-    let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
-
-    // Stack trace is empty. Reflow was triggered by native code.
-    if (path === "") {
-      return;
-    }
-
-    // Check if this is an expected reflow.
-    for (let stack of EXPECTED_REFLOWS) {
-      if (path.startsWith(stack)) {
-        ok(true, "expected uninterruptible reflow '" + stack + "'");
-        return;
-      }
-    }
-
-    ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
-  },
-
-  reflowInterruptible(start, end) {
-    // We're not interested in interruptible reflows.
-  },
-
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
-                                         Ci.nsISupportsWeakReference])
-};
-
-function waitForTransitionEnd() {
-  return new Promise(resolve => {
-    let tab = gBrowser.selectedTab;
-    tab.addEventListener("transitionend", function onEnd(event) {
-      if (event.propertyName === "max-width") {
-        tab.removeEventListener("transitionend", onEnd);
-        resolve();
-      }
-    });
-  });
-}
rename from browser/base/content/test/windows/browser_toolbariconcolor_restyles.js
rename to browser/base/content/test/performance/browser_toolbariconcolor_restyles.js
rename from browser/base/content/test/general/browser_windowopen_reflows.js
rename to browser/base/content/test/performance/browser_windowopen_reflows.js
--- a/browser/base/content/test/general/browser_windowopen_reflows.js
+++ b/browser/base/content/test/performance/browser_windowopen_reflows.js
@@ -1,117 +1,73 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+/**
+ * WHOA THERE: We should never be adding new things to EXPECTED_REFLOWS. This
+ * is a whitelist that should slowly go away as we improve the performance of
+ * the front-end. Instead of adding more reflows to the whitelist, you should
+ * be modifying your code to avoid the reflow.
+ *
+ * See https://developer.mozilla.org/en-US/Firefox/Performance_best_practices_for_Firefox_fe_engineers
+ * for tips on how to do that.
+ */
 const EXPECTED_REFLOWS = [
-  // handleEvent flushes layout to get the tabstrip width after a resize.
-  "handleEvent@chrome://browser/content/tabbrowser.xml|",
-
-  // Loading a tab causes a reflow.
-  "loadTabs@chrome://browser/content/tabbrowser.xml|" +
-    "loadOneOrMoreURIs@chrome://browser/content/browser.js|" +
-    "_delayedStartup@chrome://browser/content/browser.js|",
+  // Selecting the address bar causes two reflows, unfortunately.
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "_delayedStartup@chrome://browser/content/browser.js",
+  ],
 
-  // Selecting the address bar causes a reflow.
-  "select@chrome://global/content/bindings/textbox.xml|" +
-    "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
-    "_delayedStartup@chrome://browser/content/browser.js|",
+  // Selecting the address bar causes two reflows, unfortunately.
+  [
+    "select@chrome://global/content/bindings/textbox.xml",
+    "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+    "_delayedStartup@chrome://browser/content/browser.js",
+  ],
+];
 
-  // Focusing the content area causes a reflow.
-  "_delayedStartup@chrome://browser/content/browser.js|",
-];
+if (Services.appinfo.OS == "Darwin") {
+  // TabsInTitlebar._update causes a reflow on OS X trying to do calculations
+  // since layout info is already dirty. This doesn't seem to happen before
+  // MozAfterPaint on Linux.
+  EXPECTED_REFLOWS.push(
+    [
+      "rect@chrome://browser/content/browser-tabsintitlebar.js",
+      "_update@chrome://browser/content/browser-tabsintitlebar.js",
+      "updateAppearance@chrome://browser/content/browser-tabsintitlebar.js",
+      "handleEvent@chrome://browser/content/tabbrowser.xml",
+    ],
+  );
+}
 
 if (Services.appinfo.OS == "WINNT" || Services.appinfo.OS == "Darwin") {
-  // TabsInTitlebar._update causes a reflow on OS X and Windows trying to do calculations
-  // since layout info is already dirty. This doesn't seem to happen before
-  // MozAfterPaint on Linux.
-  EXPECTED_REFLOWS.push("rect@chrome://browser/content/browser-tabsintitlebar.js|" +
-                          "_update@chrome://browser/content/browser-tabsintitlebar.js|" +
-                          "updateAppearance@chrome://browser/content/browser-tabsintitlebar.js|" +
-                          "handleEvent@chrome://browser/content/tabbrowser.xml|");
-}
-
-if (Services.appinfo.OS == "Darwin") {
-  // _onOverflow causes a reflow getting widths.
-  EXPECTED_REFLOWS.push("_onOverflow@resource:///modules/CustomizableUI.jsm|" +
-                        "init@resource:///modules/CustomizableUI.jsm|" +
-                        "observe@resource:///modules/CustomizableUI.jsm|" +
-                        "_delayedStartup@chrome://browser/content/browser.js|");
-  // Same as above since in packaged builds there are no function names and the resource URI includes "app"
-  EXPECTED_REFLOWS.push("@resource://app/modules/CustomizableUI.jsm|" +
-                          "@resource://app/modules/CustomizableUI.jsm|" +
-                          "@resource://app/modules/CustomizableUI.jsm|" +
-                          "_delayedStartup@chrome://browser/content/browser.js|");
+  EXPECTED_REFLOWS.push(
+    [
+      "handleEvent@chrome://browser/content/tabbrowser.xml",
+      "inferFromText@chrome://browser/content/browser.js",
+      "handleEvent@chrome://browser/content/browser.js",
+    ],
+  );
 }
 
 /*
  * This test ensures that there are no unexpected
  * uninterruptible reflows when opening new windows.
  */
-function test() {
-  waitForExplicitFinish();
-
-  // Add a reflow observer and open a new window
+add_task(async function() {
   let win = OpenBrowserWindow();
-  let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIWebNavigation)
-                    .QueryInterface(Ci.nsIDocShell);
-  docShell.addWeakReflowObserver(observer);
-
-  // Wait until the mozafterpaint event occurs.
-  waitForMozAfterPaint(win, function paintListener() {
-    // Remove reflow observer and clean up.
-    docShell.removeWeakReflowObserver(observer);
-    win.close();
-
-    finish();
-  });
-}
-
-var observer = {
-  reflow(start, end) {
-    // Gather information about the current code path.
-    let stack = new Error().stack;
-    let path = stack.split("\n").slice(1).map(line => {
-      return line.replace(/:\d+:\d+$/, "");
-    }).join("|");
-    let pathWithLineNumbers = (new Error().stack).split("\n").slice(1).join("|");
-
-    // Stack trace is empty. Reflow was triggered by native code.
-    if (path === "") {
-      return;
-    }
 
-    // Check if this is an expected reflow.
-    for (let expectedStack of EXPECTED_REFLOWS) {
-      if (path.startsWith(expectedStack) ||
-          // Accept an empty function name for gBrowserInit._delayedStartup or TabsInTitlebar._update to workaround bug 906578.
-          path.startsWith(expectedStack.replace(/(^|\|)(gBrowserInit\._delayedStartup|TabsInTitlebar\._update)@/, "$1@"))) {
-        ok(true, "expected uninterruptible reflow '" + expectedStack + "'");
-        return;
-      }
-    }
-
-    ok(false, "unexpected uninterruptible reflow '" + pathWithLineNumbers + "'");
-  },
-
-  reflowInterruptible(start, end) {
-    // We're not interested in interruptible reflows.
-  },
+  await withReflowObserver(async function() {
+    let resizeEvent = BrowserTestUtils.waitForEvent(win, "resize");
+    let delayedStartup =
+      TestUtils.topicObserved("browser-delayed-startup-finished",
+                              subject => subject == win);
+    await resizeEvent;
+    await delayedStartup;
+  }, EXPECTED_REFLOWS, win);
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
-                                         Ci.nsISupportsWeakReference])
-};
+  await BrowserTestUtils.closeWindow(win);
+});
 
-function waitForMozAfterPaint(win, callback) {
-  let dwu = win.QueryInterface(Ci.nsIInterfaceRequestor)
-               .getInterface(Ci.nsIDOMWindowUtils);
-  let lastTransactionId = dwu.lastTransactionId;
-
-  win.addEventListener("MozAfterPaint", function onEnd(event) {
-    if (event.target != win || event.transactionId <= lastTransactionId)
-      return;
-    win.removeEventListener("MozAfterPaint", onEnd);
-    executeSoon(callback);
-  });
-}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/performance/head.js
@@ -0,0 +1,155 @@
+/**
+ * Async utility function for ensuring that no unexpected uninterruptible
+ * reflows occur during some period of time in a window.
+ *
+ * The helper works by running a JS function before each event is
+ * dispatched that attempts to dirty the layout tree - the idea being
+ * that this puts us in the "worst case scenario" so that any JS
+ * that attempts to query for layout or style information will cause
+ * a reflow to fire. We also dirty the layout tree after each reflow
+ * occurs, for good measure.
+ *
+ * This sounds good in theory, but it's trickier in practice due to
+ * various optimizations in our Layout engine. The default function
+ * for dirtying the layout tree adds a margin to the first element
+ * child it finds in the window to a maximum of 3px, and then goes
+ * back to 0px again and loops.
+ *
+ * This is not sufficient for reflows that we expect to happen within
+ * scrollable frames, as Gecko is able to side-step reflowing the
+ * contents of a scrollable frame if outer frames are dirtied. Because
+ * of this, it's currently possible to override the default node to
+ * dirty with one more appropriate for the test.
+ *
+ * It is also theoretically possible for enough events to fire between
+ * reflows such that the before and after state of the layout tree is
+ * exactly the same, meaning that no reflow is required, which opens
+ * us up to missing expected reflows. This seems to be possible in
+ * theory, but hasn't yet shown up in practice - it's just something
+ * to be aware of.
+ *
+ * Bug 1363361 has been filed for a more reliable way of dirtying layout.
+ *
+ * @param testFn (async function)
+ *        The async function that will exercise the browser activity that is
+ *        being tested for reflows.
+ * @param expectedStacks (Array, optional)
+ *        An Array of Arrays representing stacks.
+ *
+ *        Example:
+ *
+ *        [
+ *          // This reflow is caused by lorem ipsum
+ *          [
+ *            "select@chrome://global/content/bindings/textbox.xml",
+ *            "focusAndSelectUrlBar@chrome://browser/content/browser.js",
+ *            "openLinkIn@chrome://browser/content/utilityOverlay.js",
+ *            "openUILinkIn@chrome://browser/content/utilityOverlay.js",
+ *            "BrowserOpenTab@chrome://browser/content/browser.js",
+ *          ],
+ *
+ *          // This reflow is caused by lorem ipsum
+ *          [
+ *            "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml",
+ *            "_fillTrailingGap@chrome://browser/content/tabbrowser.xml",
+ *            "_handleNewTab@chrome://browser/content/tabbrowser.xml",
+ *            "onxbltransitionend@chrome://browser/content/tabbrowser.xml",
+ *          ],
+ *
+ *        ]
+ *
+ *        Note that line numbers are not included in the stacks.
+ *
+ *        Order of the reflows doesn't matter. Expected reflows that aren't seen
+ *        will cause an assertion failure. When this argument is not passed,
+ *        it defaults to the empty Array, meaning no reflows are expected.
+ * @param window (browser window, optional)
+ *        The browser window to monitor. Defaults to the current window.
+ * @param elemToDirty (DOM node, optional)
+ *        Callers can provide a custom DOM node to change some layout style
+ *        on in the event that the action being tested is occurring within
+ *        a scrollable frame. Otherwise, withReflowObserver defaults to dirtying
+ *        the first element child of the window.
+ */
+async function withReflowObserver(testFn, expectedStacks = [], win = window, elemToDirty) {
+  if (!elemToDirty) {
+    elemToDirty = win.document.firstElementChild;
+  }
+
+  let i = 0;
+  let dirtyFrameFn = (e) => {
+    elemToDirty.style.margin = (++i % 4) + "px";
+  };
+
+  let els = Cc["@mozilla.org/eventlistenerservice;1"]
+              .getService(Ci.nsIEventListenerService);
+
+  // We're going to remove the stacks one by one as we see them so that
+  // we can check for expected, unseen reflows, so let's clone the array.
+  expectedStacks = expectedStacks.slice(0);
+
+  let observer = {
+    reflow(start, end) {
+      // Gather information about the current code path, slicing out the current
+      // frame.
+      let path = (new Error().stack).split("\n").slice(1).map(line => {
+        return line.replace(/:\d+:\d+$/, "");
+      }).join("|");
+
+      let pathWithLineNumbers = (new Error().stack).split("\n").slice(1);
+
+      // Just in case, dirty the frame now that we've reflowed.
+      dirtyFrameFn();
+
+      // Stack trace is empty. Reflow was triggered by native code, which
+      // we ignore.
+      if (path === "") {
+        return;
+      }
+
+      let index = expectedStacks.findIndex(stack => path.startsWith(stack.join("|")));
+
+      if (index != -1) {
+        Assert.ok(true, "expected uninterruptible reflow: '" +
+                  JSON.stringify(pathWithLineNumbers, null, "\t") + "'");
+        expectedStacks.splice(index, 1);
+      } else {
+        Assert.ok(false, "unexpected uninterruptible reflow \n" +
+                         JSON.stringify(pathWithLineNumbers, null, "\t") + "\n");
+      }
+    },
+
+    reflowInterruptible(start, end) {
+      // We're not interested in interruptible reflows.
+    },
+
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+                                           Ci.nsISupportsWeakReference])
+  };
+
+  let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .QueryInterface(Ci.nsIDocShell);
+  docShell.addWeakReflowObserver(observer);
+
+  els.addListenerForAllEvents(win, dirtyFrameFn, true);
+
+  try {
+    dirtyFrameFn();
+    await testFn();
+  } finally {
+    for (let remainder of expectedStacks) {
+      Assert.ok(false,
+                `Unused expected reflow: ${JSON.stringify(remainder, null, "\t")}.\n` +
+                "This is probably a good thing - just remove it from the " +
+                "expected list.");
+    }
+
+
+    els.removeListenerForAllEvents(win, dirtyFrameFn, true);
+    docShell.removeWeakReflowObserver(observer);
+
+    elemToDirty.style.margin = "";
+  }
+}
+
deleted file mode 100644
--- a/browser/base/content/test/windows/browser.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[DEFAULT]
-[browser_toolbariconcolor_restyles.js]
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -124,20 +124,16 @@ browser.jar:
         content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
         content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
         content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
         content/browser/robot.ico                     (content/robot.ico)
-        content/browser/sync/aboutSyncTabs.xul        (content/sync/aboutSyncTabs.xul)
-        content/browser/sync/aboutSyncTabs.js         (content/sync/aboutSyncTabs.js)
-        content/browser/sync/aboutSyncTabs.css        (content/sync/aboutSyncTabs.css)
-        content/browser/sync/aboutSyncTabs-bindings.xml  (content/sync/aboutSyncTabs-bindings.xml)
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
         content/browser/safeMode.xul                  (content/safeMode.xul)
         content/browser/sanitize.js                   (content/sanitize.js)
         content/browser/sanitize.xul                  (content/sanitize.xul)
         content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
         content/browser/contentSearchUI.js            (content/contentSearchUI.js)
--- a/browser/base/moz.build
+++ b/browser/base/moz.build
@@ -17,33 +17,33 @@ MOCHITEST_CHROME_MANIFESTS += [
 BROWSER_CHROME_MANIFESTS += [
     'content/test/alerts/browser.ini',
     'content/test/captivePortal/browser.ini',
     'content/test/contextMenu/browser.ini',
     'content/test/forms/browser.ini',
     'content/test/general/browser.ini',
     'content/test/newtab/browser.ini',
     'content/test/pageinfo/browser.ini',
+    'content/test/performance/browser.ini',
     'content/test/permissions/browser.ini',
     'content/test/plugins/browser.ini',
     'content/test/popupNotifications/browser.ini',
     'content/test/popups/browser.ini',
     'content/test/referrer/browser.ini',
     'content/test/sidebar/browser.ini',
     'content/test/siteIdentity/browser.ini',
     'content/test/social/browser.ini',
     'content/test/static/browser.ini',
     'content/test/sync/browser.ini',
     'content/test/tabcrashed/browser.ini',
     'content/test/tabPrompts/browser.ini',
     'content/test/tabs/browser.ini',
     'content/test/urlbar/browser.ini',
     'content/test/webextensions/browser.ini',
     'content/test/webrtc/browser.ini',
-    'content/test/windows/browser.ini',
 ]
 
 if CONFIG['MOZ_UPDATER']:
     BROWSER_CHROME_MANIFESTS += ['content/test/appUpdate/browser.ini']
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_VERSION_DISPLAY'] = CONFIG['MOZ_APP_VERSION_DISPLAY']
 
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -75,18 +75,16 @@ static const RedirEntry kRedirMap[] = {
     nsIAboutModule::ALLOW_SCRIPT },
   { "searchreset", "chrome://browser/content/search/searchReset.xhtml",
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "sessionrestore", "chrome://browser/content/aboutSessionRestore.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
   { "welcomeback", "chrome://browser/content/aboutWelcomeBack.xhtml",
     nsIAboutModule::ALLOW_SCRIPT },
-  { "sync-tabs", "chrome://browser/content/sync/aboutSyncTabs.xul",
-    nsIAboutModule::ALLOW_SCRIPT },
   // Linkable because of indexeddb use (bug 1228118)
   { "home", "chrome://browser/content/abouthome/aboutHome.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::MAKE_LINKABLE |
     nsIAboutModule::ENABLE_INDEXED_DB },
   // the newtab's actual URL will be determined when the channel is created
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -92,17 +92,16 @@ static const mozilla::Module::ContractID
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "tabcrashed", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "feeds", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "privatebrowsing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "rights", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "robots", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "searchreset", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sessionrestore", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "welcomeback", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
-    { NS_ABOUT_MODULE_CONTRACTID_PREFIX "sync-tabs", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #ifdef MOZ_SERVICES_HEALTHREPORT
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #endif
--- a/browser/components/contextualidentity/test/browser/browser_eme.js
+++ b/browser/components/contextualidentity/test/browser/browser_eme.js
@@ -98,17 +98,17 @@ add_task(async function test() {
   // Generate the key info for the default container.
   let keyInfo = generateKeyInfo(TESTKEY);
 
   // Update the media key for the default container.
   let result = await ContentTask.spawn(defaultContainer.browser, keyInfo, async function(aKeyInfo) {
     let access = await content.navigator.requestMediaKeySystemAccess("org.w3.clearkey",
                                                                      [{
                                                                        initDataTypes: [aKeyInfo.initDataType],
-                                                                       videoCapabilities: [{contentType: "video/webm"}],
+                                                                       videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}],
                                                                        sessionTypes: ["persistent-license"],
                                                                        persistentState: "required",
                                                                      }]);
     let mediaKeys = await access.createMediaKeys();
     let session = mediaKeys.createSession(aKeyInfo.sessionType);
     let res = {};
 
     // Insert the media key.
@@ -151,17 +151,17 @@ add_task(async function test() {
 
   // Open a tab with personal container.
   let personalContainer = await openTabInUserContext(TEST_URL + "empty_file.html", USER_ID_PERSONAL);
 
   await ContentTask.spawn(personalContainer.browser, keyInfo, async function(aKeyInfo) {
     let access = await content.navigator.requestMediaKeySystemAccess("org.w3.clearkey",
                                                                      [{
                                                                        initDataTypes: [aKeyInfo.initDataType],
-                                                                       videoCapabilities: [{contentType: "video/webm"}],
+                                                                       videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}],
                                                                        sessionTypes: ["persistent-license"],
                                                                        persistentState: "required",
                                                                      }]);
     let mediaKeys = await access.createMediaKeys();
     let session = mediaKeys.createSession(aKeyInfo.sessionType);
 
     // First, load the session to check that mediakeys do not share with
     // default container.
--- a/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js
+++ b/browser/components/contextualidentity/test/browser/browser_forgetAPI_EME_forgetThisSite.js
@@ -93,17 +93,17 @@ async function setupEMEKey(browser) {
   // Generate the key info.
   let keyInfo = generateKeyInfo(TEST_EME_KEY);
 
   // Setup the EME key.
   let result = await ContentTask.spawn(browser, keyInfo, async function(aKeyInfo) {
     let access = await content.navigator.requestMediaKeySystemAccess("org.w3.clearkey",
                                                                      [{
                                                                        initDataTypes: [aKeyInfo.initDataType],
-                                                                       videoCapabilities: [{contentType: "video/webm"}],
+                                                                       videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}],
                                                                        sessionTypes: ["persistent-license"],
                                                                        persistentState: "required",
                                                                      }]);
     let mediaKeys = await access.createMediaKeys();
     let session = mediaKeys.createSession(aKeyInfo.sessionType);
     let res = {};
 
     // Insert the EME key.
@@ -148,17 +148,17 @@ async function checkEMEKey(browser, emeS
   // Generate the key info.
   let keyInfo = generateKeyInfo(TEST_EME_KEY);
   keyInfo.sessionId = emeSessionId;
 
   await ContentTask.spawn(browser, keyInfo, async function(aKeyInfo) {
     let access = await content.navigator.requestMediaKeySystemAccess("org.w3.clearkey",
                                                                      [{
                                                                        initDataTypes: [aKeyInfo.initDataType],
-                                                                       videoCapabilities: [{contentType: "video/webm"}],
+                                                                       videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}],
                                                                        sessionTypes: ["persistent-license"],
                                                                        persistentState: "required",
                                                                      }]);
     let mediaKeys = await access.createMediaKeys();
     let session = mediaKeys.createSession(aKeyInfo.sessionType);
 
     // First, load the session with the sessionId.
     await session.load(aKeyInfo.sessionId);
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -142,16 +142,23 @@ var gMenuBuilder = {
   },
 
   removeTopLevelMenuIfNeeded(element) {
     // If there is only one visible top level element we don't need the
     // root menu element for the extension.
     let menuPopup = element.firstChild;
     if (menuPopup && menuPopup.childNodes.length == 1) {
       let onlyChild = menuPopup.firstChild;
+
+      // Keep single checkbox items in the submenu on Linux since
+      // the extension icon overlaps the checkbox otherwise.
+      if (AppConstants.platform === "linux" && onlyChild.getAttribute("type") === "checkbox") {
+        return element;
+      }
+
       onlyChild.remove();
       return onlyChild;
     }
 
     return element;
   },
 
   buildSingleElement(item, contextData) {
--- a/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js
+++ b/browser/components/extensions/test/browser/browser_ext_contextMenus_checkboxes.js
@@ -19,36 +19,64 @@ add_task(async function() {
         browser.test.sendMessage("contextmenus-click", info);
       });
 
       browser.contextMenus.create({
         title: "Checkbox",
         type: "checkbox",
       });
 
-      browser.contextMenus.create({
-        type: "separator",
-      });
+      browser.test.sendMessage("single-contextmenu-item-added");
+
+      browser.test.onMessage.addListener(msg => {
+        if (msg !== "add-additional-menu-items") {
+          return;
+        }
+
+        browser.contextMenus.create({
+          type: "separator",
+        });
 
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
-        checked: true,
-      });
+        browser.contextMenus.create({
+          title: "Checkbox",
+          type: "checkbox",
+          checked: true,
+        });
 
-      browser.contextMenus.create({
-        title: "Checkbox",
-        type: "checkbox",
+        browser.contextMenus.create({
+          title: "Checkbox",
+          type: "checkbox",
+        });
+
+        browser.test.notifyPass("contextmenus-checkboxes");
       });
-
-      browser.test.notifyPass("contextmenus-checkboxes");
     },
   });
 
   await extension.startup();
+
+  await extension.awaitMessage("single-contextmenu-item-added");
+
+  async function testSingleCheckboxItem() {
+    let extensionMenuRoot = await openExtensionContextMenu();
+
+    // On Linux, the single menu item should be contained in a submenu.
+    if (AppConstants.platform === "linux") {
+      let items = extensionMenuRoot.getElementsByAttribute("type", "checkbox");
+      is(items.length, 1, "single checkbox should be in the submenu on Linux");
+      await closeContextMenu();
+    } else {
+      is(extensionMenuRoot, null, "there should be no submenu for a single checkbox item");
+      await closeContextMenu();
+    }
+  }
+
+  await testSingleCheckboxItem();
+
+  extension.sendMessage("add-additional-menu-items");
   await extension.awaitFinish("contextmenus-checkboxes");
 
   function confirmCheckboxStates(extensionMenuRoot, expectedStates) {
     let checkboxItems = extensionMenuRoot.getElementsByAttribute("type", "checkbox");
 
     is(checkboxItems.length, 3, "there should be 3 checkbox items in the context menu");
 
     is(checkboxItems[0].hasAttribute("checked"), expectedStates[0], `checkbox item 1 has state (checked=${expectedStates[0]})`);
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -9,27 +9,22 @@ function genericChecker() {
     kind = "popup";
   } else if (path.indexOf("tab") != -1) {
     kind = "tab";
   }
   window.kind = kind;
 
   browser.test.onMessage.addListener((msg, ...args) => {
     if (msg == kind + "-check-views") {
-      let windowId = args[0];
       let counts = {
-        "background": 0,
-        "tab": 0,
-        "popup": 0,
-        "kind": 0,
-        "window": 0,
+        background: 0,
+        tab: 0,
+        popup: 0,
+        kind: 0,
       };
-      if (Number.isInteger(windowId)) {
-        counts.window = browser.extension.getViews({windowId: windowId}).length;
-      }
       if (kind !== "background") {
         counts.kind = browser.extension.getViews({type: kind}).length;
       }
       let views = browser.extension.getViews();
       let background;
       for (let i = 0; i < views.length; i++) {
         let view = views[i];
         browser.test.assertTrue(view.kind in counts, "view type is valid");
@@ -43,18 +38,23 @@ function genericChecker() {
       if (background) {
         browser.runtime.getBackgroundPage().then(view => {
           browser.test.assertEq(background, view, "runtime.getBackgroundPage() is correct");
           browser.test.sendMessage("counts", counts);
         });
       } else {
         browser.test.sendMessage("counts", counts);
       }
+    } else if (msg == kind + "-getViews-with-filter") {
+      let filter = args[0];
+      let count = browser.extension.getViews(filter).length;
+      browser.test.sendMessage("getViews-count", count);
     } else if (msg == kind + "-open-tab") {
-      browser.tabs.create({windowId: args[0], url: browser.runtime.getURL("tab.html")});
+      browser.tabs.create({windowId: args[0], url: browser.runtime.getURL("tab.html")})
+        .then((tab) => browser.test.sendMessage("opened-tab", tab.id));
     } else if (msg == kind + "-close-tab") {
       browser.tabs.query({
         windowId: args[0],
       }, tabs => {
         let tab = tabs.find(tab => tab.url.indexOf("tab.html") != -1);
         browser.tabs.remove(tab.id, () => {
           browser.test.sendMessage("closed");
         });
@@ -107,38 +107,50 @@ add_task(async function() {
   let {Management: {global: {windowTracker}}} = Cu.import("resource://gre/modules/Extension.jsm", {});
 
   let winId1 = windowTracker.getId(win1);
   let winId2 = windowTracker.getId(win2);
 
   async function openTab(winId) {
     extension.sendMessage("background-open-tab", winId);
     await extension.awaitMessage("tab-ready");
+    return extension.awaitMessage("opened-tab");
   }
 
-  async function checkViews(kind, tabCount, popupCount, kindCount, windowId = undefined, windowCount = 0) {
-    extension.sendMessage(kind + "-check-views", windowId);
+  async function checkViews(kind, tabCount, popupCount, kindCount) {
+    extension.sendMessage(kind + "-check-views");
     let counts = await extension.awaitMessage("counts");
     is(counts.background, 1, "background count correct");
     is(counts.tab, tabCount, "tab count correct");
     is(counts.popup, popupCount, "popup count correct");
     is(counts.kind, kindCount, "count for type correct");
-    is(counts.window, windowCount, "count for window correct");
+  }
+
+  async function checkViewsWithFilter(filter, expectedCount) {
+    extension.sendMessage("background-getViews-with-filter", filter);
+    let count = await extension.awaitMessage("getViews-count");
+    is(count, expectedCount, `count for ${JSON.stringify(filter)} correct`);
   }
 
   await checkViews("background", 0, 0, 0);
+  await checkViewsWithFilter({windowId: -1}, 1);
+  await checkViewsWithFilter({tabId: -1}, 1);
 
-  await openTab(winId1);
+  let tabId1 = await openTab(winId1);
 
-  await checkViews("background", 1, 0, 0, winId1, 1);
+  await checkViews("background", 1, 0, 0);
   await checkViews("tab", 1, 0, 1);
+  await checkViewsWithFilter({windowId: winId1}, 1);
+  await checkViewsWithFilter({tabId: tabId1}, 1);
 
-  await openTab(winId2);
+  let tabId2 = await openTab(winId2);
 
-  await checkViews("background", 2, 0, 0, winId2, 1);
+  await checkViews("background", 2, 0, 0);
+  await checkViewsWithFilter({windowId: winId2}, 1);
+  await checkViewsWithFilter({tabId: tabId2}, 1);
 
   async function triggerPopup(win, callback) {
     await clickBrowserAction(extension, win);
     await awaitExtensionPanel(extension, win);
 
     await extension.awaitMessage("popup-ready");
 
     await callback();
@@ -148,28 +160,34 @@ add_task(async function() {
 
   // The popup occasionally closes prematurely if we open it immediately here.
   // I'm not sure what causes it to close (it's something internal, and seems to
   // be focus-related, but it's not caused by JS calling hidePopup), but even a
   // short timeout seems to consistently fix it.
   await new Promise(resolve => win1.setTimeout(resolve, 10));
 
   await triggerPopup(win1, async function() {
-    await checkViews("background", 2, 1, 0, winId1, 2);
+    await checkViews("background", 2, 1, 0);
     await checkViews("popup", 2, 1, 1);
+    await checkViewsWithFilter({windowId: winId1}, 2);
+    await checkViewsWithFilter({type: "popup", tabId: -1}, 1);
   });
 
   await triggerPopup(win2, async function() {
-    await checkViews("background", 2, 1, 0, winId2, 2);
+    await checkViews("background", 2, 1, 0);
     await checkViews("popup", 2, 1, 1);
+    await checkViewsWithFilter({windowId: winId2}, 2);
+    await checkViewsWithFilter({type: "popup", tabId: -1}, 1);
   });
 
   info("checking counts after popups");
 
-  await checkViews("background", 2, 0, 0, winId1, 1);
+  await checkViews("background", 2, 0, 0);
+  await checkViewsWithFilter({windowId: winId1}, 1);
+  await checkViewsWithFilter({tabId: -1}, 1);
 
   info("closing one tab");
 
   extension.sendMessage("background-close-tab", winId1);
   await extension.awaitMessage("closed");
 
   info("one tab closed, one remains");
 
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -165,45 +165,32 @@ class TestFirefoxRefresh(MarionetteTestC
         titleInBookmarks = self.marionette.execute_script("""
           let url = arguments[0];
           let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(makeURI(url), {}, {});
           return bookmarkIds.length == 1 ? PlacesUtils.bookmarks.getItemTitle(bookmarkIds[0]) : "";
         """, script_args=[self._bookmarkURL])
         self.assertEqual(titleInBookmarks, self._bookmarkText)
 
     def checkHistory(self):
-        historyResults = self.runAsyncCode("""
-          let placeInfos = [];
-          PlacesUtils.asyncHistory.getPlacesInfo(makeURI(arguments[0]), {
-            handleError(resultCode, place) {
-              placeInfos = null;
-              marionetteScriptFinished("Unexpected error in fetching visit: " + resultCode);
-            },
-            handleResult(placeInfo) {
-              placeInfos.push(placeInfo);
-            },
-            handleCompletion() {
-              if (placeInfos) {
-                if (!placeInfos.length) {
-                  marionetteScriptFinished("No visits found");
-                } else {
-                  marionetteScriptFinished(placeInfos);
-                }
-              }
-            },
+        historyResult = self.runAsyncCode("""
+          PlacesUtils.history.fetch(arguments[0]).then(pageInfo => {
+            if (!pageInfo) {
+              marionetteScriptFinished("No visits found");
+            } else {
+              marionetteScriptFinished(pageInfo);
+            }
+          }).catch(e => {
+            marionetteScriptFinished("Unexpected error in fetching page: " + e);
           });
         """, script_args=[self._historyURL])
-        if type(historyResults) == str:
-            self.fail(historyResults)
+        if type(historyResult) == str:
+            self.fail(historyResult)
             return
 
-        historyCount = len(historyResults)
-        self.assertEqual(historyCount, 1, "Should have exactly 1 entry for URI, got %d" % historyCount)
-        if historyCount == 1:
-            self.assertEqual(historyResults[0]['title'], self._historyTitle)
+        self.assertEqual(historyResult['title'], self._historyTitle)
 
     def checkFormHistory(self):
         formFieldResults = self.runAsyncCode("""
           let results = [];
           global.FormHistory.search(["value"], {fieldname: arguments[0]}, {
             handleError(error) {
               results = error;
             },
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -655,17 +655,17 @@ var BookmarkPropertiesPanel = {
       info.feedUrl = this._feedURI;
       if (this._siteURI)
         info.siteUrl = this._siteURI;
 
       itemGuid = await PlacesTransactions.NewLivemark(info).transact();
     } else if (this._itemType == BOOKMARK_FOLDER) {
       itemGuid = await PlacesTransactions.NewFolder(info).transact();
       for (let uri of this._URIs) {
-        let placeInfo = await PlacesUtils.promisePlaceInfo(uri);
+        let placeInfo = await PlacesUtils.history.fetch(uri);
         let title = placeInfo ? placeInfo.title : "";
         await PlacesTransactions.transact({ parentGuid: itemGuid, uri, title });
       }
     } else {
       throw new Error(`unexpected value for _itemType:  ${this._itemType}`);
     }
 
     this._itemGuid = itemGuid;
deleted file mode 100644
--- a/browser/locales/en-US/chrome/browser/aboutSyncTabs.dtd
+++ /dev/null
@@ -1,20 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!ENTITY tabs.otherDevices.label               "Tabs From Other Devices">
-
-<!ENTITY tabs.searchText.label                   "Type here to find tabs…">
-
-<!-- LOCALIZATION NOTE (tabs.context.openTab.accesskey, tabs.context.openMultipleTabs.accesskey):
-     Only one of these will show at a time (based on selection), so reusing accesskey is ok. -->
-<!ENTITY tabs.context.openTab.label                   "Open This Tab">
-<!ENTITY tabs.context.openTab.accesskey               "O">
-<!ENTITY tabs.context.openMultipleTabs.label          "Open Selected Tabs">
-<!ENTITY tabs.context.openMultipleTabs.accesskey      "O">
-<!ENTITY tabs.context.bookmarkSingleTab.label         "Bookmark This Tab…">
-<!ENTITY tabs.context.bookmarkSingleTab.accesskey     "B">
-<!ENTITY tabs.context.bookmarkMultipleTabs.label      "Bookmark Selected Tabs…">
-<!ENTITY tabs.context.bookmarkMultipleTabs.accesskey  "B">
-<!ENTITY tabs.context.refreshList.label               "Refresh List">
-<!ENTITY tabs.context.refreshList.accesskey           "R">
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -15,17 +15,16 @@
     locale/browser/aboutHome.dtd                   (%chrome/browser/aboutHome.dtd)
     locale/browser/accounts.properties             (%chrome/browser/accounts.properties)
 #ifdef MOZ_SERVICES_HEALTHREPORT
     locale/browser/aboutHealthReport.dtd           (%chrome/browser/aboutHealthReport.dtd)
 #endif
     locale/browser/aboutSearchReset.dtd            (%chrome/browser/aboutSearchReset.dtd)
     locale/browser/aboutSessionRestore.dtd         (%chrome/browser/aboutSessionRestore.dtd)
     locale/browser/aboutTabCrashed.dtd             (%chrome/browser/aboutTabCrashed.dtd)
-    locale/browser/aboutSyncTabs.dtd               (%chrome/browser/aboutSyncTabs.dtd)
     locale/browser/browser.dtd                     (%chrome/browser/browser.dtd)
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
     locale/browser/lightweightThemes.properties    (%chrome/browser/lightweightThemes.properties)
     locale/browser/newTab.dtd                      (%chrome/browser/newTab.dtd)
     locale/browser/newTab.properties               (%chrome/browser/newTab.properties)
     locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
deleted file mode 100644
--- a/browser/themes/linux/aboutSyncTabs.css
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#tabs-display,
-#tabsList {
-  background-color: transparent;
-  -moz-appearance: none;
-  margin: 0;
-}
-
-#tabsList {
-  width: 100%;
-}
-
-#tabs-display {
-  background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
-}
-
-#headers {
-  background: url(chrome://browser/skin/sync-32.png) no-repeat;
-  margin-top: 4px;
-  width: 45em;
-  height: 32px;
-  margin-inline-start: 2em;
-  margin-inline-end: 2em;
-}
-
-#headers:-moz-locale-dir(rtl) {
-  background-position-x: 100%;
-}
-
-#tabsListHeading {
-  font-size: 140%;
-  font-weight: bold;
-  margin-inline-start: 40px;
-}
-
-richlistitem {
-  margin-inline-end: 2em;
-}
-
-richlistitem[selected="true"],
-richlistitem:focus {
-  outline-style: none;
-}
-
-richlistitem[type="tab"] {
-  min-height: 3em;
-  border: #999999 1px solid !important;
-  padding: 2px 5px;
-  margin-bottom: 4px;
-  margin-inline-start: 4em;
-  border-radius: 6px;
-  background-color: menu;
-  width: 44em;
-  opacity: 0.9;
-  box-shadow:
-    inset rgba(255, 255, 255, 0.5) 0 1px 0px,
-    inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
-    rgba(0, 0, 0, 0.1) 0px 1px 0px;
-}
-
-richlistitem[type="tab"][selected="true"] {
-  background-color: -moz-MenuHover;
-}
-
-richlistitem[type="client"] {
-  min-height: 2em;
-  color: #000000;
-  margin-inline-start: 2em;
-  margin-top: 2px;
-  margin-bottom: 3px;
-  width: 42em;
-  border-radius: 6px;
-  background-color: transparent;
-  -moz-user-focus: ignore !important;
-}
-richlistitem.mobile[type="client"] {
-  list-style-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon");
-}
-richlistitem.desktop[type="client"] {
-  list-style-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
-}
-
-.title,
-.clientName {
-  color: #000000;
-  font-size: 1.1em;
-}
-
-.title[selected="true"],
-.url[selected="true"] {
-  color: inherit;
-}
-
-.url {
-  color: -moz-nativehyperlinktext;
-  font-size: 0.95em;
-}
-
-.tabIcon {
-  padding-inline-start: 2px;
-  padding-top: 2px;
-}
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 % override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
-  skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css     (syncedtabs/sidebar.css)
 * skin/classic/browser/browser.css
 * skin/classic/browser/compacttheme.css
   skin/classic/browser/Info.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-customize@2x.png
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-exit@2x.png
@@ -94,19 +93,16 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-selected-start.svg    (tab-selected-start.svg)
 
   skin/classic/browser/tabbrowser/tab-stroke-end.png        (tabbrowser/tab-stroke-end.png)
   skin/classic/browser/tabbrowser/tab-stroke-end@2x.png     (tabbrowser/tab-stroke-end@2x.png)
   skin/classic/browser/tabbrowser/tab-stroke-start.png      (tabbrowser/tab-stroke-start.png)
   skin/classic/browser/tabbrowser/tab-stroke-start@2x.png   (tabbrowser/tab-stroke-start@2x.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png      (tabbrowser/tabDragIndicator.png)
 
-  skin/classic/browser/sync-16.png
-  skin/classic/browser/sync-32.png
-  skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-horizontalbar.png
   skin/classic/browser/sync-horizontalbar@2x.png
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/syncProgress-horizontalbar.png
   skin/classic/browser/syncProgress-horizontalbar@2x.png
 #ifdef E10S_TESTING_ONLY
   skin/classic/browser/e10s-64@2x.png (../shared/e10s-64@2x.png)
deleted file mode 100644
--- a/browser/themes/osx/aboutSyncTabs.css
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#tabs-display,
-#tabsList {
-  background-color: transparent;
-  -moz-appearance: none;
-  margin: 0;
-}
-
-#tabsList {
-  width: 100%;
-}
-
-#tabs-display {
-  background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
-}
-
-#headers {
-  background: url(chrome://browser/skin/sync-32.png) no-repeat;
-  margin-top: 4px;
-  width: 45em;
-  height: 32px;
-  margin-inline-start: 2em;
-  margin-inline-end: 2em;
-}
-
-#headers:-moz-locale-dir(rtl) {
-  background-position-x: 100%;
-}
-
-#tabsListHeading {
-  font-size: 140%;
-  font-weight: bold;
-  margin-inline-start: 40px;
-}
-
-richlistitem {
-  margin-inline-end: 2em;
-}
-
-richlistitem[selected="true"],
-richlistitem:focus {
-  outline-style: none;
-}
-
-richlistitem[type="tab"] {
-  min-height: 3em;
-  border: #999999 1px solid !important;
-  padding: 2px 5px;
-  margin-bottom: 4px;
-  margin-inline-start: 4em;
-  border-radius: 6px;
-  background-color: menu;
-  width: 44em;
-  opacity: 0.9;
-  box-shadow:
-    inset rgba(255, 255, 255, 0.5) 0 1px 0px,
-    inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
-    rgba(0, 0, 0, 0.1) 0px 1px 0px;
-}
-
-richlistitem[type="tab"][selected="true"] {
-  background-color: -moz-MenuHover;
-}
-
-richlistitem[type="client"] {
-  min-height: 2em;
-  color: #000000;
-  margin-inline-start: 2em;
-  margin-top: 2px;
-  margin-bottom: 3px;
-  width: 42em;
-  border-radius: 6px;
-  background-color: transparent;
-  -moz-user-focus: ignore !important;
-}
-richlistitem.mobile[type="client"] {
-  list-style-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon");
-}
-richlistitem.desktop[type="client"] {
-  list-style-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
-}
-
-.title,
-.clientName {
-  color: #000000;
-  font-size: 1.1em;
-}
-
-.title[selected="true"],
-.url[selected="true"] {
-  color: inherit;
-}
-
-.url {
-  color: -moz-nativehyperlinktext;
-  font-size: 0.95em;
-}
-
-.tabIcon {
-  padding-inline-start: 2px;
-  padding-top: 2px;
-}
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -2,17 +2,16 @@
 # 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/.
 
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
-  skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css          (syncedtabs/sidebar.css)
 * skin/classic/browser/browser.css
 * skin/classic/browser/compacttheme.css
   skin/classic/browser/Info.png
   skin/classic/browser/subtle-pattern.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-forward.png
   skin/classic/browser/menuPanel-customize.png
@@ -141,19 +140,16 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-selected-start.svg                 (tab-selected-start.svg)
 
   skin/classic/browser/tabbrowser/tab-stroke-end.png                     (tabbrowser/tab-stroke-end.png)
   skin/classic/browser/tabbrowser/tab-stroke-end@2x.png                  (tabbrowser/tab-stroke-end@2x.png)
   skin/classic/browser/tabbrowser/tab-stroke-start.png                   (tabbrowser/tab-stroke-start.png)
   skin/classic/browser/tabbrowser/tab-stroke-start@2x.png                (tabbrowser/tab-stroke-start@2x.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png                   (tabbrowser/tabDragIndicator.png)
   skin/classic/browser/tabbrowser/tabDragIndicator@2x.png                (tabbrowser/tabDragIndicator@2x.png)
-  skin/classic/browser/sync-16.png
-  skin/classic/browser/sync-32.png
-  skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-horizontalbar.png
   skin/classic/browser/sync-horizontalbar@2x.png
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/syncProgress-horizontalbar.png
   skin/classic/browser/syncProgress-horizontalbar@2x.png
   skin/classic/browser/Toolbar-background-noise.png         (Toolbar-background-noise.png)
   skin/classic/browser/yosemite/menuPanel-customize.png                (menuPanel-customize-yosemite.png)
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -165,16 +165,17 @@
 #tracking-protection-icon:not([state]) {
   margin-inline-end: -18px;
   pointer-events: none;
   opacity: 0;
   /* Only animate the shield in, when it disappears hide it immediately. */
   transition: none;
 }
 
+#urlbar[pageproxystate="invalid"] > #identity-box > #extension-icon,
 #urlbar[pageproxystate="invalid"] > #identity-box > #tracking-protection-icon {
   visibility: collapse;
 }
 
 /* CONNECTION ICON, EXTENSION ICON */
 
 #connection-icon,
 #extension-icon {
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -8,33 +8,35 @@
   -moz-context-properties: fill;
   fill: var(--toolbarbutton-icon-fill);
 }
 
 toolbar[brighttext] :-moz-any(@primaryToolbarButtons@) {
   fill: var(--toolbarbutton-icon-fill-inverted);
 }
 
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+%ifdef MOZ_PHOTON_THEME
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+%endif
+#nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+  transform: scaleX(-1);
+}
+
 #back-button {
   list-style-image: url("chrome://browser/skin/back-large.svg");
 }
 
-#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
-  transform: scaleX(-1);
-}
-
 #forward-button {
   list-style-image: url("chrome://browser/skin/forward.svg");
 }
 
 %ifdef MOZ_PHOTON_THEME
-#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
-  transform: scaleX(-1);
-}
-
 #reload-button {
   list-style-image: url("chrome://browser/skin/reload.svg");
 }
 
 #stop-button {
   list-style-image: url("chrome://browser/skin/stop.svg");
 }
 %endif
@@ -166,35 +168,27 @@ toolbar:not([brighttext]) #bookmarks-men
 #zoom-controls:not(@inAnyPanel@) > #zoom-in-button {
   list-style-image: url("chrome://browser/skin/zoom-in.svg");
 }
 
 #nav-bar-overflow-button {
   list-style-image: url("chrome://browser/skin/chevron.svg");
 }
 
-#nav-bar-overflow-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
-  transform: scaleX(-1);
-}
-
 #email-link-button[cui-areatype="toolbar"] {
   list-style-image: url("chrome://browser/skin/mail.svg");
 }
 
 #sidebar-button[cui-areatype="toolbar"] {
   list-style-image: url("chrome://browser/skin/sidebars.svg");
 }
 
 #panic-button[cui-areatype="toolbar"] {
   list-style-image: url("chrome://browser/skin/forget.svg");
 }
 
 #panic-button[cui-areatype="toolbar"][open] {
   fill: rgb(213, 32, 20);
 }
 
-#panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
-  transform: scaleX(-1);
-}
-
 #webide-button[cui-areatype="toolbar"] {
   list-style-image: url("chrome://browser/skin/webIDE.svg");
 }
deleted file mode 100644
--- a/browser/themes/windows/aboutSyncTabs.css
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#tabs-display,
-#tabsList {
-  background-color: transparent;
-  -moz-appearance: none;
-  margin: 0;
-}
-
-#tabsList {
-  width: 100%;
-}
-
-#tabs-display {
-  background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
-}
-
-#headers {
-  background: url(chrome://browser/skin/sync-32.png) no-repeat;
-  margin-top: 4px;
-  width: 45em;
-  height: 32px;
-  margin-inline-start: 2em;
-  margin-inline-end: 2em;
-}
-
-#headers:-moz-locale-dir(rtl) {
-  background-position-x: 100%;
-}
-
-#tabsListHeading {
-  font-size: 140%;
-  font-weight: bold;
-  margin-inline-start: 40px;
-}
-
-richlistitem {
-  margin-inline-end: 2em;
-}
-
-richlistitem[selected="true"],
-richlistitem:focus {
-  outline-style: none;
-}
-
-richlistitem[type="tab"] {
-  min-height: 3em;
-  border: #999999 1px solid !important;
-  padding: 2px 5px;
-  margin-bottom: 4px;
-  margin-inline-start: 4em;
-  border-radius: 6px;
-  background-color: menu;
-  width: 44em;
-  opacity: 0.9;
-  box-shadow:
-    inset rgba(255, 255, 255, 0.5) 0 1px 0px,
-    inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
-    rgba(0, 0, 0, 0.1) 0px 1px 0px;
-}
-
-richlistitem[type="tab"][selected="true"] {
-  background-color: -moz-MenuHover;
-}
-
-richlistitem[type="client"] {
-  min-height: 2em;
-  color: #000000;
-  margin-inline-start: 2em;
-  margin-top: 2px;
-  margin-bottom: 3px;
-  width: 42em;
-  border-radius: 6px;
-  background-color: transparent;
-  -moz-user-focus: ignore !important;
-}
-richlistitem.mobile[type="client"] {
-  list-style-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon");
-}
-richlistitem.desktop[type="client"] {
-  list-style-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
-}
-
-.title,
-.clientName {
-  color: #000000;
-  font-size: 1.1em;
-}
-
-.title[selected="true"],
-.url[selected="true"] {
-  color: inherit;
-}
-
-.url {
-  color: -moz-nativehyperlinktext;
-  font-size: 0.95em;
-}
-
-.tabIcon {
-  padding-inline-start: 2px;
-  padding-top: 2px;
-}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -2,17 +2,16 @@
 # 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/.
 
 browser.jar:
 % skin browser classic/1.0 %skin/classic/browser/
 #include ../shared/jar.inc.mn
   skin/classic/browser/sanitizeDialog.css
   skin/classic/browser/aboutSessionRestore-window-icon.png
-  skin/classic/browser/aboutSyncTabs.css
 * skin/classic/browser/syncedtabs/sidebar.css     (syncedtabs/sidebar.css)
 * skin/classic/browser/browser.css
 * skin/classic/browser/compacttheme.css
   skin/classic/browser/caption-buttons.svg
   skin/classic/browser/Info.png
   skin/classic/browser/livemark-folder.png
   skin/classic/browser/menu-back.png
   skin/classic/browser/menu-forward.png
@@ -120,19 +119,16 @@ browser.jar:
   skin/classic/browser/tabbrowser/tab-selected-end.svg         (tab-selected-end.svg)
   skin/classic/browser/tabbrowser/tab-selected-start.svg       (tab-selected-start.svg)
 
   skin/classic/browser/tabbrowser/tab-stroke-end.png           (tabbrowser/tab-stroke-end.png)
   skin/classic/browser/tabbrowser/tab-stroke-end@2x.png        (tabbrowser/tab-stroke-end@2x.png)
   skin/classic/browser/tabbrowser/tab-stroke-start.png         (tabbrowser/tab-stroke-start.png)
   skin/classic/browser/tabbrowser/tab-stroke-start@2x.png      (tabbrowser/tab-stroke-start@2x.png)
   skin/classic/browser/tabbrowser/tabDragIndicator.png         (tabbrowser/tabDragIndicator.png)
-  skin/classic/browser/sync-16.png
-  skin/classic/browser/sync-32.png
-  skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.svg  (../shared/sync-desktopIcon.svg)
   skin/classic/browser/sync-horizontalbar.png
   skin/classic/browser/sync-horizontalbar@2x.png
   skin/classic/browser/sync-horizontalbar-win7.png
   skin/classic/browser/sync-horizontalbar-win7@2x.png
   skin/classic/browser/sync-mobileIcon.svg  (../shared/sync-mobileIcon.svg)
   skin/classic/browser/syncProgress-horizontalbar.png
   skin/classic/browser/syncProgress-horizontalbar@2x.png
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -60,30 +60,29 @@ def check_build_environment(help, dist):
             '  *     1. cd %s\n'
             '  *     2. gmake distclean\n'
             '  ***'
             % ('\n  '.join(conflict_files), topsrcdir)
         )
 
     return result
 
-set_config('TOPSRCDIR', delayed_getattr(check_build_environment, 'topsrcdir'))
-set_config('TOPOBJDIR', delayed_getattr(check_build_environment, 'topobjdir'))
-set_config('MOZ_BUILD_ROOT', delayed_getattr(check_build_environment,
-                                             'topobjdir'))
-set_config('DIST', delayed_getattr(check_build_environment, 'dist'))
+set_config('TOPSRCDIR', check_build_environment.topsrcdir)
+set_config('TOPOBJDIR', check_build_environment.topobjdir)
+set_config('MOZ_BUILD_ROOT', check_build_environment.topobjdir)
+set_config('DIST', check_build_environment.dist)
 
 add_old_configure_assignment(
-    '_topsrcdir', delayed_getattr(check_build_environment, 'topsrcdir'))
+    '_topsrcdir', check_build_environment.topsrcdir)
 add_old_configure_assignment(
-    '_objdir', delayed_getattr(check_build_environment, 'topobjdir'))
+    '_objdir', check_build_environment.topobjdir)
 add_old_configure_assignment(
-    'MOZ_BUILD_ROOT', delayed_getattr(check_build_environment, 'topobjdir'))
+    'MOZ_BUILD_ROOT', check_build_environment.topobjdir)
 add_old_configure_assignment(
-    'DIST', delayed_getattr(check_build_environment, 'dist'))
+    'DIST', check_build_environment.dist)
 
 option(env='MOZ_AUTOMATION', help='Enable options for automated builds')
 set_config('MOZ_AUTOMATION', depends_if('MOZ_AUTOMATION')(lambda x: True))
 
 
 option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script')
 
 option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project')
@@ -553,47 +552,46 @@ def target_variables(target):
 
     return namespace(
         OS_TARGET=os_target,
         OS_ARCH=os_arch,
         OS_TEST=os_test,
         INTEL_ARCHITECTURE=target.cpu in ('x86', 'x86_64') or None,
     )
 
-set_config('OS_TARGET', delayed_getattr(target_variables, 'OS_TARGET'))
+set_config('OS_TARGET', target_variables.OS_TARGET)
 add_old_configure_assignment('OS_TARGET',
-                             delayed_getattr(target_variables, 'OS_TARGET'))
-set_config('OS_ARCH', delayed_getattr(target_variables, 'OS_ARCH'))
+                             target_variables.OS_TARGET)
+set_config('OS_ARCH', target_variables.OS_ARCH)
 add_old_configure_assignment('OS_ARCH',
-                             delayed_getattr(target_variables, 'OS_ARCH'))
-set_config('OS_TEST', delayed_getattr(target_variables, 'OS_TEST'))
+                             target_variables.OS_ARCH)
+set_config('OS_TEST', target_variables.OS_TEST)
 add_old_configure_assignment('OS_TEST',
-                             delayed_getattr(target_variables, 'OS_TEST'))
-set_config('CPU_ARCH', delayed_getattr(target, 'cpu'))
-add_old_configure_assignment('CPU_ARCH', delayed_getattr(target, 'cpu'))
-set_config('INTEL_ARCHITECTURE', delayed_getattr(target_variables,
-                                                 'INTEL_ARCHITECTURE'))
-set_config('TARGET_CPU', delayed_getattr(target, 'raw_cpu'))
-set_config('TARGET_OS', delayed_getattr(target, 'raw_os'))
+                             target_variables.OS_TEST)
+set_config('CPU_ARCH', target.cpu)
+add_old_configure_assignment('CPU_ARCH', target.cpu)
+set_config('INTEL_ARCHITECTURE', target_variables.INTEL_ARCHITECTURE)
+set_config('TARGET_CPU', target.raw_cpu)
+set_config('TARGET_OS', target.raw_os)
 
 
 @depends(host)
 def host_variables(host):
     if host.kernel == 'kFreeBSD':
         os_arch = 'GNU_kFreeBSD'
     else:
         os_arch = host.kernel
     return namespace(
         HOST_OS_ARCH=os_arch,
     )
 
-set_config('HOST_CPU_ARCH', delayed_getattr(host, 'cpu'))
-set_config('HOST_OS_ARCH', delayed_getattr(host_variables, 'HOST_OS_ARCH'))
+set_config('HOST_CPU_ARCH', host.cpu)
+set_config('HOST_OS_ARCH', host_variables.HOST_OS_ARCH)
 add_old_configure_assignment('HOST_OS_ARCH',
-                             delayed_getattr(host_variables, 'HOST_OS_ARCH'))
+                             host_variables.HOST_OS_ARCH)
 
 @depends(target)
 def target_is_windows(target):
     if target.kernel == 'WINNT':
         return True
 
 set_define('_WINDOWS', target_is_windows)
 set_define('WIN32', target_is_windows)
@@ -712,29 +710,24 @@ def milestone(build_env, _):
         is_nightly = True
     elif 'a' not in milestone:
         is_release_or_beta = True
 
     return namespace(version=milestone,
                      is_nightly=is_nightly,
                      is_release_or_beta=is_release_or_beta)
 
-@depends(milestone)
-def is_nightly(milestone):
-    return milestone.is_nightly
-
-set_config('GRE_MILESTONE', delayed_getattr(milestone, 'version'))
-set_config('NIGHTLY_BUILD', is_nightly)
-set_define('NIGHTLY_BUILD', is_nightly)
-add_old_configure_assignment('NIGHTLY_BUILD',
-                             is_nightly)
-set_config('RELEASE_OR_BETA', delayed_getattr(milestone, 'is_release_or_beta'))
-set_define('RELEASE_OR_BETA', delayed_getattr(milestone, 'is_release_or_beta'))
+set_config('GRE_MILESTONE', milestone.version)
+set_config('NIGHTLY_BUILD', milestone.is_nightly)
+set_define('NIGHTLY_BUILD', milestone.is_nightly)
+add_old_configure_assignment('NIGHTLY_BUILD', milestone.is_nightly)
+set_config('RELEASE_OR_BETA', milestone.is_release_or_beta)
+set_define('RELEASE_OR_BETA', milestone.is_release_or_beta)
 add_old_configure_assignment('RELEASE_OR_BETA',
-                             delayed_getattr(milestone, 'is_release_or_beta'))
+                             milestone.is_release_or_beta)
 
 # The app update channel is 'default' when not supplied. The value is used in
 # the application's confvars.sh (and is made available to a project specific
 # moz.configure).
 option('--enable-update-channel',
        nargs=1,
        help='Select application update channel',
        default='default')
--- a/build/moz.configure/keyfiles.configure
+++ b/build/moz.configure/keyfiles.configure
@@ -55,10 +55,10 @@ def id_and_secret_keyfile(desc):
         )
 
     content = keyfile(desc, help='Use the client id and secret key contained '
                                  'in the given keyfile for %s requests' % desc,
                       callback=id_and_secret)
 
 
     name = desc.upper().replace(' ', '_')
-    set_config('MOZ_%s_CLIENTID' % name, delayed_getattr(content, 'id'))
-    set_config('MOZ_%s_KEY' % name, delayed_getattr(content, 'secret'))
+    set_config('MOZ_%s_CLIENTID' % name, content.id)
+    set_config('MOZ_%s_KEY' % name, content.secret)
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -22,19 +22,19 @@ def yasm_version(yasm):
         yasm, '--version',
         onerror=lambda: die('Failed to get yasm version.')
     ).splitlines()[0].split()[1]
     return Version(version)
 
 # Until we move all the yasm consumers out of old-configure.
 # bug 1257904
 add_old_configure_assignment('_YASM_MAJOR_VERSION',
-                             delayed_getattr(yasm_version, 'major'))
+                             yasm_version.major)
 add_old_configure_assignment('_YASM_MINOR_VERSION',
-                             delayed_getattr(yasm_version, 'minor'))
+                             yasm_version.minor)
 
 @depends(yasm, target)
 def yasm_asflags(yasm, target):
     if yasm:
         asflags = {
             ('OSX', 'x86'): '-f macho32',
             ('OSX', 'x86_64'): '-f macho64',
             ('WINNT', 'x86'): '-f win32',
@@ -640,17 +640,17 @@ def compiler(language, host_or_target, c
                     for k, v in other_compiler.__dict__.iteritems()
                 })
 
     # Normally, we'd use `var` instead of `_var`, but the interaction with
     # old-configure complicates things, and for now, we a) can't take the plain
     # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
     # old-configure AC_SUBST it (because it's autoconf doing it, not us)
     compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
-                          input=delayed_getattr(provided_compiler, 'compiler'),
+                          input=provided_compiler.compiler,
                           paths=toolchain_search_path)
 
     @depends(compiler, provided_compiler, compiler_wrapper, host_or_target)
     @checking('whether %s can be used' % what, lambda x: bool(x))
     @imports(_from='mozbuild.shellutil', _import='quote')
     def valid_compiler(compiler, provided_compiler, compiler_wrapper,
                        host_or_target):
         wrapper = list(compiler_wrapper or ())
@@ -786,21 +786,21 @@ def compiler(language, host_or_target, c
     # be provided.
     add_old_configure_assignment(var, depends_if(valid_compiler)(
         lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)))
 
     # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
     # old-configure to do some of its still existing checks.
     if language == 'C':
         set_config(
-            '%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
+            '%s_TYPE' % var, valid_compiler.type)
         add_old_configure_assignment(
-            '%s_TYPE' % var, delayed_getattr(valid_compiler, 'type'))
+            '%s_TYPE' % var, valid_compiler.type)
         add_old_configure_assignment(
-            '%s_VERSION' % var, delayed_getattr(valid_compiler, 'version'))
+            '%s_VERSION' % var, valid_compiler.version)
 
     valid_compiler = compiler_class(valid_compiler)
 
     def compiler_error():
         raise FatalCheckError('Failed compiling a simple %s source with %s'
                               % (language, what))
 
     valid_compiler.try_compile(check_msg='%s works' % what,
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -361,37 +361,16 @@ def dependable(obj):
         return depends(when=True)(obj)
     return depends(when=True)(lambda: obj)
 
 
 always = dependable(True)
 never = dependable(False)
 
 
-# Some @depends function return namespaces, and one could want to use one
-# specific attribute from such a namespace as a "value" given to functions
-# such as `set_config`. But those functions do not take immediate values.
-# The `delayed_getattr` function allows access to attributes from the result
-# of a @depends function in a non-immediate manner.
-#   @depends('--option')
-#   def option(value)
-#       return namespace(foo=value)
-#   set_config('FOO', delayed_getattr(option, 'foo')
-@template
-def delayed_getattr(func, key):
-    @depends(func)
-    def result(value):
-        # The @depends function we're being passed may have returned
-        # None, or an object that simply doesn't have the wanted key.
-        # In that case, just return None.
-        return getattr(value, key, None)
-
-    return result
-
-
 # Like @depends, but the decorated function is only called if one of the
 # arguments it would be called with has a positive value (bool(value) is True)
 @template
 def depends_if(*args, **kwargs):
     if kwargs:
         assert len(kwargs) == 1
         when = kwargs['when']
     else:
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -148,17 +148,17 @@ def valid_windows_sdk_dir(compiler, wind
         include=sdk.include,
         lib=sdk.lib,
         version=biggest_version,
     )
 
 
 add_old_configure_assignment(
     'WINDOWSSDKDIR',
-    delayed_getattr(valid_windows_sdk_dir, 'path'))
+    valid_windows_sdk_dir.path)
 add_old_configure_assignment(
     'MOZ_WINSDK_MAXVER',
     depends(valid_windows_sdk_dir)(
         lambda x: '0x%04X0000' % x.version if x else None))
 
 
 @imports(_from='mozbuild.shellutil', _import='quote')
 def valid_ucrt_sdk_dir_result(value):
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -968,16 +968,17 @@ endif
 #
 # but, given the idiosyncracies of make, can also be called without arguments:
 #
 #   $(call CARGO_BUILD)
 define CARGO_BUILD
 env $(environment_cleaner) $(rustflags_override) \
 	CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
 	RUSTC=$(RUSTC) \
+	MOZ_SRC=$(topsrcdir) \
 	MOZ_DIST=$(ABS_DIST) \
 	LIBCLANG_PATH=$(MOZ_LIBCLANG_PATH) \
 	CLANG_PATH=$(MOZ_CLANG_PATH) \
 	PKG_CONFIG_ALLOW_CROSS=1 \
 	RUST_BACKTRACE=1 \
 	MOZ_TOPOBJDIR=$(topobjdir) \
 	$(1) \
 	$(CARGO) build $(cargo_build_flags)
--- a/devtools/.eslintrc.js
+++ b/devtools/.eslintrc.js
@@ -23,16 +23,18 @@ module.exports = {
     "loader": true,
     "module": true,
     "Node": true,
     "reportError": true,
     "require": true,
     "setInterval": true,
     "setTimeout": true,
     "uneval": true,
+    "TextDecoder": true,
+    "TextEncoder": true,
     "URL": true,
     "WebSocket": true,
     "XMLHttpRequest": true
   },
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
--- a/devtools/client/framework/source-map-url-service.js
+++ b/devtools/client/framework/source-map-url-service.js
@@ -78,12 +78,18 @@ SourceMapURLService.prototype.originalPo
   if (!urlInfo) {
     return null;
   }
   // Call getOriginalURLs to make sure the source map has been
   // fetched.  We don't actually need the result of this though.
   await this._sourceMapService.getOriginalURLs(urlInfo);
   const location = { sourceId: urlInfo.id, line, column, sourceUrl: url };
   let resolvedLocation = await this._sourceMapService.getOriginalLocation(location);
-  return resolvedLocation === location ? null : resolvedLocation;
+  if (!resolvedLocation ||
+      (resolvedLocation.line === location.line &&
+       resolvedLocation.column === location.column &&
+       resolvedLocation.sourceUrl === location.sourceUrl)) {
+    return null;
+  }
+  return resolvedLocation;
 };
 
 exports.SourceMapURLService = SourceMapURLService;
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -63,16 +63,17 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_destroy-after-navigation.js]
 [browser_inspector_destroy-before-ready.js]
 [browser_inspector_expand-collapse.js]
 [browser_inspector_gcli-inspect-command.js]
 [browser_inspector_highlighter-01.js]
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-04.js]
+[browser_inspector_highlighter-05.js]
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-cancel.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-cssgrid_01.js]
 [browser_inspector_highlighter-cssgrid_02.js]
 [browser_inspector_highlighter-csstransform_01.js]
 [browser_inspector_highlighter-csstransform_02.js]
 [browser_inspector_highlighter-embed.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-05.js
@@ -0,0 +1,69 @@
+/* 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 is testing that the Anonymous Content is properly inserted into the document.
+// Usually that is happening during the "interactive" state of the document, to have them
+// ready as soon as possible.
+// However, in some conditions, that's not possible since we don't have access yet to
+// the `CustomContentContainer`, that is used to add the Anonymous Content.
+// That can happen if the page has some external resource, as <link>, that takes time
+// to load and / or returns the wrong content. This is not happening, for instance, with
+// images.
+//
+// In those case, we want to be sure that if we're not able to insert the Anonymous
+// Content at the "interactive" state, we're doing so when the document is loaded.
+//
+// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1365075
+
+const server = createTestHTTPServer();
+const filepath = "/slow.css";
+const cssuri = `http://localhost:${server.identity.primaryPort}${filepath}`;
+
+// Register a slow css file handler so we can simulate a long loading time.
+server.registerContentType("css", "text/css");
+server.registerPathHandler(filepath, (metadata, response) => {
+  info("CSS has been requested");
+  response.processAsync();
+  setTimeout(() => {
+    info("CSS is responding");
+    response.finish();
+  }, 2000);
+});
+
+const TEST_URL = "data:text/html," + encodeURIComponent(`
+  <!DOCTYPE html>
+  <html>
+    <head>
+      <link href="${cssuri}" rel="stylesheet" />
+    </head>
+    <body>
+      <p>Slow page</p>
+     </body>
+  </html>
+`);
+
+add_task(function* () {
+  info("Open the inspector to a blank page.");
+  let { inspector, tab, testActor } = yield openInspectorForURL("about:blank");
+
+  let pageLoaded = waitForPageLoad(tab);
+
+  info("Navigate to the test url and waiting for the page to be loaded.");
+  yield navigateTo(inspector, TEST_URL);
+  yield pageLoaded;
+
+  info("Shows the box model highligher for the <p> node.");
+  let divFront = yield getNodeFront("p", inspector);
+  yield inspector.highlighter.showBoxModel(divFront);
+
+  info("Check the node is highlighted.");
+  is(yield testActor.isHighlighting(), true,
+    "Box Model highlighter is working as expected.");
+});
+
+const waitForPageLoad = (tab) => new Promise(resolve => {
+  tab.linkedBrowser.addEventListener("load", resolve, {capture: true, once: true});
+});
--- a/devtools/client/shared/components/stack-trace.js
+++ b/devtools/client/shared/components/stack-trace.js
@@ -30,23 +30,26 @@ const AsyncFrame = createFactory(createC
 
 const StackTrace = createClass({
   displayName: "StackTrace",
 
   propTypes: {
     stacktrace: PropTypes.array.isRequired,
     onViewSourceInDebugger: PropTypes.func.isRequired,
     onViewSourceInScratchpad: PropTypes.func,
+    // Service to enable the source map feature.
+    sourceMapService: PropTypes.object,
   },
 
   render() {
     let {
       stacktrace,
       onViewSourceInDebugger,
-      onViewSourceInScratchpad
+      onViewSourceInScratchpad,
+      sourceMapService,
     } = this.props;
 
     let frames = [];
     stacktrace.forEach((s, i) => {
       if (s.asyncCause) {
         frames.push("\t", AsyncFrame({
           key: `${i}-asyncframe`,
           asyncCause: s.asyncCause
@@ -62,17 +65,18 @@ const StackTrace = createClass({
           line: s.lineNumber,
           column: s.columnNumber,
         },
         showFunctionName: true,
         showAnonymousFunctionName: true,
         showFullSourceUrl: true,
         onClick: (/^Scratchpad\/\d+$/.test(source))
           ? onViewSourceInScratchpad
-          : onViewSourceInDebugger
+          : onViewSourceInDebugger,
+        sourceMapService,
       }), "\n");
     });
 
     return dom.div({ className: "stack-trace" }, frames);
   }
 });
 
 module.exports = StackTrace;
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -6,16 +6,17 @@ support-files =
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
 [test_searchbox.html]
 [test_searchbox-with-autocomplete.html]
 [test_sidebar_toggle.html]
 [test_stack-trace.html]
+[test_stack-trace-source-maps.html]
 [test_tabs_accessibility.html]
 [test_tabs_menu.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
 [test_tree_05.html]
 [test_tree_06.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_stack-trace-source-maps.html
@@ -0,0 +1,105 @@
+<!-- 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>
+<!--
+Test the rendering of a stack trace with source maps
+-->
+<head>
+  <meta charset="utf-8">
+  <title>StackTrace component test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script src="head.js"></script>
+<script>
+/* import-globals-from head.js */
+"use strict";
+
+window.onload = function () {
+  let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
+  let React = browserRequire("devtools/client/shared/vendor/react");
+  let StackTrace = React.createFactory(
+    browserRequire("devtools/client/shared/components/stack-trace")
+  );
+  ok(StackTrace, "Got the StackTrace factory");
+
+  add_task(function* () {
+    let stacktrace = [
+      {
+        filename: "https://bugzilla.mozilla.org/bundle.js",
+        lineNumber: 99,
+        columnNumber: 10
+      },
+      {
+        functionName: "loadFunc",
+        filename: "https://bugzilla.mozilla.org/bundle.js",
+        lineNumber: 108,
+      }
+    ];
+
+    let props = {
+      stacktrace,
+      onViewSourceInDebugger: () => {},
+      onViewSourceInScratchpad: () => {},
+      // A mock source map service.
+      sourceMapService: {
+	originalPositionFor: function (url, line, column) {
+	  let newLine = line === 99 ? 1 : 7;
+	  // Return a phony promise-like thing that resolves
+	  // immediately.
+	  return {
+	    then: function (consequence) {
+	      consequence({
+		sourceId: "whatever",
+		sourceUrl: "https://bugzilla.mozilla.org/original.js",
+		line: newLine,
+		column,
+	      });
+	    },
+	  };
+	}
+      },
+    };
+
+    let trace = ReactDOM.render(StackTrace(props), window.document.body);
+    yield forceRender(trace);
+
+    let traceEl = ReactDOM.findDOMNode(trace);
+    ok(traceEl, "Rendered StackTrace has an element");
+
+    // Get the child nodes and filter out the text-only whitespace ones
+    let frameEls = Array.from(traceEl.childNodes)
+      .filter(n => n.className && n.className.includes("frame"));
+    ok(frameEls, "Rendered StackTrace has frames");
+    is(frameEls.length, 2, "StackTrace has 2 frames");
+
+    checkFrameString({
+      el: frameEls[0],
+      functionName: "<anonymous>",
+      source: "https://bugzilla.mozilla.org/original.js",
+      file: "original.js",
+      line: 1,
+      column: 10,
+      shouldLink: true,
+      tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:1:10",
+    });
+
+    checkFrameString({
+      el: frameEls[1],
+      functionName: "loadFunc",
+      source: "https://bugzilla.mozilla.org/original.js",
+      file: "original.js",
+      line: 7,
+      column: null,
+      shouldLink: true,
+      tooltip: "View source in Debugger → https://bugzilla.mozilla.org/original.js:7",
+    });
+  });
+};
+</script>
+</body>
+</html>
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -16,17 +16,17 @@ const Editor = require("devtools/client/
 const promise = require("promise");
 const {shortSource, prettifyCSS} = require("devtools/shared/inspector/css-logic");
 const {console} = require("resource://gre/modules/Console.jsm");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {Task} = require("devtools/shared/task");
 const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
 const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
-const {TextDecoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {
   getString,
   showFilePicker,
 } = require("resource://devtools/client/styleeditor/StyleEditorUtil.jsm");
 
 const LOAD_ERROR = "error-load";
 const SAVE_ERROR = "error-save";
 
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -14,22 +14,21 @@ loader.lazyImporter(this, "escapeHTML", 
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "TableWidget", "devtools/client/shared/widgets/TableWidget", true);
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
 
 const { extend } = require("sdk/core/heritage");
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const STRINGS_URI = "devtools/client/locales/webconsole.properties";
 
 const WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
 const { getSourceNames } = require("devtools/client/shared/source-utils");
 const {Task} = require("devtools/shared/task");
-const l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const {PluralForm} = require("devtools/shared/plural-form");
 
 const MAX_STRING_GRIP_LENGTH = 36;
 const {ELLIPSIS} = require("devtools/shared/l10n");
 
 const validProtocols = /^(http|https|ftp|data|javascript|resource|chrome):/i;
 
@@ -3551,19 +3550,22 @@ Widgets.Stacktrace.prototype = extend(Wi
     if (this.element) {
       return this;
     }
 
     let result = this.element = this.document.createElementNS(XHTML_NS, "div");
     result.className = "stacktrace devtools-monospace";
 
     if (this.stacktrace) {
+      const target = this.message.output.toolboxTarget;
+      const toolbox = gDevTools.getToolbox(target);
       this.output.owner.ReactDOM.render(this.output.owner.StackTraceView({
         stacktrace: this.stacktrace,
-        onViewSourceInDebugger: frame => this.output.openLocationInDebugger(frame)
+        onViewSourceInDebugger: frame => this.output.openLocationInDebugger(frame),
+        sourceMapService: toolbox ? toolbox.sourceMapURLService : null,
       }), result);
     }
 
     return this;
   }
 });
 
 /**
--- a/devtools/client/webconsole/hudservice.js
+++ b/devtools/client/webconsole/hudservice.js
@@ -17,18 +17,17 @@ var Services = require("Services");
 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);
 loader.lazyRequireGetter(this, "showDoorhanger", "devtools/client/shared/doorhanger", true);
 loader.lazyRequireGetter(this, "viewSource", "devtools/client/shared/view-source");
 
-const STRINGS_URI = "devtools/client/locales/webconsole.properties";
-var l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,resizable,dialog=no";
 
 // The preference prefix for all of the Browser Console filters.
 const BROWSER_CONSOLE_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter.";
 
 var gHudId = 0;
 
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -22,18 +22,17 @@ loader.lazyRequireGetter(this, "ToolSide
 loader.lazyRequireGetter(this, "Messages", "devtools/client/webconsole/console-output", true);
 loader.lazyRequireGetter(this, "asyncStorage", "devtools/shared/async-storage");
 loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/main", true);
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
 loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
 loader.lazyImporter(this, "VariablesViewController", "resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 
-const STRINGS_URI = "devtools/client/locales/webconsole.properties";
-var l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 // Constants used for defining the direction of JSTerm input history navigation.
 const HISTORY_BACK = -1;
 const HISTORY_FORWARD = 1;
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
--- a/devtools/client/webconsole/moz.build
+++ b/devtools/client/webconsole/moz.build
@@ -14,13 +14,14 @@ DIRS += [
 DevToolsModules(
     'console-commands.js',
     'console-output.js',
     'hudservice.js',
     'jsterm.js',
     'panel.js',
     'utils.js',
     'webconsole-connection-proxy.js',
+    'webconsole-l10n.js',
     'webconsole.js',
 )
 
-with Files('**'):
-    BUG_COMPONENT = ('Firefox', 'Developer Tools: Console')
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Developer Tools: Console')
--- a/devtools/client/webconsole/net/components/net-info-body.js
+++ b/devtools/client/webconsole/net/components/net-info-body.js
@@ -33,17 +33,19 @@ const PropTypes = React.PropTypes;
  */
 var NetInfoBody = React.createClass({
   propTypes: {
     tabActive: PropTypes.number.isRequired,
     actions: PropTypes.object.isRequired,
     data: PropTypes.shape({
       request: PropTypes.object.isRequired,
       response: PropTypes.object.isRequired
-    })
+    }),
+    // Service to enable the source map feature.
+    sourceMapService: PropTypes.object,
   },
 
   displayName: "NetInfoBody",
 
   getDefaultProps() {
     return {
       tabActive: 0
     };
@@ -71,17 +73,17 @@ var NetInfoBody = React.createClass({
   },
 
   hasStackTrace() {
     let {cause} = this.state.data;
     return cause && cause.stacktrace && cause.stacktrace.length > 0;
   },
 
   getTabPanels() {
-    let actions = this.props.actions;
+    let { actions, sourceMapService } = this.props;
     let data = this.state.data;
     let {request} = data;
 
     // Flags for optional tabs. Some tabs are visible only if there
     // are data to display.
     let hasParams = request.queryString && request.queryString.length;
     let hasPostData = request.bodySize > 0;
 
@@ -148,17 +150,18 @@ var NetInfoBody = React.createClass({
     if (this.hasStackTrace()) {
       panels.push(
         TabPanel({
           className: "stacktrace-tab",
           key: "stacktrace",
           title: Locale.$STR("netRequest.callstack")},
           StackTraceTab({
             data: data,
-            actions: actions
+            actions: actions,
+            sourceMapService: sourceMapService,
           })
         )
       );
     }
 
     return panels;
   },
 
--- a/devtools/client/webconsole/net/components/stacktrace-tab.js
+++ b/devtools/client/webconsole/net/components/stacktrace-tab.js
@@ -8,22 +8,24 @@ const StackTrace = createFactory(require
 
 const StackTraceTab = createClass({
   displayName: "StackTraceTab",
 
   propTypes: {
     data: PropTypes.object.isRequired,
     actions: PropTypes.shape({
       onViewSourceInDebugger: PropTypes.func.isRequired
-    })
+    }),
+    // Service to enable the source map feature.
+    sourceMapService: PropTypes.object,
   },
 
   render() {
     let { stacktrace } = this.props.data.cause;
-    let { actions } = this.props;
+    let { actions, sourceMapService } = this.props;
     let onViewSourceInDebugger = actions.onViewSourceInDebugger.bind(actions);
 
-    return StackTrace({ stacktrace, onViewSourceInDebugger });
+    return StackTrace({ stacktrace, onViewSourceInDebugger, sourceMapService });
   }
 });
 
 // Exports from this module
 module.exports = StackTraceTab;
--- a/devtools/client/webconsole/net/net-request.js
+++ b/devtools/client/webconsole/net/net-request.js
@@ -158,17 +158,18 @@ NetRequest.prototype = {
     let doc = messageBody.ownerDocument;
     this.netInfoBodyBox = doc.createElementNS(XHTML_NS, "div");
     this.netInfoBodyBox.classList.add("netInfoBody");
     messageBody.appendChild(this.netInfoBodyBox);
 
     // As soon as Redux is in place state and actions will come from
     // separate modules.
     let body = NetInfoBody({
-      actions: this
+      actions: this,
+      sourceMapService: this.owner.sourceMapURLService,
     });
 
     // Render net info body!
     this.body = ReactDOM.render(body, this.netInfoBodyBox);
 
     this.refresh();
   },
 
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -25,16 +25,17 @@ const ConsoleOutput = createClass({
   displayName: "ConsoleOutput",
 
   propTypes: {
     messages: PropTypes.object.isRequired,
     messagesUi: PropTypes.object.isRequired,
     serviceContainer: PropTypes.shape({
       attachRefToHud: PropTypes.func.isRequired,
       openContextMenu: PropTypes.func.isRequired,
+      sourceMapService: PropTypes.object,
     }),
     autoscroll: PropTypes.bool.isRequired,
     dispatch: PropTypes.func.isRequired,
     timestampsVisible: PropTypes.bool,
     groups: PropTypes.object.isRequired,
     messagesTableData: PropTypes.object.isRequired,
   },
 
--- a/devtools/client/webconsole/new-console-output/components/filter-bar.js
+++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js
@@ -8,17 +8,16 @@ const {
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
 const {
   filterTextSet,
-  filtersClear,
   filterBarToggle,
   messagesClear
 } = require("devtools/client/webconsole/new-console-output/actions/index");
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const {
   MESSAGE_LEVEL
 } = require("../constants");
 const FilterButton = require("devtools/client/webconsole/new-console-output/components/filter-button");
@@ -44,20 +43,16 @@ const FilterBar = createClass({
   onClickMessagesClear: function () {
     this.props.dispatch(messagesClear());
   },
 
   onClickFilterBarToggle: function () {
     this.props.dispatch(filterBarToggle());
   },
 
-  onClickFiltersClear: function () {
-    this.props.dispatch(filtersClear());
-  },
-
   onSearchInput: function (e) {
     this.props.dispatch(filterTextSet(e.target.value));
   },
 
   render() {
     const {dispatch, filter, filterBarVisible} = this.props;
     let children = [];
 
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-command.js
@@ -15,44 +15,42 @@ const Message = createFactory(require("d
 
 ConsoleCommand.displayName = "ConsoleCommand";
 
 ConsoleCommand.propTypes = {
   message: PropTypes.object.isRequired,
   autoscroll: PropTypes.bool.isRequired,
   indent: PropTypes.number.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
+  serviceContainer: PropTypes.object,
 };
 
 ConsoleCommand.defaultProps = {
   indent: 0,
 };
 
 /**
  * Displays input from the console.
  */
 function ConsoleCommand(props) {
   const {
     autoscroll,
     indent,
     message,
     timestampsVisible,
+    serviceContainer,
   } = props;
 
   const {
     source,
     type,
     level,
     messageText: messageBody,
   } = message;
 
-  const {
-    serviceContainer,
-  } = props;
-
   return Message({
     source,
     type,
     level,
     topLevelClasses: [],
     messageBody,
     scrollToMessage: autoscroll,
     serviceContainer,
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -15,16 +15,17 @@ const Message = createFactory(require("d
 const GripMessageBody = require("devtools/client/webconsole/new-console-output/components/grip-message-body");
 
 EvaluationResult.displayName = "EvaluationResult";
 
 EvaluationResult.propTypes = {
   message: PropTypes.object.isRequired,
   indent: PropTypes.number.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
+  serviceContainer: PropTypes.object,
 };
 
 EvaluationResult.defaultProps = {
   indent: 0,
 };
 
 function EvaluationResult(props) {
   const {
--- a/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/page-error.js
@@ -15,16 +15,17 @@ const Message = createFactory(require("d
 
 PageError.displayName = "PageError";
 
 PageError.propTypes = {
   message: PropTypes.object.isRequired,
   open: PropTypes.bool,
   indent: PropTypes.number.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
+  serviceContainer: PropTypes.object,
 };
 
 PageError.defaultProps = {
   open: false,
   indent: 0,
 };
 
 function PageError(props) {
--- a/devtools/client/webconsole/new-console-output/components/message.js
+++ b/devtools/client/webconsole/new-console-output/components/message.js
@@ -143,16 +143,17 @@ const Message = createClass({
       attachment = dom.div(
         {
           className: "stacktrace devtools-monospace"
         },
         StackTrace({
           stacktrace: stacktrace,
           onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger,
           onViewSourceInScratchpad: serviceContainer.onViewSourceInScratchpad,
+          sourceMapService: serviceContainer.sourceMapService,
         })
       );
     }
 
     // If there is an expandable part, make it collapsible.
     let collapse = null;
     if (collapsible) {
       collapse = CollapseButton({
--- a/devtools/client/webconsole/new-console-output/selectors/messages.js
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -4,16 +4,17 @@
  * 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 { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { getLogLimit } = require("devtools/client/webconsole/new-console-output/selectors/prefs");
 const { getGripPreviewItems } = require("devtools/client/shared/components/reps/reps");
+const { getSourceNames } = require("devtools/client/shared/source-utils");
 const {
   MESSAGE_TYPE,
   MESSAGE_SOURCE
 } = require("devtools/client/webconsole/new-console-output/constants");
 
 function getAllMessages(state) {
   let messages = getAllMessagesById(state);
   let logLimit = getLogLimit(state);
@@ -126,18 +127,19 @@ function matchSearchFilters(message, fil
 
 /**
  * Returns true if given text is included in provided stack frame.
  */
 function isTextInFrame(text, frame) {
   if (!frame) {
     return false;
   }
-  return Object.values(frame)
-    .join(":")
+
+  const { short } = getSourceNames(frame.source);
+  return `${short}:${frame.line}:${frame.column}`
     .toLocaleLowerCase()
     .includes(text.toLocaleLowerCase());
 }
 
 /**
  * Returns true if given text is included in provided parameters.
  */
 function isTextInParameters(text, parameters) {
--- a/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -94,18 +94,17 @@ describe("ConsoleAPICall component:", ()
 
     it("renders a timestamp when passed a truthy timestampsVisible prop", () => {
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
       const wrapper = render(ConsoleApiCall({
         message,
         serviceContainer,
         timestampsVisible: true,
       }));
-      const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
-      const { timestampString } = new L10n();
+      const { timestampString } = require("devtools/client/webconsole/webconsole-l10n");
 
       expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp));
     });
 
     it("does not render a timestamp when not asked to", () => {
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
       const wrapper = render(ConsoleApiCall({
         message,
--- a/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/evaluation-result.test.js
@@ -92,18 +92,17 @@ describe("EvaluationResult component:", 
   });
 
   it("has a timestamp when passed a truthy timestampsVisible prop", () => {
     const message = stubPreparedMessages.get("new Date(0)");
     const wrapper = render(EvaluationResult({
       message,
       timestampsVisible: true,
     }));
-    const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
-    const { timestampString } = new L10n();
+    const { timestampString } = require("devtools/client/webconsole/webconsole-l10n");
 
     expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp));
   });
 
   it("does not have a timestamp when timestampsVisible prop is falsy", () => {
     const message = stubPreparedMessages.get("new Date(0)");
     const wrapper = render(EvaluationResult({
       message,
--- a/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/network-event-message.test.js
@@ -24,18 +24,17 @@ describe("NetworkEventMessage component:
   describe("GET request", () => {
     it("renders as expected", () => {
       const message = stubPreparedMessages.get("GET request eventTimings");
       const wrapper = render(NetworkEventMessage({
         message,
         serviceContainer,
         timestampsVisible: true,
       }));
-      const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
-      const { timestampString } = new L10n();
+      const { timestampString } = require("devtools/client/webconsole/webconsole-l10n");
 
       expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp));
       expect(wrapper.find(".message-body .method").text()).toBe("GET");
       expect(wrapper.find(".message-body .xhr").length).toBe(0);
       expect(wrapper.find(".message-body .url").length).toBe(1);
       expect(wrapper.find(".message-body .url").text()).toBe(EXPECTED_URL);
       expect(wrapper.find(".message-body .status").length).toBe(1);
       expect(wrapper.find(".message-body .status").text()).toMatch(EXPECTED_STATUS);
--- a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -27,18 +27,17 @@ const serviceContainer = require("devtoo
 describe("PageError component:", () => {
   it("renders", () => {
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
     const wrapper = render(PageError({
       message,
       serviceContainer,
       timestampsVisible: true,
     }));
-    const L10n = require("devtools/client/webconsole/new-console-output/test/fixtures/L10n");
-    const { timestampString } = new L10n();
+    const { timestampString } = require("devtools/client/webconsole/webconsole-l10n");
 
     expect(wrapper.find(".timestamp").text()).toBe(timestampString(message.timeStamp));
 
     expect(wrapper.find(".message-body").text())
       .toBe("ReferenceError: asdf is not defined[Learn More]");
 
     // The stacktrace should be closed by default.
     const frameLinks = wrapper.find(`.stack-trace`);
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -9,19 +9,17 @@
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
-var {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
-const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties";
-var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
+var WCUL10n = require("devtools/client/webconsole/webconsole-l10n");
 
 Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
 registerCleanupFunction(function* () {
   Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
 
   let browserConsole = HUDService.getBrowserConsole();
   if (browserConsole) {
     if (browserConsole.jsterm) {
--- a/devtools/client/webconsole/new-console-output/test/require-helper.js
+++ b/devtools/client/webconsole/new-console-output/test/require-helper.js
@@ -22,18 +22,16 @@ requireHacker.global_hook("default", pat
     // For Rep's use of AMD
     case "devtools/client/shared/vendor/react.default":
       return `const React = require('devtools/client/shared/vendor/react-dev'); module.exports = React`;
   }
 
   // Some modules depend on Chrome APIs which don't work in mocha. When such a module
   // is required, replace it with a mock version.
   switch (path) {
-    case "devtools/client/webconsole/utils":
-      return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/WebConsoleUtils")`;
     case "devtools/shared/l10n":
       return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/LocalizationHelper")`;
     case "devtools/shared/plural-form":
       return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/PluralForm")`;
     case "Services":
     case "Services.default":
       return `module.exports = require("devtools/client/webconsole/new-console-output/test/fixtures/Services")`;
     case "devtools/shared/client/main":
--- a/devtools/client/webconsole/new-console-output/test/store/search.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/search.test.js
@@ -61,16 +61,28 @@ describe("Searching in grips", () => {
 
   describe("Search in logs with net messages", () => {
     it("matches on network messages", () => {
       store.dispatch(actions.filterToggle("net"));
       store.dispatch(actions.filterTextSet("get"));
       expect(getAllMessages(store.getState()).size).toEqual(1);
     });
   });
+
+  describe("Search in frame", () => {
+    it("matches on file name", () => {
+      store.dispatch(actions.filterTextSet("test-console-api.html:1:27"));
+      expect(getAllMessages(store.getState()).size).toEqual(7);
+    });
+
+    it("do not match on full url", () => {
+      store.dispatch(actions.filterTextSet("http://example.com/browser/devtools"));
+      expect(getAllMessages(store.getState()).size).toEqual(0);
+    });
+  });
 });
 
 function prepareBaseStore() {
   const store = setupStore([
     "console.log('foobar', 'test')",
     "console.warn('danger, will robinson!')",
     "console.table(['red', 'green', 'blue']);",
     "console.count('bar')",
--- a/devtools/client/webconsole/new-console-output/utils/messages.js
+++ b/devtools/client/webconsole/new-console-output/utils/messages.js
@@ -1,19 +1,17 @@
 /* -*- 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 WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
-const STRINGS_URI = "devtools/client/locales/webconsole.properties";
-const l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const {
   MESSAGE_SOURCE,
   MESSAGE_TYPE,
   MESSAGE_LEVEL,
 } = require("../constants");
 const {
   ConsoleMessage,
--- a/devtools/client/webconsole/test/head.js
+++ b/devtools/client/webconsole/test/head.js
@@ -32,18 +32,17 @@ const CATEGORY_SERVER = 7;
 const SEVERITY_ERROR = 0;
 const SEVERITY_WARNING = 1;
 const SEVERITY_INFO = 2;
 const SEVERITY_LOG = 3;
 
 // The indent of a console group in pixels.
 const GROUP_INDENT = 12;
 
-const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties";
-var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
+var WCUL10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const DOCS_GA_PARAMS = "?utm_source=mozilla" +
                        "&utm_medium=firefox-console-errors" +
                        "&utm_campaign=default";
 
 flags.testing = true;
 
 function loadTab(url, preferredRemoteType) {
--- a/devtools/client/webconsole/utils.js
+++ b/devtools/client/webconsole/utils.js
@@ -3,17 +3,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/. */
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const Services = require("Services");
-const {LocalizationHelper} = require("devtools/shared/l10n");
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
 // function() { ...
 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
@@ -280,70 +279,8 @@ var WebConsoleUtils = {
       return false;
     };
     return handler;
   },
 };
 
 exports.Utils = WebConsoleUtils;
 
-// Localization
-
-WebConsoleUtils.L10n = function (bundleURI) {
-  this._helper = new LocalizationHelper(bundleURI);
-};
-
-WebConsoleUtils.L10n.prototype = {
-  /**
-   * Generates a formatted timestamp string for displaying in console messages.
-   *
-   * @param integer [milliseconds]
-   *        Optional, allows you to specify the timestamp in milliseconds since
-   *        the UNIX epoch.
-   * @return string
-   *         The timestamp formatted for display.
-   */
-  timestampString: function (milliseconds) {
-    let d = new Date(milliseconds ? milliseconds : null);
-    let hours = d.getHours(), minutes = d.getMinutes();
-    let seconds = d.getSeconds();
-    milliseconds = d.getMilliseconds();
-    let parameters = [hours, minutes, seconds, milliseconds];
-    return this.getFormatStr("timestampFormat", parameters);
-  },
-
-  /**
-   * Retrieve a localized string.
-   *
-   * @param string name
-   *        The string name you want from the Web Console string bundle.
-   * @return string
-   *         The localized string.
-   */
-  getStr: function (name) {
-    try {
-      return this._helper.getStr(name);
-    } catch (ex) {
-      console.error("Failed to get string: " + name);
-      throw ex;
-    }
-  },
-
-  /**
-   * Retrieve a localized string formatted with values coming from the given
-   * array.
-   *
-   * @param string name
-   *        The string name you want from the Web Console string bundle.
-   * @param array array
-   *        The array of values you want in the formatted string.
-   * @return string
-   *         The formatted local string.
-   */
-  getFormatStr: function (name, array) {
-    try {
-      return this._helper.getFormatStr(name, ...array);
-    } catch (ex) {
-      console.error("Failed to format string: " + name);
-      throw ex;
-    }
-  },
-};
--- a/devtools/client/webconsole/webconsole-connection-proxy.js
+++ b/devtools/client/webconsole/webconsole-connection-proxy.js
@@ -5,18 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Utils: WebConsoleUtils} = require("devtools/client/webconsole/utils");
 const defer = require("devtools/shared/defer");
 const Services = require("Services");
 
-const STRINGS_URI = "devtools/client/locales/webconsole.properties";
-var l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
 // Web Console connection proxy
 
 /**
  * The WebConsoleConnectionProxy handles the connection between the Web Console
  * and the application we connect to through the remote debug protocol.
  *
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/webconsole-l10n.js
@@ -0,0 +1,70 @@
+/* -*- 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 {LocalizationHelper} = require("devtools/shared/l10n");
+const helper = new LocalizationHelper("devtools/client/locales/webconsole.properties");
+
+const l10n = {
+
+  /**
+   * Generates a formatted timestamp string for displaying in console messages.
+   *
+   * @param integer [milliseconds]
+   *        Optional, allows you to specify the timestamp in milliseconds since
+   *        the UNIX epoch.
+   * @return string
+   *         The timestamp formatted for display.
+   */
+  timestampString: function (milliseconds) {
+    let d = new Date(milliseconds ? milliseconds : null);
+    let hours = d.getHours(), minutes = d.getMinutes();
+    let seconds = d.getSeconds();
+    milliseconds = d.getMilliseconds();
+    let parameters = [hours, minutes, seconds, milliseconds];
+    return l10n.getFormatStr("timestampFormat", parameters);
+  },
+
+  /**
+   * Retrieve a localized string.
+   *
+   * @param string name
+   *        The string name you want from the Web Console string bundle.
+   * @return string
+   *         The localized string.
+   */
+  getStr: function (name) {
+    try {
+      return helper.getStr(name);
+    } catch (ex) {
+      console.error("Failed to get string: " + name);
+      throw ex;
+    }
+  },
+
+  /**
+   * Retrieve a localized string formatted with values coming from the given
+   * array.
+   *
+   * @param string name
+   *        The string name you want from the Web Console string bundle.
+   * @param array array
+   *        The array of values you want in the formatted string.
+   * @return string
+   *         The formatted local string.
+   */
+  getFormatStr: function (name, array) {
+    try {
+      return helper.getFormatStr(name, ...array);
+    } catch (ex) {
+      console.error("Failed to format string: " + name);
+      throw ex;
+    }
+  },
+};
+
+module.exports = l10n;
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -33,18 +33,17 @@ loader.lazyRequireGetter(this, "gSequenc
 loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
 loader.lazyImporter(this, "VariablesViewController", "resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "KeyShortcuts", "devtools/client/shared/key-shortcuts");
 loader.lazyRequireGetter(this, "ZoomKeys", "devtools/client/shared/zoom-keys");
 loader.lazyRequireGetter(this, "WebConsoleConnectionProxy", "devtools/client/webconsole/webconsole-connection-proxy", true);
 
 const {PluralForm} = require("devtools/shared/plural-form");
-const STRINGS_URI = "devtools/client/locales/webconsole.properties";
-var l10n = new WebConsoleUtils.L10n(STRINGS_URI);
+const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 
 const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Web/Security/Mixed_content";
 
 const IGNORED_SOURCE_URLS = ["debugger eval code"];
 
 // The amount of time in milliseconds that we wait before performing a live
--- a/devtools/client/webconsole/webpack.config.js
+++ b/devtools/client/webconsole/webpack.config.js
@@ -44,16 +44,17 @@ let webpackConfig = {
 webpackConfig.resolve = {
   alias: {
     "Services": "devtools-modules/client/shared/shim/Services",
 
     "devtools/client/webconsole/jsterm": path.join(projectPath, "jsterm-stub"),
     "devtools/client/webconsole/utils": path.join(__dirname, "new-console-output/test/fixtures/WebConsoleUtils"),
     "devtools/client/webconsole/new-console-output": path.join(__dirname, "new-console-output"),
     "devtools/client/webconsole/webconsole-connection-proxy": path.join(__dirname, "webconsole-connection-proxy"),
+    "devtools/client/webconsole/webconsole-l10n": path.join(__dirname, "webconsole-l10n"),
 
     "react": path.join(__dirname, "node_modules/react"),
     "devtools/client/shared/vendor/immutable": "immutable",
     "devtools/client/shared/vendor/react": "react",
     "devtools/client/shared/vendor/react-dom": "react-dom",
     "devtools/client/shared/vendor/react-redux": "react-redux",
     "devtools/client/shared/vendor/redux": "redux",
 
--- a/devtools/client/webide/modules/app-manager.js
+++ b/devtools/client/webide/modules/app-manager.js
@@ -4,17 +4,17 @@
 
 const {Cu} = require("chrome");
 
 const promise = require("promise");
 const {TargetFactory} = require("devtools/client/framework/target");
 const Services = require("Services");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const EventEmitter = require("devtools/shared/event-emitter");
-const {TextEncoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {AppProjects} = require("devtools/client/webide/modules/app-projects");
 const TabStore = require("devtools/client/webide/modules/tab-store");
 const {AppValidator} = require("devtools/client/webide/modules/app-validator");
 const {ConnectionManager, Connection} = require("devtools/shared/client/connection-manager");
 const {AppActorFront} = require("devtools/shared/apps/app-actor-front");
 const {getDeviceFront} = require("devtools/shared/fronts/device");
 const {getPreferenceFront} = require("devtools/shared/fronts/preference");
 const {Task} = require("devtools/shared/task");
--- a/devtools/client/webide/modules/build.js
+++ b/devtools/client/webide/modules/build.js
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {Cu, Cc, Ci} = require("chrome");
 
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
-const { TextDecoder, OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
 const Subprocess = require("sdk/system/child_process/subprocess");
 
 const ProjectBuilding = exports.ProjectBuilding = {
   fetchPackageManifest: Task.async(function* (project) {
     let manifestPath = OS.Path.join(project.location, "package.json");
     let exists = yield OS.File.exists(manifestPath);
     if (!exists) {
       // No explicit manifest, try to generate one if possible
--- a/devtools/client/webide/test/test_build.html
+++ b/devtools/client/webide/test/test_build.html
@@ -13,17 +13,17 @@
   </head>
 
   <body>
 
     <script type="application/javascript">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
-        let {TextDecoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+        let {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
         let {ProjectBuilding} = require("devtools/client/webide/modules/build");
 
         Task.spawn(function* () {
             let win = yield openWebIDE();
             let winProject = getProjectWindow(win);
             let AppManager = win.AppManager;
 
             function isProjectMarkedAsValid() {
--- a/devtools/client/webide/test/test_manifestUpdate.html
+++ b/devtools/client/webide/test/test_manifestUpdate.html
@@ -13,17 +13,17 @@
   </head>
 
   <body>
 
     <script type="application/javascript">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
-        let {TextDecoder, OS}  = Cu.import("resource://gre/modules/osfile.jsm", {});
+        let {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 
         Task.spawn(function* () {
             let win = yield openWebIDE();
             let winProject = getProjectWindow(win);
             let AppManager = win.AppManager;
 
             function isProjectMarkedAsValid() {
               let details = win.frames[1];
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -1,15 +1,15 @@
 /* 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 { Cc, Ci, Cu, Cr } = require("chrome");
 const { getCurrentZoom, getWindowDimensions, getViewportDimensions,
   getRootBindingParent, loadSheet } = require("devtools/shared/layout/utils");
 const { on, emit } = require("sdk/event/core");
 
 const lazyContainer = {};
 
 loader.lazyRequireGetter(lazyContainer, "CssLogic",
   "devtools/server/css-logic", true);
@@ -265,17 +265,33 @@ CanvasFrameAnonymousContentHelper.protot
 
     let node = this.nodeBuilder();
 
     // It was stated that hidden documents don't accept
     // `insertAnonymousContent` calls yet. That doesn't seems the case anymore,
     // at least on desktop. Therefore, removing the code that was dealing with
     // that scenario, fixes when we're adding anonymous content in a tab that
     // is not the active one (see bug 1260043 and bug 1260044)
-    this._content = doc.insertAnonymousContent(node);
+    try {
+      this._content = doc.insertAnonymousContent(node);
+    } catch (e) {
+      // If the `insertAnonymousContent` fails throwing a `NS_ERROR_UNEXPECTED`, it means
+      // we don't have access to a `CustomContentContainer` yet (see bug 1365075).
+      // At this point, it could only happen on document's interactive state, and we
+      // need to wait until the `complete` state before inserting the anonymous content
+      // again.
+      if (e.result === Cr.NS_ERROR_UNEXPECTED && doc.readyState === "interactive") {
+        // The next state change will be "complete" since the current is "interactive"
+        doc.addEventListener("readystatechange", () => {
+          this._content = doc.insertAnonymousContent(node);
+        }, { once: true });
+      } else {
+        throw e;
+      }
+    }
   },
 
   _remove() {
     try {
       let doc = this.anonymousContentDocument;
       doc.removeAnonymousContent(this._content);
     } catch (e) {
       // If the current window isn't the one the content was inserted into, this
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -358,20 +358,16 @@ exports.defineLazyModuleGetter = functio
 DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
   return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
 });
 
 DevToolsUtils.defineLazyGetter(this, "OS", () => {
   return Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
 });
 
-DevToolsUtils.defineLazyGetter(this, "TextDecoder", () => {
-  return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder;
-});
-
 DevToolsUtils.defineLazyGetter(this, "NetworkHelper", () => {
   return require("devtools/shared/webconsole/network-helper");
 });
 
 /**
  * Performs a request to load the desired URL and returns a promise.
  *
  * @param urlIn String
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -15,17 +15,17 @@
 
 const { Cu, CC, Cc, Ci } = require("chrome");
 const { Loader } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {});
 const { Services } = jsmScope;
 // Steal various globals only available in JSM scope (and not Sandbox one)
 const { PromiseDebugging, ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot,
-        atob, btoa } = jsmScope;
+        atob, btoa, TextEncoder, TextDecoder } = jsmScope;
 const { URL } = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
                            {wantGlobalProperties: ["URL"]});
 
 /**
  * Defines a getter on a specified object that will be created upon first use.
  *
  * @param object
  *        The object to define the lazy getter on.
@@ -210,16 +210,18 @@ defineLazyGetter(exports.modules, "FileR
 
 // List of all custom globals exposed to devtools modules.
 // Changes here should be mirrored to devtools/.eslintrc.
 exports.globals = {
   isWorker: false,
   reportError: Cu.reportError,
   atob: atob,
   btoa: btoa,
+  TextEncoder: TextEncoder,
+  TextDecoder: TextDecoder,
   URL,
   loader: {
     lazyGetter: defineLazyGetter,
     lazyImporter: defineLazyModuleGetter,
     lazyServiceGetter: defineLazyServiceGetter,
     lazyRequireGetter: lazyRequireGetter,
     // Defined by Loader.jsm
     id: null
--- a/devtools/shared/css/generated/properties-db.js
+++ b/devtools/shared/css/generated/properties-db.js
@@ -1229,16 +1229,18 @@ exports.CSS_PROPERTIES = {
   "-moz-stack-sizing": {
     "isInherited": false,
     "subproperties": [
       "-moz-stack-sizing"
     ],
     "supports": [],
     "values": [
       "ignore",
+      "ignore-horizontal",
+      "ignore-vertical",
       "inherit",
       "initial",
       "stretch-to-fit",
       "unset"
     ]
   },
   "-moz-tab-size": {
     "isInherited": true,
@@ -3397,16 +3399,18 @@ exports.CSS_PROPERTIES = {
       "horizontal",
       "horizontal-tb",
       "hsl",
       "hsla",
       "hue",
       "icon",
       "ideographic",
       "ignore",
+      "ignore-horizontal",
+      "ignore-vertical",
       "inactive",
       "infinite",
       "inherit",
       "initial",
       "inline",
       "inline-axis",
       "inline-block",
       "inline-end",
--- a/devtools/shared/gcli/commands/cmd.js
+++ b/devtools/shared/gcli/commands/cmd.js
@@ -1,16 +1,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/. */
 
 "use strict";
 
 const { Cc, Ci, Cu } = require("chrome");
-const { OS, TextDecoder } = Cu.import("resource://gre/modules/osfile.jsm", {});
+const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
 const { Task } = require("devtools/shared/task");
 
 const gcli = require("gcli/index");
 const l10n = require("gcli/l10n");
 
 loader.lazyGetter(this, "prefBranch", function () {
   let prefService = Cc["@mozilla.org/preferences-service;1"]
                       .getService(Ci.nsIPrefService);
--- a/dom/base/AnonymousContent.cpp
+++ b/dom/base/AnonymousContent.cpp
@@ -223,14 +223,15 @@ AnonymousContent::GetComputedStyleProper
 
   nsIPresShell* shell = element->OwnerDoc()->GetShell();
   if (!shell) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
   RefPtr<nsComputedDOMStyle> cs =
-    new nsComputedDOMStyle(element, NS_LITERAL_STRING(""), shell);
+    new nsComputedDOMStyle(element, NS_LITERAL_STRING(""), shell,
+                           nsComputedDOMStyle::eAll);
   aRv = cs->GetPropertyValue(aPropertyName, aResult);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2294,23 +2294,25 @@ Element::InternalGetAttrNameFromQName(co
 bool
 Element::MaybeCheckSameAttrVal(int32_t aNamespaceID,
                                nsIAtom* aName,
                                nsIAtom* aPrefix,
                                const nsAttrValueOrString& aValue,
                                bool aNotify,
                                nsAttrValue& aOldValue,
                                uint8_t* aModType,
-                               bool* aHasListeners)
+                               bool* aHasListeners,
+                               bool* aOldValueSet)
 {
   bool modification = false;
   *aHasListeners = aNotify &&
     nsContentUtils::HasMutationListeners(this,
                                          NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
                                          this);
+  *aOldValueSet = false;
 
   // If we have no listeners and aNotify is false, we are almost certainly
   // coming from the content sink and will almost certainly have no previous
   // value.  Even if we do, setting the value is cheap when we have no
   // listeners and don't plan to notify.  The check for aNotify here is an
   // optimization, the check for *aHasListeners is a correctness issue.
   if (*aHasListeners || aNotify) {
     BorrowedAttrInfo info(GetAttrInfo(aNamespaceID, aName));
@@ -2325,16 +2327,17 @@ Element::MaybeCheckSameAttrVal(int32_t a
         // If the current attribute value contains a pointer to some other data
         // structure that gets updated in the process of setting the attribute
         // we'll no longer have the old value of the attribute. Therefore, we
         // should serialize the attribute value now to keep a snapshot.
         //
         // We have to serialize the value anyway in order to create the
         // mutation event so there's no cost in doing it now.
         aOldValue.SetToSerialized(*info.mValue);
+        *aOldValueSet = true;
       }
       bool valueMatches = aValue.EqualsAsStrings(*info.mValue);
       if (valueMatches && aPrefix == info.mName->GetPrefix()) {
         return true;
       }
       modification = true;
     }
   }
@@ -2344,20 +2347,22 @@ Element::MaybeCheckSameAttrVal(int32_t a
   return false;
 }
 
 bool
 Element::OnlyNotifySameValueSet(int32_t aNamespaceID, nsIAtom* aName,
                                 nsIAtom* aPrefix,
                                 const nsAttrValueOrString& aValue,
                                 bool aNotify, nsAttrValue& aOldValue,
-                                uint8_t* aModType, bool* aHasListeners)
+                                uint8_t* aModType, bool* aHasListeners,
+                                bool* aOldValueSet)
 {
   if (!MaybeCheckSameAttrVal(aNamespaceID, aName, aPrefix, aValue, aNotify,
-                             aOldValue, aModType, aHasListeners)) {
+                             aOldValue, aModType, aHasListeners,
+                             aOldValueSet)) {
     return false;
   }
 
   nsAutoScriptBlocker scriptBlocker;
   nsNodeUtils::AttributeSetToCurrentValue(this, aNamespaceID, aName);
   return true;
 }
 
@@ -2378,19 +2383,20 @@ Element::SetAttr(int32_t aNamespaceID, n
 
   uint8_t modType;
   bool hasListeners;
   // We don't want to spend time preparsing class attributes if the value is not
   // changing, so just init our nsAttrValueOrString with aValue for the
   // OnlyNotifySameValueSet call.
   nsAttrValueOrString value(aValue);
   nsAttrValue oldValue;
+  bool oldValueSet;
 
   if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
-                             oldValue, &modType, &hasListeners)) {
+                             oldValue, &modType, &hasListeners, &oldValueSet)) {
     return NS_OK;
   }
 
   nsAttrValue attrValue;
   nsAttrValue* preparsedAttrValue;
   if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::_class) {
     attrValue.ParseAtomArray(aValue);
     value.ResetToAttrValue(attrValue);
@@ -2413,17 +2419,18 @@ Element::SetAttr(int32_t aNamespaceID, n
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
   // Even the value was pre-parsed, we still need to call ParseAttribute because
   // it can have side effects.
   if (!ParseAttribute(aNamespaceID, aName, aValue, attrValue)) {
     attrValue.SetTo(aValue);
   }
 
-  return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValue,
+  return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
+                          oldValueSet ? &oldValue : nullptr,
                           attrValue, modType, hasListeners, aNotify,
                           kCallAfterSetAttr, document, updateBatch);
 }
 
 nsresult
 Element::SetParsedAttr(int32_t aNamespaceID, nsIAtom* aName,
                        nsIAtom* aPrefix, nsAttrValue& aParsedValue,
                        bool aNotify)
@@ -2438,42 +2445,44 @@ Element::SetParsedAttr(int32_t aNamespac
     return NS_ERROR_FAILURE;
   }
 
 
   uint8_t modType;
   bool hasListeners;
   nsAttrValueOrString value(aParsedValue);
   nsAttrValue oldValue;
+  bool oldValueSet;
 
   if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
-                             oldValue, &modType, &hasListeners)) {
+                             oldValue, &modType, &hasListeners, &oldValueSet)) {
     return NS_OK;
   }
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNamespaceID, aName, modType,
                                      &aParsedValue);
   }
 
   nsresult rv = BeforeSetAttr(aNamespaceID, aName, &value, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsIDocument* document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
-  return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValue,
+  return SetAttrAndNotify(aNamespaceID, aName, aPrefix,
+                          oldValueSet ? &oldValue : nullptr,
                           aParsedValue, modType, hasListeners, aNotify,
                           kCallAfterSetAttr, document, updateBatch);
 }
 
 nsresult
 Element::SetAttrAndNotify(int32_t aNamespaceID,
                           nsIAtom* aName,
                           nsIAtom* aPrefix,
-                          const nsAttrValue& aOldValue,
+                          const nsAttrValue* aOldValue,
                           nsAttrValue& aParsedValue,
                           uint8_t aModType,
                           bool aFireMutation,
                           bool aNotify,
                           bool aCallAfterSetAttr,
                           nsIDocument* aComposedDocument,
                           const mozAutoDocUpdate&)
 {
@@ -2484,85 +2493,104 @@ Element::SetAttrAndNotify(int32_t aNames
   // SetAndSwapMappedAttr below
   nsAttrValue valueForAfterSetAttr;
   if (aCallAfterSetAttr) {
     valueForAfterSetAttr.SetTo(aParsedValue);
   }
 
   bool hadValidDir = false;
   bool hadDirAuto = false;
+  bool oldValueSet;
 
   if (aNamespaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::dir) {
       hadValidDir = HasValidDir() || IsHTMLElement(nsGkAtoms::bdi);
       hadDirAuto = HasDirAuto(); // already takes bdi into account
     }
 
     // XXXbz Perhaps we should push up the attribute mapping function
     // stuff to Element?
     if (!IsAttributeMapped(aName) ||
-        !SetMappedAttribute(aName, aParsedValue, &rv)) {
-      rv = mAttrsAndChildren.SetAndSwapAttr(aName, aParsedValue);
+        !SetAndSwapMappedAttribute(aName, aParsedValue, &oldValueSet, &rv)) {
+      rv = mAttrsAndChildren.SetAndSwapAttr(aName, aParsedValue, &oldValueSet);
     }
   }
   else {
     RefPtr<mozilla::dom::NodeInfo> ni;
     ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(aName, aPrefix,
                                                    aNamespaceID,
                                                    nsIDOMNode::ATTRIBUTE_NODE);
 
-    rv = mAttrsAndChildren.SetAndSwapAttr(ni, aParsedValue);
+    rv = mAttrsAndChildren.SetAndSwapAttr(ni, aParsedValue, &oldValueSet);
   }
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // If the old value owns its own data, we know it is OK to keep using it.
-  const nsAttrValue* oldValue =
-      aParsedValue.StoresOwnData() ? &aParsedValue : &aOldValue;
-
-  NS_ENSURE_SUCCESS(rv, rv);
+  // oldValue will be null if there was no previously set value
+  const nsAttrValue* oldValue;
+  if (aParsedValue.StoresOwnData()) {
+    if (oldValueSet) {
+      oldValue = &aParsedValue;
+    } else {
+      oldValue = nullptr;
+    }
+  } else {
+    // No need to conditionally assign null here. If there was no previously
+    // set value for the attribute, aOldValue will already be null.
+    oldValue = aOldValue;
+  }
 
   if (aComposedDocument || HasFlag(NODE_FORCE_XBL_BINDINGS)) {
     RefPtr<nsXBLBinding> binding = GetXBLBinding();
     if (binding) {
       binding->AttributeChanged(aName, aNamespaceID, false, aNotify);
     }
   }
 
   nsIDocument* ownerDoc = OwnerDoc();
   if (ownerDoc && GetCustomElementData()) {
-    nsCOMPtr<nsIAtom> oldValueAtom = oldValue->GetAsAtom();
+    nsCOMPtr<nsIAtom> oldValueAtom;
+    if (oldValue) {
+      oldValueAtom = oldValue->GetAsAtom();
+    } else {
+      // If there is no old value, get the value of the uninitialized attribute
+      // that was swapped with aParsedValue.
+      oldValueAtom = aParsedValue.GetAsAtom();
+    }
     nsCOMPtr<nsIAtom> newValueAtom = valueForAfterSetAttr.GetAsAtom();
     LifecycleCallbackArgs args = {
       nsDependentAtomString(aName),
       aModType == nsIDOMMutationEvent::ADDITION ?
         NullString() : nsDependentAtomString(oldValueAtom),
       nsDependentAtomString(newValueAtom)
     };
 
     nsContentUtils::EnqueueLifecycleCallback(
       ownerDoc, nsIDocument::eAttributeChanged, this, &args);
   }
 
   if (aCallAfterSetAttr) {
-    rv = AfterSetAttr(aNamespaceID, aName, &valueForAfterSetAttr, aNotify);
+    rv = AfterSetAttr(aNamespaceID, aName, &valueForAfterSetAttr, oldValue,
+                      aNotify);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::dir) {
       OnSetDirAttr(this, &valueForAfterSetAttr,
                    hadValidDir, hadDirAuto, aNotify);
     }
   }
 
   UpdateState(aNotify);
 
   if (aNotify) {
     // Don't pass aOldValue to AttributeChanged since it may not be reliable.
     // Callers only compute aOldValue under certain conditions which may not
     // be triggered by all nsIMutationObservers.
     nsNodeUtils::AttributeChanged(this, aNamespaceID, aName, aModType,
-        oldValue == &aParsedValue ? &aParsedValue : nullptr);
+        aParsedValue.StoresOwnData() ? &aParsedValue : nullptr);
   }
 
   if (aFireMutation) {
     InternalMutationEvent mutation(true, eLegacyAttrModified);
 
     nsAutoString ns;
     nsContentUtils::NameSpaceManager()->GetNameSpaceURI(aNamespaceID, ns);
     Attr* attrNode =
@@ -2570,17 +2598,17 @@ Element::SetAttrAndNotify(int32_t aNames
     mutation.mRelatedNode = attrNode;
 
     mutation.mAttrName = aName;
     nsAutoString newValue;
     GetAttr(aNamespaceID, aName, newValue);
     if (!newValue.IsEmpty()) {
       mutation.mNewAttrValue = NS_Atomize(newValue);
     }
-    if (!oldValue->IsEmptyString()) {
+    if (oldValue && !oldValue->IsEmptyString()) {
       mutation.mPrevAttrValue = oldValue->GetAsAtom();
     }
     mutation.mAttrChange = aModType;
 
     mozAutoSubtreeModified subtree(OwnerDoc(), this);
     (new AsyncEventDispatcher(this, mutation))->RunDOMEventWhenSafe();
   }
 
@@ -2613,19 +2641,20 @@ Element::ParseAttribute(int32_t aNamespa
       return true;
     }
   }
 
   return false;
 }
 
 bool
-Element::SetMappedAttribute(nsIAtom* aName,
-                            nsAttrValue& aValue,
-                            nsresult* aRetval)
+Element::SetAndSwapMappedAttribute(nsIAtom* aName,
+                                   nsAttrValue& aValue,
+                                   bool* aValueWasSet,
+                                   nsresult* aRetval)
 {
   *aRetval = NS_OK;
   return false;
 }
 
 EventListenerManager*
 Element::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
                                         bool* aDefer)
@@ -2770,17 +2799,17 @@ Element::UnsetAttr(int32_t aNameSpaceID,
       nsDependentAtomString(oldValueAtom),
       NullString()
     };
 
     nsContentUtils::EnqueueLifecycleCallback(
       ownerDoc, nsIDocument::eAttributeChanged, this, &args);
   }
 
-  rv = AfterSetAttr(aNameSpaceID, aName, nullptr, aNotify);
+  rv = AfterSetAttr(aNameSpaceID, aName, nullptr, &oldValue, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
   UpdateState(aNotify);
 
   if (aNotify) {
     // We can always pass oldValue here since there is no new value which could
     // have corrupted it.
     nsNodeUtils::AttributeChanged(this, aNameSpaceID, aName,
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -626,35 +626,59 @@ public:
   /**
    * Helper for SetAttr/SetParsedAttr. This method will return true if aNotify
    * is true or there are mutation listeners that must be triggered, the
    * attribute is currently set, and the new value that is about to be set is
    * different to the current value. As a perf optimization the new and old
    * values will not actually be compared if we aren't notifying and we don't
    * have mutation listeners (in which case it's cheap to just return false
    * and let the caller go ahead and set the value).
-   * @param aOldValue Set to the old value of the attribute, but only if there
-   *   are event listeners. If set, the type of aOldValue will be either
+   * @param aOldValue [out] Set to the old value of the attribute, but only if
+   *   there are event listeners. If set, the type of aOldValue will be either
    *   nsAttrValue::eString or nsAttrValue::eAtom.
-   * @param aModType Set to nsIDOMMutationEvent::MODIFICATION or to
+   * @param aModType [out] Set to nsIDOMMutationEvent::MODIFICATION or to
    *   nsIDOMMutationEvent::ADDITION, but only if this helper returns true
-   * @param aHasListeners Set to true if there are mutation event listeners
-   *   listening for NS_EVENT_BITS_MUTATION_ATTRMODIFIED
+   * @param aHasListeners [out] Set to true if there are mutation event
+   *   listeners listening for NS_EVENT_BITS_MUTATION_ATTRMODIFIED
+   * @param aOldValueSet [out] Indicates whether an old attribute value has been
+   *   stored in aOldValue. The bool will be set to true if a value was stored.
    */
   bool MaybeCheckSameAttrVal(int32_t aNamespaceID, nsIAtom* aName,
                              nsIAtom* aPrefix,
                              const nsAttrValueOrString& aValue,
                              bool aNotify, nsAttrValue& aOldValue,
-                             uint8_t* aModType, bool* aHasListeners);
+                             uint8_t* aModType, bool* aHasListeners,
+                             bool* aOldValueSet);
 
+  /**
+   * Notifies mutation listeners if aNotify is true, there are mutation
+   * listeners, and the attribute value is changing.
+   *
+   * @param aNamespaceID The namespace of the attribute
+   * @param aName The local name of the attribute
+   * @param aPrefix The prefix of the attribute
+   * @param aValue The value that the attribute is being changed to
+   * @param aNotify If true, mutation listeners will be notified if they exist
+   *   and the attribute value is changing
+   * @param aOldValue [out] Set to the old value of the attribute, but only if
+   *   there are event listeners. If set, the type of aOldValue will be either
+   *   nsAttrValue::eString or nsAttrValue::eAtom.
+   * @param aModType [out] Set to nsIDOMMutationEvent::MODIFICATION or to
+   *   nsIDOMMutationEvent::ADDITION, but only if this helper returns true
+   * @param aHasListeners [out] Set to true if there are mutation event
+   *   listeners listening for NS_EVENT_BITS_MUTATION_ATTRMODIFIED
+   * @param aOldValueSet [out] Indicates whether an old attribute value has been
+   *   stored in aOldValue. The bool will be set to true if a value was stored.
+   */
   bool OnlyNotifySameValueSet(int32_t aNamespaceID, nsIAtom* aName,
                               nsIAtom* aPrefix,
                               const nsAttrValueOrString& aValue,
                               bool aNotify, nsAttrValue& aOldValue,
-                              uint8_t* aModType, bool* aHasListeners);
+                              uint8_t* aModType, bool* aHasListeners,
+                              bool* aOldValueSet);
 
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsIAtom* aPrefix,
                            const nsAString& aValue, bool aNotify) override;
   // aParsedValue receives the old value of the attribute. That's useful if
   // either the input or output value of aParsedValue is StoresOwnData.
   nsresult SetParsedAttr(int32_t aNameSpaceID, nsIAtom* aName, nsIAtom* aPrefix,
                          nsAttrValue& aParsedValue, bool aNotify);
   // GetAttr is not inlined on purpose, to keep down codesize from all
@@ -1347,31 +1371,34 @@ protected:
    * @param aNamespaceID  namespace of attribute
    * @param aAttribute    local-name of attribute
    * @param aPrefix       aPrefix of attribute
    * @param aOldValue     The old value of the attribute to use as a fallback
    *                      in the cases where the actual old value (i.e.
    *                      its current value) is !StoresOwnData() --- in which
    *                      case the current value is probably already useless.
    *                      If the current value is StoresOwnData() (or absent),
-   *                      aOldValue will not be used.
+   *                      aOldValue will not be used. aOldValue will only be set
+   *                      in certain circumstances (there are mutation
+   *                      listeners, element is a custom element, attribute was
+   *                      not previously unset). Otherwise it will be null.
    * @param aParsedValue  parsed new value of attribute. Replaced by the
    *                      old value of the attribute. This old value is only
    *                      useful if either it or the new value is StoresOwnData.
    * @param aModType      nsIDOMMutationEvent::MODIFICATION or ADDITION.  Only
    *                      needed if aFireMutation or aNotify is true.
    * @param aFireMutation should mutation-events be fired?
    * @param aNotify       should we notify document-observers?
    * @param aCallAfterSetAttr should we call AfterSetAttr?
    * @param aComposedDocument The current composed document of the element.
    */
   nsresult SetAttrAndNotify(int32_t aNamespaceID,
                             nsIAtom* aName,
                             nsIAtom* aPrefix,
-                            const nsAttrValue& aOldValue,
+                            const nsAttrValue* aOldValue,
                             nsAttrValue& aParsedValue,
                             uint8_t aModType,
                             bool aFireMutation,
                             bool aNotify,
                             bool aCallAfterSetAttr,
                             nsIDocument* aComposedDocument,
                             const mozAutoDocUpdate& aGuard);
 
@@ -1405,23 +1432,30 @@ protected:
   /**
    * Try to set the attribute as a mapped attribute, if applicable.  This will
    * only be called for attributes that are in the null namespace and only on
    * attributes that returned true when passed to IsAttributeMapped.  The
    * caller will not try to set the attr in any other way if this method
    * returns true (the value of aRetval does not matter for that purpose).
    *
    * @param aName the name of the attribute
-   * @param aValue the nsAttrValue to set
+   * @param aValue the nsAttrValue to set. Will be swapped with the existing
+   *               value of the attribute if the attribute already exists.
+   * @param [out] aValueWasSet If the attribute was not set previously,
+   *                           aValue will be swapped with an empty attribute
+   *                           and aValueWasSet will be set to false. Otherwise,
+   *                           aValueWasSet will be set to true and aValue will
+   *                           contain the previous value set.
    * @param [out] aRetval the nsresult status of the operation, if any.
    * @return true if the setting was attempted, false otherwise.
    */
-  virtual bool SetMappedAttribute(nsIAtom* aName,
-                                  nsAttrValue& aValue,
-                                  nsresult* aRetval);
+  virtual bool SetAndSwapMappedAttribute(nsIAtom* aName,
+                                         nsAttrValue& aValue,
+                                         bool* aValueWasSet,
+                                         nsresult* aRetval);
 
   /**
    * Hook that is called by Element::SetAttr to allow subclasses to
    * deal with attribute sets.  This will only be called after we verify that
    * we're actually doing an attr set and will be called before
    * AttributeWillChange and before ParseAttribute and hence before we've set
    * the new value.
    *
@@ -1439,29 +1473,34 @@ protected:
                                  bool aNotify)
   {
     return NS_OK;
   }
 
   /**
    * Hook that is called by Element::SetAttr to allow subclasses to
    * deal with attribute sets.  This will only be called after we have called
-   * SetAndTakeAttr and AttributeChanged (that is, after we have actually set
-   * the attr).  It will always be called under a scriptblocker.
+   * SetAndSwapAttr (that is, after we have actually set the attr).  It will
+   * always be called under a scriptblocker.
    *
    * @param aNamespaceID the namespace of the attr being set
    * @param aName the localname of the attribute being set
    * @param aValue the value it's being set to.  If null, the attr is being
    *        removed.
+   * @param aOldValue the value that the attribute had previously. If null,
+   *        the attr was not previously set. This argument may not have the
+   *        correct value for SVG elements, or other cases in which the
+   *        attribute value doesn't store its own data
    * @param aNotify Whether we plan to notify document observers.
    */
   // Note that this is inlined so that when subclasses call it it gets
   // inlined.  Those calls don't go through a vtable.
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
   {
     return NS_OK;
   }
 
   /**
    * Hook to allow subclasses to produce a different EventListenerManager if
    * needed for attachment of attribute-defined handlers
    */
--- a/dom/base/nsAttrAndChildArray.cpp
+++ b/dom/base/nsAttrAndChildArray.cpp
@@ -384,22 +384,25 @@ nsAttrAndChildArray::AttrAt(uint32_t aPo
   if (aPos < nonmapped) {
     return &ATTRS(mImpl)[aPos].mValue;
   }
 
   return mImpl->mMappedAttrs->AttrAt(aPos - nonmapped);
 }
 
 nsresult
-nsAttrAndChildArray::SetAndSwapAttr(nsIAtom* aLocalName, nsAttrValue& aValue)
+nsAttrAndChildArray::SetAndSwapAttr(nsIAtom* aLocalName, nsAttrValue& aValue,
+                                    bool* aHadValue)
 {
+  *aHadValue = false;
   uint32_t i, slotCount = AttrSlotCount();
   for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
     if (ATTRS(mImpl)[i].mName.Equals(aLocalName)) {
       ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
+      *aHadValue = true;
       return NS_OK;
     }
   }
 
   NS_ENSURE_TRUE(i < ATTRCHILD_ARRAY_MAX_ATTR_COUNT,
                  NS_ERROR_FAILURE);
 
   if (i == slotCount && !AddAttrSlot()) {
@@ -409,31 +412,32 @@ nsAttrAndChildArray::SetAndSwapAttr(nsIA
   new (&ATTRS(mImpl)[i].mName) nsAttrName(aLocalName);
   new (&ATTRS(mImpl)[i].mValue) nsAttrValue();
   ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
 
   return NS_OK;
 }
 
 nsresult
-nsAttrAndChildArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue)
+nsAttrAndChildArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName,
+                                    nsAttrValue& aValue, bool* aHadValue)
 {
   int32_t namespaceID = aName->NamespaceID();
   nsIAtom* localName = aName->NameAtom();
   if (namespaceID == kNameSpaceID_None) {
-    return SetAndSwapAttr(localName, aValue);
+    return SetAndSwapAttr(localName, aValue, aHadValue);
   }
 
+  *aHadValue = false;
   uint32_t i, slotCount = AttrSlotCount();
   for (i = 0; i < slotCount && AttrSlotIsTaken(i); ++i) {
     if (ATTRS(mImpl)[i].mName.Equals(localName, namespaceID)) {
       ATTRS(mImpl)[i].mName.SetTo(aName);
-      ATTRS(mImpl)[i].mValue.Reset();
       ATTRS(mImpl)[i].mValue.SwapValueWith(aValue);
-
+      *aHadValue = true;
       return NS_OK;
     }
   }
 
   NS_ENSURE_TRUE(i < ATTRCHILD_ARRAY_MAX_ATTR_COUNT,
                  NS_ERROR_FAILURE);
 
   if (i == slotCount && !AddAttrSlot()) {
@@ -578,30 +582,31 @@ nsAttrAndChildArray::IndexOfAttr(nsIAtom
       }
     }
   }
 
   return -1;
 }
 
 nsresult
-nsAttrAndChildArray::SetAndTakeMappedAttr(nsIAtom* aLocalName,
+nsAttrAndChildArray::SetAndSwapMappedAttr(nsIAtom* aLocalName,
                                           nsAttrValue& aValue,
                                           nsMappedAttributeElement* aContent,
-                                          nsHTMLStyleSheet* aSheet)
+                                          nsHTMLStyleSheet* aSheet,
+                                          bool* aHadValue)
 {
   bool willAdd = true;
   if (mImpl && mImpl->mMappedAttrs) {
     willAdd = !mImpl->mMappedAttrs->GetAttr(aLocalName);
   }
 
   RefPtr<nsMappedAttributes> mapped =
     GetModifiableMapped(aContent, aSheet, willAdd);
 
-  mapped->SetAndTakeAttr(aLocalName, aValue);
+  mapped->SetAndSwapAttr(aLocalName, aValue, aHadValue);
 
   return MakeMappedUnique(mapped);
 }
 
 nsresult
 nsAttrAndChildArray::DoSetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet)
 {
   NS_PRECONDITION(mImpl && mImpl->mMappedAttrs,
--- a/dom/base/nsAttrAndChildArray.h
+++ b/dom/base/nsAttrAndChildArray.h
@@ -86,18 +86,23 @@ public:
   // kNameSpaceID_None.  This is always case-sensitive.
   const nsAttrValue* GetAttr(const nsAString& aName) const;
   // Get an nsAttrValue by qualified name.  Can optionally do
   // ASCII-case-insensitive name matching.
   const nsAttrValue* GetAttr(const nsAString& aName,
                              nsCaseTreatment aCaseSensitive) const;
   const nsAttrValue* AttrAt(uint32_t aPos) const;
   // SetAndSwapAttr swaps the current attribute value with aValue.
-  nsresult SetAndSwapAttr(nsIAtom* aLocalName, nsAttrValue& aValue);
-  nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue);
+  // If the attribute was unset, an empty value will be swapped into aValue
+  // and aHadValue will be set to false. Otherwise, aHadValue will be set to
+  // true.
+  nsresult SetAndSwapAttr(nsIAtom* aLocalName, nsAttrValue& aValue,
+                          bool* aHadValue);
+  nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue,
+                          bool* aHadValue);
 
   // Remove the attr at position aPos.  The value of the attr is placed in
   // aValue; any value that was already in aValue is destroyed.
   nsresult RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue);
 
   // Returns attribute name at given position, *not* out-of-bounds safe
   const nsAttrName* AttrNameAt(uint32_t aPos) const;
 
@@ -105,19 +110,24 @@ public:
   BorrowedAttrInfo AttrInfoAt(uint32_t aPos) const;
 
   // Returns attribute name at given position or null if aPos is out-of-bounds
   const nsAttrName* GetSafeAttrNameAt(uint32_t aPos) const;
 
   const nsAttrName* GetExistingAttrNameFromQName(const nsAString& aName) const;
   int32_t IndexOfAttr(nsIAtom* aLocalName, int32_t aNamespaceID = kNameSpaceID_None) const;
 
-  nsresult SetAndTakeMappedAttr(nsIAtom* aLocalName, nsAttrValue& aValue,
+  // SetAndSwapMappedAttr swaps the current attribute value with aValue.
+  // If the attribute was unset, an empty value will be swapped into aValue
+  // and aHadValue will be set to false. Otherwise, aHadValue will be set to
+  // true.
+  nsresult SetAndSwapMappedAttr(nsIAtom* aLocalName, nsAttrValue& aValue,
                                 nsMappedAttributeElement* aContent,
-                                nsHTMLStyleSheet* aSheet);
+                                nsHTMLStyleSheet* aSheet,
+                                bool* aHadValue);
   nsresult SetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet) {
     if (!mImpl || !mImpl->mMappedAttrs) {
       return NS_OK;
     }
     return DoSetMappedAttrStyleSheet(aSheet);
   }
   void WalkMappedAttributeStyleRules(nsRuleWalker* aRuleWalker);
 
--- a/dom/base/nsDOMCID.h
+++ b/dom/base/nsDOMCID.h
@@ -16,18 +16,11 @@
   0x9eb760f0, 0x4380, 0x11d2,                       \
  {0xb3, 0x28, 0x00, 0x80, 0x5f, 0x8a, 0x38, 0x59} }
 
 #define NS_SCRIPT_NAMESET_REGISTRY_CID               \
  { /* 45f27d10-987b-11d2-bd40-00105aa45e89 */        \
   0x45f27d10, 0x987b, 0x11d2,                        \
  {0xbd, 0x40, 0x00, 0x10, 0x5a, 0xa4, 0x5e, 0x89} }
 
-//The dom cannot provide the crypto or pkcs11 classes that
-//were used in older days, so if someone wants to provide
-//the service they must implement an object and give it
-//this class ID
-#define NS_CRYPTO_CONTRACTID "@mozilla.org/security/crypto;1"
-#define NS_PKCS11_CONTRACTID "@mozilla.org/security/pkcs11;1"
-
 #define NS_XPATH_EVALUATOR_CONTRACTID "@mozilla.org/dom/xpath-evaluator;1"
 
 #endif /* nsDOMCID_h__ */
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2802,16 +2802,17 @@ nsDOMWindowUtils::GetUnanimatedComputedS
   // nsComputedDOMStyle in this case.
   if (computedValue.GetUnit() == StyleAnimationValue::eUnit_DiscreteCSSValue &&
       (computedValue.GetCSSValueValue()->GetUnit() == eCSSUnit_Unset ||
        computedValue.GetCSSValueValue()->GetUnit() == eCSSUnit_Initial ||
        computedValue.GetCSSValueValue()->GetUnit() == eCSSUnit_Inherit)) {
     RefPtr<nsComputedDOMStyle> computedStyle =
       NS_NewComputedDOMStyle(
        element, aPseudoElement, shell,
+       nsComputedDOMStyle::StyleType::eAll,
        nsComputedDOMStyle::AnimationFlag::eWithoutAnimation);
     computedStyle->GetPropertyValue(propertyID, aResult);
     return NS_OK;
   }
 
   DebugOnly<bool> uncomputeResult =
     StyleAnimationValue::UncomputeValue(propertyID,
                                         Move(computedValue), aResult);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11057,23 +11057,56 @@ nsGlobalWindow::UpdateCanvasFocus(bool a
   }
 }
 
 already_AddRefed<nsICSSDeclaration>
 nsGlobalWindow::GetComputedStyle(Element& aElt, const nsAString& aPseudoElt,
                                  ErrorResult& aError)
 {
   MOZ_ASSERT(IsInnerWindow());
-  FORWARD_TO_OUTER_OR_THROW(GetComputedStyleOuter,
-                            (aElt, aPseudoElt), aError, nullptr);
+  return GetComputedStyleHelper(aElt, aPseudoElt, false, aError);
 }
 
 already_AddRefed<nsICSSDeclaration>
-nsGlobalWindow::GetComputedStyleOuter(Element& aElt,
-                                      const nsAString& aPseudoElt)
+nsGlobalWindow::GetDefaultComputedStyle(Element& aElt,
+                                        const nsAString& aPseudoElt,
+                                        ErrorResult& aError)
+{
+  MOZ_ASSERT(IsInnerWindow());
+  return GetComputedStyleHelper(aElt, aPseudoElt, true, aError);
+}
+
+nsresult
+nsGlobalWindow::GetComputedStyleHelper(nsIDOMElement* aElt,
+                                       const nsAString& aPseudoElt,
+                                       bool aDefaultStylesOnly,
+                                       nsIDOMCSSStyleDeclaration** aReturn)
+{
+  MOZ_ASSERT(IsInnerWindow());
+
+  NS_ENSURE_ARG_POINTER(aReturn);
+  *aReturn = nullptr;
+
+  nsCOMPtr<dom::Element> element = do_QueryInterface(aElt);
+  if (!element) {
+    return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+  }
+
+  ErrorResult rv;
+  nsCOMPtr<nsIDOMCSSStyleDeclaration> declaration =
+    GetComputedStyleHelper(*element, aPseudoElt, aDefaultStylesOnly, rv);
+  declaration.forget(aReturn);
+
+  return rv.StealNSResult();
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindow::GetComputedStyleHelperOuter(Element& aElt,
+                                            const nsAString& aPseudoElt,
+                                            bool aDefaultStylesOnly)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return nullptr;
   }
 
   nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell();
@@ -11095,21 +11128,34 @@ nsGlobalWindow::GetComputedStyleOuter(El
 
     presShell = mDocShell->GetPresShell();
     if (!presShell) {
       return nullptr;
     }
   }
 
   RefPtr<nsComputedDOMStyle> compStyle =
-    NS_NewComputedDOMStyle(&aElt, aPseudoElt, presShell);
+    NS_NewComputedDOMStyle(&aElt, aPseudoElt, presShell,
+                           aDefaultStylesOnly ? nsComputedDOMStyle::eDefaultOnly :
+                                                nsComputedDOMStyle::eAll);
 
   return compStyle.forget();
 }
 
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindow::GetComputedStyleHelper(Element& aElt,
+                                       const nsAString& aPseudoElt,
+                                       bool aDefaultStylesOnly,
+                                       ErrorResult& aError)
+{
+  FORWARD_TO_OUTER_OR_THROW(GetComputedStyleHelperOuter,
+                            (aElt, aPseudoElt, aDefaultStylesOnly),
+                            aError, nullptr);
+}
+
 Storage*
 nsGlobalWindow::GetSessionStorage(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   nsIPrincipal *principal = GetPrincipal();
   nsIDocShell* docShell = GetDocShell();
 
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1148,16 +1148,20 @@ public:
                                mozilla::ErrorResult& aError);
   void CancelIdleCallback(uint32_t aHandle);
 
 #ifdef MOZ_WEBSPEECH
   mozilla::dom::SpeechSynthesis*
     GetSpeechSynthesis(mozilla::ErrorResult& aError);
   bool HasActiveSpeechSynthesis();
 #endif
+  already_AddRefed<nsICSSDeclaration>
+    GetDefaultComputedStyle(mozilla::dom::Element& aElt,
+                            const nsAString& aPseudoElt,
+                            mozilla::ErrorResult& aError);
   void SizeToContentOuter(mozilla::dom::CallerType aCallerType,
                           mozilla::ErrorResult& aError);
   void SizeToContent(mozilla::dom::CallerType aCallerType,
                      mozilla::ErrorResult& aError);
   mozilla::dom::Crypto* GetCrypto(mozilla::ErrorResult& aError);
   mozilla::dom::U2F* GetU2f(mozilla::ErrorResult& aError);
   nsIControllers* GetControllersOuter(mozilla::ErrorResult& aError);
   nsIControllers* GetControllers(mozilla::ErrorResult& aError);
@@ -1726,19 +1730,30 @@ protected:
                     const RefPtr<mozilla::dom::StorageEvent>& aEvent,
                     mozilla::ErrorResult& aRv);
 
 public:
   // Outer windows only.
   nsDOMWindowList* GetWindowList();
 
 protected:
+  // Helper for getComputedStyle and getDefaultComputedStyle
   already_AddRefed<nsICSSDeclaration>
-    GetComputedStyleOuter(mozilla::dom::Element& aElt,
-                          const nsAString& aPseudoElt);
+    GetComputedStyleHelperOuter(mozilla::dom::Element& aElt,
+                                const nsAString& aPseudoElt,
+                                bool aDefaultStylesOnly);
+  already_AddRefed<nsICSSDeclaration>
+    GetComputedStyleHelper(mozilla::dom::Element& aElt,
+                           const nsAString& aPseudoElt,
+                           bool aDefaultStylesOnly,
+                           mozilla::ErrorResult& aError);
+  nsresult GetComputedStyleHelper(nsIDOMElement* aElt,
+                                  const nsAString& aPseudoElt,
+                                  bool aDefaultStylesOnly,
+                                  nsIDOMCSSStyleDeclaration** aReturn);
 
   // Outer windows only.
   void PreloadLocalStorage();
 
   // Returns CSS pixels based on primary screen.  Outer windows only.
   mozilla::CSSIntPoint GetScreenXY(mozilla::dom::CallerType aCallerType,
                                    mozilla::ErrorResult& aError);
 
--- a/dom/base/nsMappedAttributeElement.cpp
+++ b/dom/base/nsMappedAttributeElement.cpp
@@ -10,23 +10,24 @@
 nsresult
 nsMappedAttributeElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
 {
   mAttrsAndChildren.WalkMappedAttributeStyleRules(aRuleWalker);
   return NS_OK;
 }
 
 bool
-nsMappedAttributeElement::SetMappedAttribute(nsIAtom* aName,
-                                             nsAttrValue& aValue,
-                                             nsresult* aRetval)
+nsMappedAttributeElement::SetAndSwapMappedAttribute(nsIAtom* aName,
+                                                    nsAttrValue& aValue,
+                                                    bool* aValueWasSet,
+                                                    nsresult* aRetval)
 {
   nsHTMLStyleSheet* sheet = OwnerDoc()->GetAttributeStyleSheet();
-  *aRetval = mAttrsAndChildren.SetAndTakeMappedAttr(aName, aValue,
-                                                    this, sheet);
+  *aRetval = mAttrsAndChildren.SetAndSwapMappedAttr(aName, aValue,
+                                                    this, sheet, aValueWasSet);
   return true;
 }
 
 nsMapRuleToAttributesFunc
 nsMappedAttributeElement::GetAttributeMappingFunction() const
 {
   return &MapNoAttributesInto;
 }
--- a/dom/base/nsMappedAttributeElement.h
+++ b/dom/base/nsMappedAttributeElement.h
@@ -35,16 +35,17 @@ protected:
 
 public:
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
 
   static void MapNoAttributesInto(const nsMappedAttributes* aAttributes, 
                                   mozilla::GenericSpecifiedValues* aGenericData);
 
   NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker) override;
-  virtual bool SetMappedAttribute(nsIAtom* aName,
-                                  nsAttrValue& aValue,
-                                  nsresult* aRetval) override;
+  virtual bool SetAndSwapMappedAttribute(nsIAtom* aName,
+                                         nsAttrValue& aValue,
+                                         bool* aValueWasSet,
+                                         nsresult* aRetval) override;
 
   virtual void NodeInfoChanged(nsIDocument* aOldDoc) override;
 };
 
 #endif // NS_MAPPEDATTRIBUTEELEMENT_H_
--- a/dom/base/nsMappedAttributes.cpp
+++ b/dom/base/nsMappedAttributes.cpp
@@ -151,24 +151,26 @@ nsMappedAttributes::LastRelease()
 
 NS_IMPL_ADDREF(nsMappedAttributes)
 NS_IMPL_RELEASE_WITH_DESTROY(nsMappedAttributes, LastRelease())
 
 NS_IMPL_QUERY_INTERFACE(nsMappedAttributes,
                         nsIStyleRule)
 
 void
-nsMappedAttributes::SetAndTakeAttr(nsIAtom* aAttrName, nsAttrValue& aValue)
+nsMappedAttributes::SetAndSwapAttr(nsIAtom* aAttrName, nsAttrValue& aValue,
+                                   bool* aValueWasSet)
 {
   NS_PRECONDITION(aAttrName, "null name");
+  *aValueWasSet = false;
   uint32_t i;
   for (i = 0; i < mAttrCount && !Attrs()[i].mName.IsSmaller(aAttrName); ++i) {
     if (Attrs()[i].mName.Equals(aAttrName)) {
-      Attrs()[i].mValue.Reset();
       Attrs()[i].mValue.SwapValueWith(aValue);
+      *aValueWasSet = true;
       return;
     }
   }
 
   NS_ASSERTION(mBufferSize >= mAttrCount + 1, "can't fit attributes");
 
   if (mAttrCount != i) {
     memmove(&Attrs()[i + 1], &Attrs()[i], (mAttrCount - i) * sizeof(InternalAttr));
--- a/dom/base/nsMappedAttributes.h
+++ b/dom/base/nsMappedAttributes.h
@@ -29,17 +29,18 @@ public:
                      nsMapRuleToAttributesFunc aMapRuleFunc);
 
   // Do not return null.
   void* operator new(size_t size, uint32_t aAttrCount = 1) CPP_THROW_NEW;
   nsMappedAttributes* Clone(bool aWillAddAttr);
 
   NS_DECL_ISUPPORTS
 
-  void SetAndTakeAttr(nsIAtom* aAttrName, nsAttrValue& aValue);
+  void SetAndSwapAttr(nsIAtom* aAttrName, nsAttrValue& aValue,
+                      bool* aValueWasSet);
   const nsAttrValue* GetAttr(nsIAtom* aAttrName) const;
   const nsAttrValue* GetAttr(const nsAString& aAttrName) const;
 
   uint32_t Count() const
   {
     return mAttrCount;
   }
 
--- a/dom/base/nsStyledElement.cpp
+++ b/dom/base/nsStyledElement.cpp
@@ -52,16 +52,17 @@ nsStyledElement::ParseAttribute(int32_t 
 nsresult
 nsStyledElement::SetInlineStyleDeclaration(DeclarationBlock* aDeclaration,
                                            const nsAString* aSerialized,
                                            bool aNotify)
 {
   SetMayHaveStyle();
   bool modification = false;
   nsAttrValue oldValue;
+  bool oldValueSet = false;
 
   bool hasListeners = aNotify &&
     nsContentUtils::HasMutationListeners(this,
                                          NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
                                          this);
 
   // There's no point in comparing the stylerule pointers since we're always
   // getting a new stylerule here. And we can't compare the stringvalues of
@@ -69,35 +70,36 @@ nsStyledElement::SetInlineStyleDeclarati
   // and thus will be the same.
   if (hasListeners) {
     // save the old attribute so we can set up the mutation event properly
     nsAutoString oldValueStr;
     modification = GetAttr(kNameSpaceID_None, nsGkAtoms::style,
                            oldValueStr);
     if (modification) {
       oldValue.SetTo(oldValueStr);
+      oldValueSet = true;
     }
   }
   else if (aNotify && IsInUncomposedDoc()) {
     modification = !!mAttrsAndChildren.GetAttr(nsGkAtoms::style);
   }
 
   nsAttrValue attrValue(do_AddRef(aDeclaration), aSerialized);
 
   // XXXbz do we ever end up with ADDITION here?  I doubt it.
   uint8_t modType = modification ?
     static_cast<uint8_t>(nsIDOMMutationEvent::MODIFICATION) :
     static_cast<uint8_t>(nsIDOMMutationEvent::ADDITION);
 
   nsIDocument* document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
   return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::style, nullptr,
-                          oldValue, attrValue, modType, hasListeners,
-                          aNotify, kDontCallAfterSetAttr, document,
-                          updateBatch);
+                          oldValueSet ? &oldValue : nullptr, attrValue, modType,
+                          hasListeners, aNotify, kDontCallAfterSetAttr,
+                          document, updateBatch);
 }
 
 // ---------------------------------------------------------------
 // Others and helpers
 
 nsICSSDeclaration*
 nsStyledElement::Style()
 {
@@ -123,17 +125,19 @@ nsStyledElement::ReparseStyleAttribute(b
   const nsAttrValue* oldVal = mAttrsAndChildren.GetAttr(nsGkAtoms::style);
   if (oldVal && (aForceIfAlreadyParsed || oldVal->Type() != nsAttrValue::eCSSDeclaration)) {
     nsAttrValue attrValue;
     nsAutoString stringValue;
     oldVal->ToString(stringValue);
     ParseStyleAttribute(stringValue, attrValue, aForceInDataDoc);
     // Don't bother going through SetInlineStyleDeclaration; we don't
     // want to fire off mutation events or document notifications anyway
-    nsresult rv = mAttrsAndChildren.SetAndSwapAttr(nsGkAtoms::style, attrValue);
+    bool oldValueSet;
+    nsresult rv = mAttrsAndChildren.SetAndSwapAttr(nsGkAtoms::style, attrValue,
+                                                   &oldValueSet);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   
   return NS_OK;
 }
 
 void
 nsStyledElement::NodeInfoChanged(nsIDocument* aOldDoc)
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -212,26 +212,16 @@ DOMInterfaces = {
     'concrete': False,
     'nativeType': 'mozilla::css::GroupRule',
 },
 
 'CSSImportRule': {
     'nativeType': 'mozilla::css::ImportRule',
 },
 
-'CSSKeyframeRule': {
-    'nativeType': 'nsCSSKeyframeRule',
-    'headerFile': 'nsCSSRules.h',
-},
-
-'CSSKeyframesRule': {
-    'nativeType': 'nsCSSKeyframesRule',
-    'headerFile': 'nsCSSRules.h',
-},
-
 'CSSLexer': {
     'wrapperCache': False
 },
 
 'CSSPrimitiveValue': {
     'nativeType': 'nsROCSSPrimitiveValue',
 },
 
--- a/dom/html/HTMLBodyElement.cpp
+++ b/dom/html/HTMLBodyElement.cpp
@@ -437,20 +437,21 @@ HTMLBodyElement::BindToTree(nsIDocument*
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
   return mAttrsAndChildren.ForceMapped(this, OwnerDoc());
 }
 
 nsresult
 HTMLBodyElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                               const nsAttrValue* aValue,
-                              bool aNotify)
+                              const nsAttrValue* aOldValue, bool aNotify)
 {
   nsresult rv = nsGenericHTMLElement::AfterSetAttr(aNameSpaceID,
-                                                   aName, aValue, aNotify);
+                                                   aName, aValue, aOldValue,
+                                                   aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
   // if the last mapped attribute was removed, don't clear the
   // nsMappedAttributes, our style can still depend on the containing frame element
   if (!aValue && IsAttributeMapped(aName)) {
     nsresult rv = mAttrsAndChildren.ForceMapped(this, OwnerDoc());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
--- a/dom/html/HTMLBodyElement.h
+++ b/dom/html/HTMLBodyElement.h
@@ -114,17 +114,19 @@ public:
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   /**
    * Called when an attribute has just been changed
    */
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
 protected:
   virtual ~HTMLBodyElement();
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
 
 private:
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
--- a/dom/html/HTMLButtonElement.cpp
+++ b/dom/html/HTMLButtonElement.cpp
@@ -423,34 +423,36 @@ HTMLButtonElement::BeforeSetAttr(int32_t
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
                                                           aValue, aNotify);
 }
 
 nsresult
 HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::type) {
       if (aValue) {
         mType = aValue->GetEnumValue();
       } else {
         mType = kButtonDefaultType->value;
       }
     }
 
     if (aName == nsGkAtoms::type || aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
-                                                         aValue, aNotify);
+                                                         aValue, aOldValue,
+                                                         aNotify);
 }
 
 NS_IMETHODIMP
 HTMLButtonElement::SaveState()
 {
   if (!mDisabledChanged) {
     return NS_OK;
   }
--- a/dom/html/HTMLButtonElement.h
+++ b/dom/html/HTMLButtonElement.h
@@ -81,18 +81,20 @@ public:
    * Called when an attribute is about to be changed
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
-  nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                        const nsAttrValue* aValue, bool aNotify) override;
+  virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
                               const nsAString& aValue,
                               nsAttrValue& aResult) override;
 
   // nsGenericHTMLElement
   virtual bool IsHTMLFocusable(bool aWithMouse,
                                bool* aIsFocusable,
--- a/dom/html/HTMLFieldSetElement.cpp
+++ b/dom/html/HTMLFieldSetElement.cpp
@@ -78,34 +78,35 @@ HTMLFieldSetElement::GetEventTargetParen
     return NS_OK;
   }
 
   return nsGenericHTMLFormElement::GetEventTargetParent(aVisitor);
 }
 
 nsresult
 HTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                  const nsAttrValue* aValue, bool aNotify)
+                                  const nsAttrValue* aValue,
+                                  const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled &&
       nsINode::GetFirstChild()) {
     if (!mElements) {
       mElements = new nsContentList(this, MatchListedElements, nullptr, nullptr,
                                     true);
     }
 
     uint32_t length = mElements->Length(true);
     for (uint32_t i=0; i<length; ++i) {
       static_cast<nsGenericHTMLFormElement*>(mElements->Item(i))
         ->FieldSetDisabledChanged(aNotify);
     }
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
-                                                aValue, aNotify);
+                                                aValue, aOldValue, aNotify);
 }
 
 // nsIDOMHTMLFieldSetElement
 
 NS_IMETHODIMP
 HTMLFieldSetElement::GetForm(nsIDOMHTMLFormElement** aForm)
 {
   return nsGenericHTMLFormElement::GetForm(aForm);
--- a/dom/html/HTMLFieldSetElement.h
+++ b/dom/html/HTMLFieldSetElement.h
@@ -38,17 +38,19 @@ public:
 
   // nsIDOMHTMLFieldSetElement
   NS_DECL_NSIDOMHTMLFIELDSETELEMENT
 
   // nsIContent
   virtual nsresult GetEventTargetParent(
                      EventChainPreVisitor& aVisitor) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   virtual nsresult InsertChildAt(nsIContent* aChild, uint32_t aIndex,
                                      bool aNotify) override;
   virtual void RemoveChildAt(uint32_t aIndex, bool aNotify) override;
 
   // nsIFormControl
   NS_IMETHOD Reset() override;
   NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -210,33 +210,35 @@ HTMLFormElement::SetAttr(int32_t aNameSp
     mNotifiedObservers = notifiedObservers;
   }
   return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                        aNotify);
 }
 
 nsresult
 HTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                              const nsAttrValue* aValue, bool aNotify)
+                              const nsAttrValue* aValue,
+                              const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aName == nsGkAtoms::novalidate && aNameSpaceID == kNameSpaceID_None) {
     // Update all form elements states because they might be [no longer]
     // affected by :-moz-ui-valid or :-moz-ui-invalid.
     for (uint32_t i = 0, length = mControls->mElements.Length();
          i < length; ++i) {
       mControls->mElements[i]->UpdateState(true);
     }
 
     for (uint32_t i = 0, length = mControls->mNotInElements.Length();
          i < length; ++i) {
       mControls->mNotInElements[i]->UpdateState(true);
     }
   }
 
-  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aNotify);
+  return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
+                                            aOldValue, aNotify);
 }
 
 NS_IMPL_STRING_ATTR(HTMLFormElement, AcceptCharset, acceptcharset)
 NS_IMPL_ACTION_ATTR(HTMLFormElement, Action, action)
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Autocomplete, autocomplete,
                                 kFormDefaultAutocomplete->tag)
 NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLFormElement, Enctype, enctype,
                                 kFormDefaultEnctype->tag)
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -109,17 +109,19 @@ public:
                    const nsAString& aValue, bool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   /**
    * Forget all information about the current submission (and the fact that we
    * are currently submitting at all).
    */
   void ForgetCurrentSubmission();
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
--- a/dom/html/HTMLIFrameElement.cpp
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -169,27 +169,27 @@ HTMLIFrameElement::SetAttr(int32_t aName
   }
 
   return NS_OK;
 }
 
 nsresult
 HTMLIFrameElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
-                                bool aNotify)
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aName == nsGkAtoms::sandbox &&
       aNameSpaceID == kNameSpaceID_None && mFrameLoader) {
     // If we have an nsFrameLoader, apply the new sandbox flags.
     // Since this is called after the setter, the sandbox flags have
     // alreay been updated.
     mFrameLoader->ApplySandboxFlags(GetSandboxFlags());
   }
   return nsGenericHTMLFrameElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                                 aNotify);
+                                                 aOldValue, aNotify);
 }
 
 nsresult
 HTMLIFrameElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify)
 {
   // Invoke on the superclass.
   nsresult rv = nsGenericHTMLFrameElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify);
--- a/dom/html/HTMLIFrameElement.h
+++ b/dom/html/HTMLIFrameElement.h
@@ -52,16 +52,17 @@ public:
   {
     return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify);
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
                                 bool aNotify) override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify) override;
 
   uint32_t GetSandboxFlags();
 
   // Web IDL binding methods
   // The XPCOM GetSrc is fine for our purposes
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -391,17 +391,18 @@ HTMLImageElement::BeforeSetAttr(int32_t 
   }
 
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
                                              aValue, aNotify);
 }
 
 nsresult
 HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                               const nsAttrValue* aValue, bool aNotify)
+                               const nsAttrValue* aValue,
+                               const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None && mForm &&
       (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
       aValue && !aValue->IsEmptyString()) {
     // add the image to the hashtable as needed
     MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
                "Expected atom value for name/id");
     mForm->AddImageElementToTable(this,
@@ -445,17 +446,17 @@ HTMLImageElement::AfterSetAttr(int32_t a
     // Mark channel as urgent-start before load image if the image load is
     // initaiated by a user interaction.
     mUseUrgentStartForChannel = EventStateManager::IsHandlingUserInput();
 
     PictureSourceSizesChanged(this, attrVal.String(), aNotify);
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
-                                            aValue, aNotify);
+                                            aValue, aOldValue, aNotify);
 }
 
 nsresult
 HTMLImageElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   // We handle image element with attribute ismap in its corresponding frame
   // element. Set mMultipleActionsPrevented here to prevent the click event
   // trigger the behaviors in Element::PostHandleEventForLinks
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -344,17 +344,19 @@ protected:
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
   void UpdateFormOwner();
 
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   // Override for nsImageLoadingContent.
   nsIContent* AsContent() override { return this; }
 
   // This is a weak reference that this element and the HTMLFormElement
   // cooperate in maintaining.
   HTMLFormElement* mForm;
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -1393,17 +1393,18 @@ HTMLInputElement::BeforeSetAttr(int32_t 
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
                                                           aValue, aNotify);
 }
 
 nsresult
 HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                               const nsAttrValue* aValue, bool aNotify)
+                               const nsAttrValue* aValue,
+                               const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     //
     // When name or type changes, radio should be added to radio group.
     // (type changes are handled in the form itself currently)
     // If we are not done creating the radio, we also should not do it.
     //
     if ((aName == nsGkAtoms::name ||
@@ -1516,17 +1517,18 @@ HTMLInputElement::AfterSetAttr(int32_t a
     } else if (aName == nsGkAtoms::autocomplete) {
       // Clear the cached @autocomplete attribute and autocompleteInfo state.
       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
       mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
-                                                         aValue, aNotify);
+                                                         aValue, aOldValue,
+                                                         aNotify);
 }
 
 void
 HTMLInputElement::BeforeSetForm(bool aBindToTree)
 {
   // No need to remove from radio group if we are just binding to tree.
   if (mType == NS_FORM_INPUT_RADIO && !aBindToTree) {
     WillRemoveFromRadioGroup();
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -970,17 +970,19 @@ protected:
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   virtual void BeforeSetForm(bool aBindToTree) override;
 
   virtual void AfterClearForm(bool aUnbindOrDelete) override;
 
   /**
    * Dispatch a select event. Returns true if the event was not cancelled.
    */
--- a/dom/html/HTMLLinkElement.cpp
+++ b/dom/html/HTMLLinkElement.cpp
@@ -341,17 +341,18 @@ HTMLLinkElement::BeforeSetAttr(int32_t a
   }
 
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
                                              aValue, aNotify);
 }
 
 nsresult
 HTMLLinkElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                              const nsAttrValue* aValue, bool aNotify)
+                              const nsAttrValue* aValue,
+                              const nsAttrValue* aOldValue, bool aNotify)
 {
   // It's safe to call ResetLinkState here because our new attr value has
   // already been set or unset.  ResetLinkState needs the updated attribute
   // value because notifying the document that content states have changed will
   // call IntrinsicState, which will try to get updated information about the
   // visitedness from Link.
   if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
     bool hasHref = aValue;
@@ -410,17 +411,17 @@ HTMLLinkElement::AfterSetAttr(int32_t aN
       if (aName == nsGkAtoms::href ||
           aName == nsGkAtoms::rel) {
         UpdateImport();
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 nsresult
 HTMLLinkElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   return GetEventTargetParentForAnchors(aVisitor);
 }
 
--- a/dom/html/HTMLLinkElement.h
+++ b/dom/html/HTMLLinkElement.h
@@ -62,16 +62,17 @@ public:
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
                                 bool aNotify) override;
   virtual bool IsLink(nsIURI** aURI) const override;
   virtual already_AddRefed<nsIURI> GetHrefURI() const override;
 
   // Element
   virtual bool ParseAttribute(int32_t aNamespaceID,
                               nsIAtom* aAttribute,
                               const nsAString& aValue,
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4230,17 +4230,18 @@ nsresult HTMLMediaElement::UnsetAttr(int
     }
   }
 
   return rv;
 }
 
 nsresult
 HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
     mSrcMediaSource = nullptr;
     if (aValue) {
       nsString srcStr = aValue->GetStringValue();
       nsCOMPtr<nsIURI> uri;
       NewURIFromString(srcStr, getter_AddRefs(uri));
       if (uri && IsMediaSourceURI(uri)) {
@@ -4252,17 +4253,17 @@ HTMLMediaElement::AfterSetAttr(int32_t a
           const char16_t* params[] = { spec.get() };
           ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
         }
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
-                                            aValue, aNotify);
+                                            aValue, aOldValue, aNotify);
 }
 
 nsresult HTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                       nsIContent* aBindingParent,
                                       bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
                                                  aParent,
@@ -4607,17 +4608,16 @@ nsresult HTMLMediaElement::InitializeDec
 
   return FinishDecoderSetup(decoder, resource, nullptr);
 }
 
 nsresult HTMLMediaElement::InitializeDecoderForChannel(nsIChannel* aChannel,
                                                        nsIStreamListener** aListener)
 {
   NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
-  NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
 
   nsAutoCString mimeType;
 
   aChannel->GetContentType(mimeType);
   NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
 
   DecoderDoctorDiagnostics diagnostics;
   RefPtr<MediaDecoder> decoder =
@@ -5275,20 +5275,16 @@ void HTMLMediaElement::DecodeError(const
   ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
 
   DecoderDoctorDiagnostics diagnostics;
   diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
 
   AudioTracks()->EmptyTracks();
   VideoTracks()->EmptyTracks();
   if (mIsLoadingFromSourceChildren) {
-    if (mDecoder) {
-      // Shut down the exiting decoder before loading the next source child.
-      ShutdownDecoder();
-    }
     mErrorSink->ResetError();
     if (mSourceLoadCandidate) {
       DispatchAsyncSourceError(mSourceLoadCandidate);
       QueueLoadFromSourceTask();
     } else {
       NS_WARNING("Should know the source we were loading from!");
     }
   } else if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -135,16 +135,17 @@ public:
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttr,
                              bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
                                 bool aNotify) override;
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   virtual void DoneCreatingElement() override;
--- a/dom/html/HTMLMenuElement.cpp
+++ b/dom/html/HTMLMenuElement.cpp
@@ -110,28 +110,29 @@ HTMLMenuElement::Build(nsIMenuBuilder* a
     return;
   }
 
   BuildSubmenu(EmptyString(), this, aBuilder);
 }
 
 nsresult
 HTMLMenuElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                              const nsAttrValue* aValue, bool aNotify)
+                              const nsAttrValue* aValue,
+                              const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::type) {
     if (aValue) {
       mType = aValue->GetEnumValue();
     } else {
       mType = kMenuDefaultType->value;
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 bool
 HTMLMenuElement::ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult)
 {
--- a/dom/html/HTMLMenuElement.h
+++ b/dom/html/HTMLMenuElement.h
@@ -25,18 +25,20 @@ public:
   NS_IMPL_FROMCONTENT_HTML_WITH_TAG(HTMLMenuElement, menu)
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMHTMLMenuElement
   NS_DECL_NSIDOMHTMLMENUELEMENT
 
-  nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                        const nsAttrValue* aValue, bool aNotify) override;
+  virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
   virtual bool ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
--- a/dom/html/HTMLMenuItemElement.cpp
+++ b/dom/html/HTMLMenuItemElement.cpp
@@ -369,17 +369,18 @@ HTMLMenuItemElement::GetText(nsAString& 
   nsContentUtils::GetNodeTextContent(this, false, text);
 
   text.CompressWhitespace(true, true);
   aText = text;
 }
 
 nsresult
 HTMLMenuItemElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                  const nsAttrValue* aValue, bool aNotify)
+                                  const nsAttrValue* aValue,
+                                  const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     // Handle type changes first, since some of the later conditions in this
     // method look at mType and want to see the new value.
     if (aName == nsGkAtoms::type) {
       if (aValue) {
         mType = aValue->GetEnumValue();
       } else {
@@ -403,17 +404,17 @@ HTMLMenuItemElement::AfterSetAttr(int32_
         mShouldInitChecked = true;
       } else {
         InitChecked();
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 void
 HTMLMenuItemElement::WalkRadioGroup(Visitor* aVisitor)
 {
   nsIContent* parent = GetParent();
   if (!parent) {
     aVisitor->Visit(this);
--- a/dom/html/HTMLMenuItemElement.h
+++ b/dom/html/HTMLMenuItemElement.h
@@ -121,17 +121,19 @@ public:
 protected:
   virtual ~HTMLMenuItemElement();
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
 
 
 protected:
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   void WalkRadioGroup(Visitor* aVisitor);
 
   HTMLMenuItemElement* GetSelectedRadio();
 
   void AddedToRadioGroup();
 
   void InitChecked();
--- a/dom/html/HTMLMetaElement.cpp
+++ b/dom/html/HTMLMetaElement.cpp
@@ -55,17 +55,18 @@ HTMLMetaElement::SetMetaReferrer(nsIDocu
       content = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(content);
       aDocument->SetHeaderData(nsGkAtoms::referrer, content);
   }
   return NS_OK;
 }
 
 nsresult
 HTMLMetaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                              const nsAttrValue* aValue, bool aNotify)
+                              const nsAttrValue* aValue,
+                              const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     nsIDocument *document = GetUncomposedDoc();
     if (aName == nsGkAtoms::content) {
       if (document && AttrValueIs(kNameSpaceID_None, nsGkAtoms::name,
                                   nsGkAtoms::viewport, eIgnoreCase)) {
         nsAutoString content;
         nsresult rv = GetContent(content);
@@ -77,17 +78,17 @@ HTMLMetaElement::AfterSetAttr(int32_t aN
     // Update referrer policy when it got changed from JS
     nsresult rv = SetMetaReferrer(document);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 nsresult
 HTMLMetaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                             nsIContent* aBindingParent,
                             bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
--- a/dom/html/HTMLMetaElement.h
+++ b/dom/html/HTMLMetaElement.h
@@ -28,17 +28,19 @@ public:
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   void CreateAndDispatchEvent(nsIDocument* aDoc, const nsAString& aEventName);
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   // XPCOM GetName is fine.
   void SetName(const nsAString& aName, ErrorResult& aRv)
--- a/dom/html/HTMLOptGroupElement.cpp
+++ b/dom/html/HTMLOptGroupElement.cpp
@@ -97,32 +97,33 @@ HTMLOptGroupElement::RemoveChildAt(uint3
 {
   SafeOptionListMutation safeMutation(GetSelect(), this, nullptr, aIndex,
                                       aNotify);
   nsGenericHTMLElement::RemoveChildAt(aIndex, aNotify);
 }
 
 nsresult
 HTMLOptGroupElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                  const nsAttrValue* aValue, bool aNotify)
+                                  const nsAttrValue* aValue,
+                                  const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled) {
     // All our children <option> have their :disabled state depending on our
     // disabled attribute. We should make sure their state is updated.
     for (nsIContent* child = nsINode::GetFirstChild(); child;
          child = child->GetNextSibling()) {
       if (child->IsHTMLElement(nsGkAtoms::option)) {
         // No need to call |IsElement()| because it's an HTML element.
         child->AsElement()->UpdateState(true);
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 EventStates
 HTMLOptGroupElement::IntrinsicState() const
 {
   EventStates state = nsGenericHTMLElement::IntrinsicState();
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
--- a/dom/html/HTMLOptGroupElement.h
+++ b/dom/html/HTMLOptGroupElement.h
@@ -39,17 +39,19 @@ public:
                      EventChainPreVisitor& aVisitor) override;
 
   virtual EventStates IntrinsicState() const override;
  
   virtual nsresult Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult,
                          bool aPreallocateChildren) const override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   virtual nsIDOMNode* AsDOMNode() override { return this; }
 
   virtual bool IsDisabled() const override {
     return HasAttr(kNameSpaceID_None, nsGkAtoms::disabled);
   }
 
   bool Disabled() const
--- a/dom/html/HTMLOptionElement.cpp
+++ b/dom/html/HTMLOptionElement.cpp
@@ -239,31 +239,32 @@ HTMLOptionElement::BeforeSetAttr(int32_t
   // more than once; make sure our mSelectedChanged state is set correctly.
   mSelectedChanged = mIsSelected != defaultSelected;
 
   return NS_OK;
 }
 
 nsresult
 HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None &&
       aName == nsGkAtoms::value && Selected()) {
     // Since this option is selected, changing value
     // may have changed missing validity state of the
     // Select element
     HTMLSelectElement* select = GetSelect();
     if (select) {
       select->UpdateValueMissingValidityState();
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
-                                            aValue, aNotify);
+                                            aValue, aOldValue, aNotify);
 }
 
 NS_IMETHODIMP
 HTMLOptionElement::GetText(nsAString& aText)
 {
   nsAutoString text;
 
   nsIContent* child = nsINode::GetFirstChild();
--- a/dom/html/HTMLOptionElement.h
+++ b/dom/html/HTMLOptionElement.h
@@ -46,17 +46,19 @@ public:
 
   virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
                                               int32_t aModType) const override;
 
   virtual nsresult BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   void SetSelectedInternal(bool aValue, bool aNotify);
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
--- a/dom/html/HTMLScriptElement.cpp
+++ b/dom/html/HTMLScriptElement.cpp
@@ -229,23 +229,24 @@ HTMLScriptElement::NoModule()
 void
 HTMLScriptElement::SetNoModule(bool aValue, ErrorResult& aRv)
 {
   SetHTMLBoolAttr(nsGkAtoms::nomodule, aValue, aRv);
 }
 
 nsresult
 HTMLScriptElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   if (nsGkAtoms::async == aName && kNameSpaceID_None == aNamespaceID) {
     mForceAsync = false;
   }
   return nsGenericHTMLElement::AfterSetAttr(aNamespaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 NS_IMETHODIMP
 HTMLScriptElement::GetInnerHTML(nsAString& aInnerHTML)
 {
   if (!nsContentUtils::GetNodeTextContent(this, false, aInnerHTML, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/html/HTMLScriptElement.h
+++ b/dom/html/HTMLScriptElement.h
@@ -53,17 +53,19 @@ public:
                               const nsAString& aValue,
                               nsAttrValue& aResult) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   // Element
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   // WebIDL
   void SetText(const nsAString& aValue, ErrorResult& rv);
   void SetCharset(const nsAString& aCharset, ErrorResult& rv);
   void SetDefer(bool aDefer, ErrorResult& rv);
   bool Defer();
   void SetSrc(const nsAString& aSrc, ErrorResult& rv);
   void SetType(const nsAString& aType, ErrorResult& rv);
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -1307,32 +1307,34 @@ HTMLSelectElement::BeforeSetAttr(int32_t
   }
 
   return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
                                                           aValue, aNotify);
 }
 
 nsresult
 HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::disabled) {
       UpdateBarredFromConstraintValidation();
     } else if (aName == nsGkAtoms::required) {
       UpdateValueMissingValidityState();
     } else if (aName == nsGkAtoms::autocomplete) {
       // Clear the cached @autocomplete attribute and autocompleteInfo state.
       mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
       mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
-                                                         aValue, aNotify);
+                                                         aValue, aOldValue,
+                                                         aNotify);
 }
 
 nsresult
 HTMLSelectElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify)
 {
   if (aNotify && aNameSpaceID == kNameSpaceID_None &&
       aAttribute == nsGkAtoms::multiple) {
--- a/dom/html/HTMLSelectElement.h
+++ b/dom/html/HTMLSelectElement.h
@@ -379,17 +379,19 @@ public:
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                nsIContent* aBindingParent,
                                bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify) override;
 
   virtual void DoneAddingChildren(bool aHaveNotified) override;
   virtual bool IsDoneAddingChildren() override {
     return mIsDoneAddingChildren;
   }
 
--- a/dom/html/HTMLSourceElement.cpp
+++ b/dom/html/HTMLSourceElement.cpp
@@ -90,17 +90,18 @@ HTMLSourceElement::UpdateMediaList(const
   }
 
   nsCSSParser cssParser;
   mMediaList = MediaList::Create(OwnerDoc()->GetStyleBackendType(), mediaStr);
 }
 
 nsresult
 HTMLSourceElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify)
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue, bool aNotify)
 {
   // If we are associated with a <picture> with a valid <img>, notify it of
   // responsive parameter changes
   Element *parent = nsINode::GetParentElement();
   if (aNameSpaceID == kNameSpaceID_None &&
       (aName == nsGkAtoms::srcset ||
        aName == nsGkAtoms::sizes ||
        aName == nsGkAtoms::media ||
@@ -135,17 +136,17 @@ HTMLSourceElement::AfterSetAttr(int32_t 
       NewURIFromString(srcStr, getter_AddRefs(uri));
       if (uri && IsMediaSourceURI(uri)) {
         NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource));
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
-                                            aValue, aNotify);
+                                            aValue, aOldValue, aNotify);
 }
 
 nsresult
 HTMLSourceElement::BindToTree(nsIDocument *aDocument,
                               nsIContent *aParent,
                               nsIContent *aBindingParent,
                               bool aCompileEventHandlers)
 {
--- a/dom/html/HTMLSourceElement.h
+++ b/dom/html/HTMLSourceElement.h
@@ -107,16 +107,17 @@ public:
 protected:
   virtual ~HTMLSourceElement();
 
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
 protected:
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
                                 bool aNotify) override;
 
 private:
   RefPtr<MediaList> mMediaList;
   RefPtr<MediaSource> mSrcMediaSource;
 
   // Generates a new MediaList using the given input
   void UpdateMediaList(const nsAttrValue* aValue);
--- a/dom/html/HTMLStyleElement.cpp
+++ b/dom/html/HTMLStyleElement.cpp
@@ -166,31 +166,32 @@ HTMLStyleElement::UnbindFromTree(bool aD
     return;
   }
 
   UpdateStyleSheetInternal(oldDoc, oldShadow);
 }
 
 nsresult
 HTMLStyleElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                               const nsAttrValue* aValue, bool aNotify)
+                               const nsAttrValue* aValue,
+                               const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::title ||
         aName == nsGkAtoms::media ||
         aName == nsGkAtoms::type) {
       UpdateStyleSheetInternal(nullptr, nullptr, true);
     } else if (aName == nsGkAtoms::scoped) {
       bool isScoped = aValue;
       UpdateStyleSheetScopedness(isScoped);
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 NS_IMETHODIMP
 HTMLStyleElement::GetInnerHTML(nsAString& aInnerHTML)
 {
   if (!nsContentUtils::GetNodeTextContent(this, false, aInnerHTML, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/dom/html/HTMLStyleElement.h
+++ b/dom/html/HTMLStyleElement.h
@@ -43,16 +43,17 @@ public:
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) override;
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
                                 bool aNotify) override;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                          bool aPreallocateChildren) const override;
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
--- a/dom/html/HTMLTableElement.cpp
+++ b/dom/html/HTMLTableElement.cpp
@@ -884,17 +884,19 @@ HTMLTableElement::BuildInheritedAttribut
   if (sheet) {
     const nsAttrValue* value = mAttrsAndChildren.GetAttr(nsGkAtoms::cellpadding);
     if (value) {
       RefPtr<nsMappedAttributes> modifiableMapped = new
       nsMappedAttributes(sheet, MapInheritedTableAttributesIntoRule);
 
       if (modifiableMapped) {
         nsAttrValue val(*value);
-        modifiableMapped->SetAndTakeAttr(nsGkAtoms::cellpadding, val);
+        bool oldValueSet;
+        modifiableMapped->SetAndSwapAttr(nsGkAtoms::cellpadding, val,
+                                         &oldValueSet);
       }
       newAttrs = sheet->UniqueMappedAttributes(modifiableMapped);
       NS_ASSERTION(newAttrs, "out of memory, but handling gracefully");
 
       if (newAttrs != modifiableMapped) {
         // Reset the stylesheet of modifiableMapped so that it doesn't
         // spend time trying to remove itself from the hash.  There is no
         // risk that modifiableMapped is in the hash since we created
@@ -945,19 +947,19 @@ HTMLTableElement::BeforeSetAttr(int32_t 
   }
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue,
                                              aNotify);
 }
 
 nsresult
 HTMLTableElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                const nsAttrValue* aValue,
-                               bool aNotify)
+                               const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aName == nsGkAtoms::cellpadding && aNameSpaceID == kNameSpaceID_None) {
     BuildInheritedAttributes();
   }
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLTableElement.h
+++ b/dom/html/HTMLTableElement.h
@@ -202,17 +202,19 @@ public:
    */
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
   /**
    * Called when an attribute has just been changed
    */
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLTableElement,
                                            nsGenericHTMLElement)
   nsMappedAttributes* GetAttributesMappedForCell();
 
 protected:
   virtual ~HTMLTableElement();
 
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -1094,17 +1094,18 @@ HTMLTextAreaElement::ContentChanged(nsIC
     // further auditing.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
     Reset();
   }
 }
 
 nsresult
 HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                  const nsAttrValue* aValue, bool aNotify)
+                                  const nsAttrValue* aValue,
+                                  const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
         aName == nsGkAtoms::readonly) {
       UpdateValueMissingValidityState();
 
       // This *has* to be called *after* validity has changed.
       if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
@@ -1113,17 +1114,17 @@ HTMLTextAreaElement::AfterSetAttr(int32_
     } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
     } else if (aName == nsGkAtoms::minlength) {
       UpdateTooShortValidityState();
     }
   }
 
   return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                                         aNotify);
+                                                         aOldValue, aNotify);
   }
 
 nsresult
 HTMLTextAreaElement::CopyInnerTo(Element* aDest, bool aPreallocateChildren)
 {
   nsresult rv = nsGenericHTMLFormElementWithState::CopyInnerTo(aDest,
                                                                aPreallocateChildren);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/html/HTMLTextAreaElement.h
+++ b/dom/html/HTMLTextAreaElement.h
@@ -360,17 +360,19 @@ protected:
   /**
    * Common method to call from the various mutation observer methods.
    * aContent is a content node that's either the one that changed or its
    * parent; we should only respond to the change if aContent is non-anonymous.
    */
   void ContentChanged(nsIContent* aContent);
 
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom *aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   /**
    * Return if an element should have a specific validity UI
    * (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
    *
    * @return Whether the element should have a validity UI.
    */
   bool ShouldShowValidityUI() const {
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -640,17 +640,18 @@ nsGenericHTMLElement::GetHrefURIForAncho
   nsCOMPtr<nsIURI> uri;
   GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri));
 
   return uri.forget();
 }
 
 nsresult
 nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                   const nsAttrValue* aValue, bool aNotify)
+                                   const nsAttrValue* aValue,
+                                   const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (IsEventAttributeName(aName) && aValue) {
       MOZ_ASSERT(aValue->Type() == nsAttrValue::eString,
                  "Expected string value for script body");
       nsresult rv = SetEventHandler(aName, aValue->GetStringValue());
       NS_ENSURE_SUCCESS(rv, rv);
     }
@@ -681,17 +682,17 @@ nsGenericHTMLElement::AfterSetAttr(int32
           dir = RecomputeDirectionality(this, aNotify);
         }
       }
       SetDirectionalityOnDescendants(this, dir, aNotify);
     }
   }
 
   return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName,
-                                                aValue, aNotify);
+                                                aValue, aOldValue, aNotify);
 }
 
 EventListenerManager*
 nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
                                                      bool* aDefer)
 {
   // Attributes on the body and frameset tags get set on the global object
   if ((mNodeInfo->Equals(nsGkAtoms::body) ||
@@ -1964,17 +1965,18 @@ nsGenericHTMLFormElement::BeforeSetAttr(
   }
 
   return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName,
                                              aValue, aNotify);
 }
 
 nsresult
 nsGenericHTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                       const nsAttrValue* aValue, bool aNotify)
+                                       const nsAttrValue* aValue,
+                                       const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     // add the control to the hashtable as needed
 
     if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) &&
         aValue && !aValue->IsEmptyString()) {
       MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom,
                  "Expected atom value for name/id");
@@ -2013,17 +2015,17 @@ nsGenericHTMLFormElement::AfterSetAttr(i
         // Because we have a new @form value (or no more @form), we have to
         // update our form owner.
         UpdateFormOwner(false, formIdElement);
       }
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
-                                            aValue, aNotify);
+                                            aValue, aOldValue, aNotify);
 }
 
 nsresult
 nsGenericHTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
 {
   if (aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage == eFocus ||
                                        aVisitor.mEvent->mMessage == eBlur)) {
     // We have to handle focus/blur event to change focus states in
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -938,17 +938,19 @@ protected:
     }
   }
 
 private:
   void RegUnRegAccessKey(bool aDoReg);
 
 protected:
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   virtual mozilla::EventListenerManager*
     GetEventListenerManagerForAttr(nsIAtom* aAttrName,
                                    bool* aDefer) override;
 
   /**
    * Dispatch a simulated mouse click by keyboard to the given element.
    */
@@ -1296,17 +1298,19 @@ public:
 protected:
   virtual ~nsGenericHTMLFormElement();
 
   virtual nsresult BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                  const nsAttrValueOrString* aValue,
                                  bool aNotify) override;
 
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
-                                const nsAttrValue* aValue, bool aNotify) override;
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                bool aNotify) override;
 
   virtual void BeforeSetForm(bool aBindToTree) {}
 
   virtual void AfterClearForm(bool aUnbindOrDelete) {}
 
   void SetForm(mozilla::dom::HTMLFormElement* aForm, bool aBindToTree);
 
   /**
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -381,17 +381,17 @@ PrincipalAllowsBrowserFrame(nsIPrincipal
   nsresult rv = permMgr->TestPermissionFromPrincipal(aPrincipal, "browser", &permission);
   NS_ENSURE_SUCCESS(rv, false);
   return permission == nsIPermissionManager::ALLOW_ACTION;
 }
 
 /* virtual */ nsresult
 nsGenericHTMLFrameElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                         const nsAttrValue* aValue,
-                                        bool aNotify)
+                                        const nsAttrValue* aOldValue, bool aNotify)
 {
   if (aName == nsGkAtoms::scrolling && aNameSpaceID == kNameSpaceID_None) {
     if (mFrameLoader) {
       nsIDocShell* docshell = mFrameLoader->GetExistingDocShell();
       nsCOMPtr<nsIScrollable> scrollable = do_QueryInterface(docshell);
       if (scrollable) {
         int32_t cur;
         scrollable->GetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X, &cur);
@@ -413,17 +413,17 @@ nsGenericHTMLFrameElement::AfterSetAttr(
   }
 
   if (aName == nsGkAtoms::mozbrowser && aNameSpaceID == kNameSpaceID_None) {
     mReallyIsBrowser = !!aValue && BrowserFramesEnabled() &&
                        PrincipalAllowsBrowserFrame(NodePrincipal());
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
-                                            aNotify);
+                                            aOldValue, aNotify);
 }
 
 void
 nsGenericHTMLFrameElement::DestroyContent()
 {
   if (mFrameLoader) {
     mFrameLoader->Destroy();
     mFrameLoader = nullptr;
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -60,16 +60,17 @@ public:
   }
   virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            bool aNotify) override;
   virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
                              bool aNotify) override;
   virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
                                 bool aNotify) override;
   virtual void DestroyContent() override;
 
   nsresult CopyInnerTo(mozilla::dom::Element* aDest, bool aPreallocateChildren);
 
   virtual int32_t TabIndexDefault() override;
 
   virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() override { return this; }
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/.eslintrc.js
@@ -0,0 +1,17 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/recommended",
+    "plugin:mozilla/browser-test",
+    "plugin:mozilla/chrome-test",
+    "plugin:mozilla/mochitest-test",
+  ],
+  "plugins": [
+    "mozilla"
+  ],
+  "rules": {
+    "brace-style": "off",
+    "no-shadow": "off",
+  }
+};
--- a/dom/indexedDB/test/blob_worker_crash_iframe.html
+++ b/dom/indexedDB/test/blob_worker_crash_iframe.html
@@ -33,36 +33,37 @@ dump("EXCEPTION IN CREATION: " + e + "\n
         report(false);
       }
     }
 
     function createAndStoreBlob() {
       const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
       var blob = new Blob(BLOB_DATA, { type: "text/plain" });
       var objectStore = db.transaction("foo", "readwrite").objectStore("foo");
-      objectStore.add({ blob: blob }, 42).onsuccess = refetchBlob;
+      objectStore.add({ blob }, 42).onsuccess = refetchBlob;
     }
 
     function refetchBlob() {
       var foo = db.transaction("foo").objectStore("foo");
       foo.get(42).onsuccess = fetchedBlobCreateWorkerAndSendBlob;
     }
 
     function fetchedBlobCreateWorkerAndSendBlob(event) {
       var idbBlob = event.target.result.blob;
-      var compositeBlob = new Blob(['I like the following blob: ', idbBlob],
+      var compositeBlob = new Blob(["I like the following blob: ", idbBlob],
                                    { type: "text/fancy" });
 
       function workerScript() {
+        /* eslint-env worker */
         onmessage = function(event) {
           // Save the Blob to the worker's global scope.
           self.holdOntoBlob = event.data;
           // Send any message so we can serialize and keep our runtime behaviour
           // consistent.
-          postMessage('kung fu death grip established');
+          postMessage("kung fu death grip established");
         }
       }
 
       var url =
         URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
 
       // Keep a reference to the worker on the window.
       var worker = window.worker = new Worker(url);
@@ -78,17 +79,17 @@ dump("EXCEPTION IN CREATION: " + e + "\n
       var objectStore = db.transaction("foo", "readwrite").objectStore("foo");
       objectStore.delete(42).onsuccess = closeDBTellOwningThread;
     }
 
     function closeDBTellOwningThread(event) {
       // Now that worker has latched the blob, clean up the database.
       db.close();
       db = null;
-      report('ready');
+      report("ready");
     }
 
     createDatastore();
   }
   </script>
 
 </head>
 
--- a/dom/indexedDB/test/browserHelpers.js
+++ b/dom/indexedDB/test/browserHelpers.js
@@ -1,13 +1,16 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+// testSteps is expected to be defined by the file including this file.
+/* global testSteps */
+
 var testGenerator = testSteps();
 
 var testResult;
 var testException;
 
 function runTest()
 {
   testGenerator.next();
--- a/dom/indexedDB/test/browser_forgetThisSite.js
+++ b/dom/indexedDB/test/browser_forgetThisSite.js
@@ -27,33 +27,33 @@ function test()
   setPermission(testPageURL2, "indexedDB");
   executeSoon(test1);
 }
 
 function test1()
 {
   // Set database version for domain 1
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(result, exception) {
       ok(result == 11, "Set version on database in " + testPageURL1);
       ok(!exception, "No exception");
       gBrowser.removeCurrentTab();
 
       executeSoon(test2);
     });
   }, {capture: true, once: true});
   content.location = testPageURL1;
 }
 
 function test2()
 {
   // Set database version for domain 2
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(result, exception) {
       ok(result == 11, "Set version on database in " + testPageURL2);
       ok(!exception, "No exception");
       gBrowser.removeCurrentTab();
 
       executeSoon(test3);
     });
   }, {capture: true, once: true});
@@ -68,33 +68,33 @@ function test3()
     executeSoon(test4);
   });
 }
 
 function test4()
 {
   // Get database version for domain 1
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(result, exception) {
       ok(result == 11, "Got correct version on database in " + testPageURL3);
       ok(!exception, "No exception");
       gBrowser.removeCurrentTab();
 
       executeSoon(test5);
     });
   }, {capture: true, once: true});
   content.location = testPageURL3;
 }
 
 function test5()
 {
   // Get database version for domain 2
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(result, exception) {
       ok(result == 1, "Got correct version on database in " + testPageURL4);
       ok(!exception, "No exception");
       gBrowser.removeCurrentTab();
 
       executeSoon(finish);
     });
   }, {capture: true, once: true});
--- a/dom/indexedDB/test/browser_forgetThisSiteAdd.html
+++ b/dom/indexedDB/test/browser_forgetThisSiteAdd.html
@@ -2,16 +2,17 @@
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
   <head>
     <title>Indexed Database Test</title>
 
     <script type="text/javascript">
+      /* import-globals-from browserHelpers.js */
       function* testSteps()
       {
         let request = indexedDB.open("browser_forgetThisSite.js", 11);
         request.onerror = grabEventAndContinueHandler;
         request.onupgradeneeded = grabEventAndContinueHandler;
         let event = yield undefined;
 
         if (event.type == "error") {
--- a/dom/indexedDB/test/browser_forgetThisSiteGet.html
+++ b/dom/indexedDB/test/browser_forgetThisSiteGet.html
@@ -2,16 +2,17 @@
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
   <head>
     <title>Indexed Database Test</title>
 
     <script type="text/javascript">
+      /* import-globals-from browserHelpers.js */
       function* testSteps()
       {
         let request = indexedDB.open("browser_forgetThisSite.js");
         request.onerror = grabEventAndContinueHandler;
         request.onsuccess = grabEventAndContinueHandler;
         let event = yield undefined;
 
         if (event.type == "error") {
--- a/dom/indexedDB/test/browser_permissionsPrompt.html
+++ b/dom/indexedDB/test/browser_permissionsPrompt.html
@@ -3,16 +3,17 @@
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
   <head>
     <meta charset=UTF-8>
     <title>Indexed Database Test</title>
 
     <script type="text/javascript">
+      /* import-globals-from browserHelpers.js */
       function* testSteps()
       {
         const name = window.location.pathname;
 
         let request = indexedDB.open(name, { version: 1,
                                              storage: "persistent" });
         request.onerror = grabEventAndContinueHandler;
         request.onsuccess = grabEventAndContinueHandler;
--- a/dom/indexedDB/test/browser_permissionsPromptAllow.js
+++ b/dom/indexedDB/test/browser_permissionsPromptAllow.js
@@ -16,71 +16,71 @@ function test()
   executeSoon(test1);
 }
 
 function test1()
 {
   info("creating tab");
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(isIDBDatabase, exception) {
       ok(isIDBDatabase,
          "First database creation was successful");
       ok(!exception, "No exception");
       is(getPermission(testPageURL, "indexedDB"),
          Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
          "Correct permission set");
       gBrowser.removeCurrentTab();
       executeSoon(test2);
     });
 
-    registerPopupEventHandler("popupshowing", function () {
+    registerPopupEventHandler("popupshowing", function() {
       ok(true, "prompt showing");
     });
-    registerPopupEventHandler("popupshown", function () {
+    registerPopupEventHandler("popupshown", function() {
       ok(true, "prompt shown");
       triggerMainCommand(this);
     });
-    registerPopupEventHandler("popuphidden", function () {
+    registerPopupEventHandler("popuphidden", function() {
       ok(true, "prompt hidden");
     });
 
   }, {capture: true, once: true});
 
   info("loading test page: " + testPageURL);
   content.location = testPageURL;
 }
 
 function test2()
 {
   info("creating tab");
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(isIDBDatabase, exception) {
       ok(isIDBDatabase,
          "First database creation was successful");
       ok(!exception, "No exception");
       is(getPermission(testPageURL, "indexedDB"),
          Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
          "Correct permission set");
       gBrowser.removeCurrentTab();
       unregisterAllPopupEventHandlers();
       removePermission(testPageURL, "indexedDB");
       executeSoon(finish);
     });
 
-    registerPopupEventHandler("popupshowing", function () {
+    registerPopupEventHandler("popupshowing", function() {
       ok(false, "Shouldn't show a popup this time");
     });
-    registerPopupEventHandler("popupshown", function () {
+    registerPopupEventHandler("popupshown", function() {
       ok(false, "Shouldn't show a popup this time");
     });
-    registerPopupEventHandler("popuphidden", function () {
+    registerPopupEventHandler("popuphidden", function() {
       ok(false, "Shouldn't show a popup this time");
     });
 
   }, {capture: true, once: true});
 
   info("loading test page: " + testPageURL);
   content.location = testPageURL;
 }
--- a/dom/indexedDB/test/browser_permissionsPromptDeny.js
+++ b/dom/indexedDB/test/browser_permissionsPromptDeny.js
@@ -26,53 +26,53 @@ add_task(function* test1() {
 
   info("creating tab");
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
   info("loading test page: " + testPageURL);
   gBrowser.selectedBrowser.loadURI(testPageURL);
   yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
-  registerPopupEventHandler("popupshowing", function () {
+  registerPopupEventHandler("popupshowing", function() {
     ok(true, "prompt showing");
   });
-  registerPopupEventHandler("popupshown", function () {
+  registerPopupEventHandler("popupshown", function() {
     ok(true, "prompt shown");
     triggerSecondaryCommand(this);
   });
-  registerPopupEventHandler("popuphidden", function () {
+  registerPopupEventHandler("popuphidden", function() {
     ok(true, "prompt hidden");
   });
 
   yield promiseMessage("InvalidStateError", gBrowser);
 
   is(getPermission(testPageURL, "indexedDB"),
      Components.interfaces.nsIPermissionManager.DENY_ACTION,
      "Correct permission set");
   gBrowser.removeCurrentTab();
 });
 
 add_task(function* test2() {
   info("creating private window");
-  let win = yield BrowserTestUtils.openNewBrowserWindow({ private : true });
-  
+  let win = yield BrowserTestUtils.openNewBrowserWindow({ private: true });
+
   info("creating private tab");
   win.gBrowser.selectedTab = win.gBrowser.addTab();
 
   info("loading test page: " + testPageURL);
   win.gBrowser.selectedBrowser.loadURI(testPageURL);
   yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
-  
-  registerPopupEventHandler("popupshowing", function () {
+
+  registerPopupEventHandler("popupshowing", function() {
     ok(false, "prompt showing");
   });
-  registerPopupEventHandler("popupshown", function () {
+  registerPopupEventHandler("popupshown", function() {
     ok(false, "prompt shown");
   });
-  registerPopupEventHandler("popuphidden", function () {
+  registerPopupEventHandler("popuphidden", function() {
     ok(false, "prompt hidden");
   });
   yield promiseMessage("InvalidStateError", win.gBrowser);
 
   is(getPermission(testPageURL, "indexedDB"),
      Components.interfaces.nsIPermissionManager.DENY_ACTION,
      "Correct permission set");
   unregisterAllPopupEventHandlers();
@@ -83,23 +83,23 @@ add_task(function* test2() {
 add_task(function* test3() {
   info("creating tab");
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
   info("loading test page: " + testPageURL);
   gBrowser.selectedBrowser.loadURI(testPageURL);
   yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
-  registerPopupEventHandler("popupshowing", function () {
+  registerPopupEventHandler("popupshowing", function() {
     ok(false, "Shouldn't show a popup this time");
   });
-  registerPopupEventHandler("popupshown", function () {
+  registerPopupEventHandler("popupshown", function() {
     ok(false, "Shouldn't show a popup this time");
   });
-  registerPopupEventHandler("popuphidden", function () {
+  registerPopupEventHandler("popuphidden", function() {
     ok(false, "Shouldn't show a popup this time");
   });
 
   yield promiseMessage("InvalidStateError", gBrowser);
 
   is(getPermission(testPageURL, "indexedDB"),
      Components.interfaces.nsIPermissionManager.DENY_ACTION,
      "Correct permission set");
--- a/dom/indexedDB/test/browser_permissionsPromptWorker.js
+++ b/dom/indexedDB/test/browser_permissionsPromptWorker.js
@@ -18,35 +18,35 @@ function test()
 function test1()
 {
   // We want a prompt.
   removePermission(testWorkerURL, "indexedDB");
 
   info("creating tab");
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(isIDBDatabase, exception) {
       ok(isIDBDatabase, "First database creation was successful");
       ok(!exception, "No exception");
       is(getPermission(testWorkerURL, "indexedDB"),
          Components.interfaces.nsIPermissionManager.ALLOW_ACTION,
          "Correct permission set");
       gBrowser.removeCurrentTab();
       executeSoon(test2);
     });
 
-    registerPopupEventHandler("popupshowing", function () {
+    registerPopupEventHandler("popupshowing", function() {
       ok(true, "prompt showing");
     });
-    registerPopupEventHandler("popupshown", function () {
+    registerPopupEventHandler("popupshown", function() {
       ok(true, "prompt shown");
       triggerMainCommand(this);
     });
-    registerPopupEventHandler("popuphidden", function () {
+    registerPopupEventHandler("popuphidden", function() {
       ok(true, "prompt hidden");
     });
 
   }, {capture: true, once: true});
 
   info("loading test page: " + testWorkerURL);
   content.location = testWorkerURL;
 }
@@ -54,34 +54,34 @@ function test1()
 function test2()
 {
   // We want a prompt.
   removePermission(testSharedWorkerURL, "indexedDB");
 
   info("creating tab");
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
 
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     setFinishedCallback(function(isIDBDatabase, exception) {
       ok(!isIDBDatabase, "First database creation was successful");
       ok(exception, "No exception");
       is(getPermission(testSharedWorkerURL, "indexedDB"),
          Components.interfaces.nsIPermissionManager.UNKNOWN_ACTION,
          "Correct permission set");
       gBrowser.removeCurrentTab();
       executeSoon(finish);
     });
 
-    registerPopupEventHandler("popupshowing", function () {
+    registerPopupEventHandler("popupshowing", function() {
       ok(false, "prompt showing");
     });
-    registerPopupEventHandler("popupshown", function () {
+    registerPopupEventHandler("popupshown", function() {
       ok(false, "prompt shown");
     });
-    registerPopupEventHandler("popuphidden", function () {
+    registerPopupEventHandler("popuphidden", function() {
       ok(false, "prompt hidden");
     });
 
   }, {capture: true, once: true});
 
   info("loading test page: " + testSharedWorkerURL);
   content.location = testSharedWorkerURL;
 }
--- a/dom/indexedDB/test/browser_permissionsSharedWorker.html
+++ b/dom/indexedDB/test/browser_permissionsSharedWorker.html
@@ -2,23 +2,25 @@
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
   <head>
     <title>Indexed Database Test</title>
 
     <script type="text/javascript">
+    // testFinishedCallback is expected to be set in the scope by head.js.
+    /* global testFinishedCallback */
     let testIsIDBDatabase;
     let testException;
 
     function runTest() {
-      let w = new SharedWorker('browser_permissionsSharedWorker.js');
+      let w = new SharedWorker("browser_permissionsSharedWorker.js");
       w.port.onmessage = function(e) {
-        if (e.data.status == 'success') {
+        if (e.data.status == "success") {
           testIsIDBDatabase = e.data.isIDBDatabase;
         } else {
           testException = e.data.error;
         }
 
         setTimeout(testFinishedCallback, 0, testIsIDBDatabase, testException);
       }
 
--- a/dom/indexedDB/test/browser_permissionsSharedWorker.js
+++ b/dom/indexedDB/test/browser_permissionsSharedWorker.js
@@ -1,14 +1,16 @@
+/* eslint-env worker */
+
 onconnect = function(e) {
   e.ports[0].onmessage = function(e) {
     var request = indexedDB.open(e.data, { version: 1,
                                            storage: "persistent" });
     request.onsuccess = function(event) {
-      e.target.postMessage({ status: 'success',
+      e.target.postMessage({ status: "success",
                              isIDBDatabase: (event.target.result instanceof IDBDatabase) });
     }
 
     request.onerror = function(event) {
-      e.target.postMessage({ status: 'error', error: event.target.error.name });
+      e.target.postMessage({ status: "error", error: event.target.error.name });
     }
   }
 }
--- a/dom/indexedDB/test/browser_permissionsWorker.html
+++ b/dom/indexedDB/test/browser_permissionsWorker.html
@@ -2,23 +2,25 @@
   Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
   <head>
     <title>Indexed Database Test</title>
 
     <script type="text/javascript">
+    // testFinishedCallback is expected to be set in the scope by head.js.
+    /* global testFinishedCallback */
     let testIsIDBDatabase;
     let testException;
 
     function runTest() {
-      let w = new Worker('browser_permissionsWorker.js');
+      let w = new Worker("browser_permissionsWorker.js");
       w.onmessage = function(e) {
-        if (e.data.status == 'success') {
+        if (e.data.status == "success") {
           testIsIDBDatabase = e.data.isIDBDatabase;
         } else {
           testException = e.data.error;
         }
 
         setTimeout(testFinishedCallback, 0, testIsIDBDatabase, testException);
       }
 
--- a/dom/indexedDB/test/browser_permissionsWorker.js
+++ b/dom/indexedDB/test/browser_permissionsWorker.js
@@ -1,12 +1,14 @@
+/* eslint-env worker */
+
 onmessage = function(e) {
   var request = indexedDB.open(e.data, { version: 1,
                                          storage: "persistent" });
   request.onsuccess = function(event) {
-    postMessage({ status: 'success',
+    postMessage({ status: "success",
                   isIDBDatabase: (event.target.result instanceof IDBDatabase) });
   }
 
   request.onerror = function(event) {
-    postMessage({ status: 'error', error: event.target.error.name });
+    postMessage({ status: "error", error: event.target.error.name });
   }
 }
--- a/dom/indexedDB/test/browser_perwindow_privateBrowsing.js
+++ b/dom/indexedDB/test/browser_perwindow_privateBrowsing.js
@@ -13,17 +13,17 @@ function test()
   // Avoids the actual prompt
   setPermission(testPageURL, "indexedDB");
   executeSoon(test1);
 }
 
 function test1()
 {
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
-  gBrowser.selectedBrowser.addEventListener("load", function () {
+  gBrowser.selectedBrowser.addEventListener("load", function() {
     if (content.location != testPageURL) {
       content.location = testPageURL;
       return;
     }
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
 
     setFinishedCallback(function(isIDBDatabase, exception) {
       ok(isIDBDatabase,
@@ -44,17 +44,17 @@ function test2()
     executeSoon(() => test3(win));
   }, {once: true});
   registerCleanupFunction(() => win.close());
 }
 
 function test3(win)
 {
   win.gBrowser.selectedTab = win.gBrowser.addTab();
-  win.gBrowser.selectedBrowser.addEventListener("load", function () {
+  win.gBrowser.selectedBrowser.addEventListener("load", function() {
     if (win.content.location != testPageURL) {
       win.content.location = testPageURL;
       return;
     }
     win.gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
 
     setFinishedCallback(function(isIDBDatabase, exception) {
       ok(!isIDBDatabase, "No database");
--- a/dom/indexedDB/test/chromeHelpers.js
+++ b/dom/indexedDB/test/chromeHelpers.js
@@ -1,14 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-var { 'classes': Cc, 'interfaces': Ci, 'utils': Cu } = Components;
+var { "classes": Cc, "interfaces": Ci, "utils": Cu } = Components;
+
+// testSteps is expected to be defined by the file including this file.
+/* global testSteps */
 
 var testGenerator = testSteps();
 
 if (!window.runTest) {
   window.runTest = function()
   {
     SimpleTest.waitForExplicitFinish();
 
--- a/dom/indexedDB/test/exceptions_in_events_iframe.html
+++ b/dom/indexedDB/test/exceptions_in_events_iframe.html
@@ -53,17 +53,17 @@
     function* testSteps() {
       window.parent.SpecialPowers.addPermission("indexedDB", true, document);
 
       // Test 1: Throwing an exception in an upgradeneeded handler should
       // abort the versionchange transaction and fire an error at the request.
       let request = indexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onsuccess = unexpectedSuccessHandler;
-      request.onupgradeneeded = function () {
+      request.onupgradeneeded = function() {
         let transaction = request.transaction;
         transaction.oncomplete = unexpectedSuccessHandler;
         transaction.onabort = grabEventAndContinueHandler
         throw "STOP";
       };
 
       let event = yield undefined;
       is(event.type, "abort",
@@ -148,17 +148,17 @@
 
       is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
       ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
 
       request = objectStore.add({}, 1);
       request.onerror = errorHandler;
       request = objectStore.add({}, 1);
       request.onsuccess = unexpectedSuccessHandler;
-      request.onerror = function (event) {
+      request.onerror = function(event) {
         event.preventDefault();
         throw "STOP";
       };
 
       event = yield undefined;
 
       is(event.type, "abort", "Got transaction abort event");
       is(event.target.error.name, "AbortError", "Got AbortError object");
--- a/dom/indexedDB/test/extensions/bootstrap.js
+++ b/dom/indexedDB/test/extensions/bootstrap.js
@@ -20,17 +20,17 @@ function testForExpectedSymbols(stage, d
 function GlobalObjectsComponent() {
   this.wrappedJSObject = this;
 }
 
 GlobalObjectsComponent.prototype =
 {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
 
-  runTest: function() {
+  runTest() {
     const name = "Splendid Test";
 
     let ok = this.ok;
     let finishTest = this.finishTest;
 
     let keyRange = IDBKeyRange.only(42);
     ok(keyRange, "Got keyRange");
 
@@ -43,17 +43,17 @@ GlobalObjectsComponent.prototype =
       let db = event.target.result;
       ok(db, "Got database");
       finishTest();
     }
   }
 };
 
 var gFactory = {
-  register: function() {
+  register() {
     var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 
     var classID = Components.ID("{d6f85dcb-537d-447e-b783-75d4b405622d}");
     var description = "IndexedDBTest";
     var contractID = "@mozilla.org/dom/indexeddb/GlobalObjectsComponent;1";
     var factory = XPCOMUtils._getFactory(GlobalObjectsComponent);
 
     registrar.registerFactory(classID, description, contractID, factory);
--- a/dom/indexedDB/test/file.js
+++ b/dom/indexedDB/test/file.js
@@ -1,13 +1,15 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from helpers.js */
+
 var bufferCache = [];
 var utils = SpecialPowers.getDOMWindowUtils(window);
 
 function getBuffer(size)
 {
   let buffer = new ArrayBuffer(size);
   is(buffer.byteLength, size, "Correct byte length");
   return buffer;
@@ -52,22 +54,22 @@ function compareBuffers(buffer1, buffer2
       return false;
     }
   }
   return true;
 }
 
 function getBlob(type, view)
 {
-  return new Blob([view], {type: type});
+  return new Blob([view], {type});
 }
 
 function getFile(name, type, view)
 {
-  return new File([view], name, {type: type});
+  return new File([view], name, {type});
 }
 
 function getRandomBlob(size)
 {
   return getBlob("binary/random", getRandomView(size));
 }
 
 function getRandomFile(name, size)
--- a/dom/indexedDB/test/head.js
+++ b/dom/indexedDB/test/head.js
@@ -1,17 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var gActiveListeners = {};
 
 function registerPopupEventHandler(eventName, callback) {
-  gActiveListeners[eventName] = function (event) {
+  gActiveListeners[eventName] = function(event) {
     if (event.target != PopupNotifications.panel)
       return;
     PopupNotifications.panel.removeEventListener(eventName,
                                                  gActiveListeners[eventName]);
     delete gActiveListeners[eventName];
 
     callback.call(PopupNotifications.panel);
   }
@@ -53,17 +53,17 @@ function triggerSecondaryCommand(popup)
   ok(notifications.length > 0, "at least one notification displayed");
   let notification = notifications[0];
   EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
 }
 
 function dismissNotification(popup)
 {
   info("dismissing notification");
-  executeSoon(function () {
+  executeSoon(function() {
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   });
 }
 
 function setFinishedCallback(callback, win)
 {
   if (!win) {
     win = window;
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -1,36 +1,41 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+// testSteps is expected to be defined by the test using this file.
+/* global testSteps:false */
+
 var testGenerator = testSteps();
 // The test js is shared between xpcshell (which has no SpecialPowers object)
 // and content mochitests (where the |Components| object is accessible only as
 // SpecialPowers.Components). Expose Components if necessary here to make things
 // work everywhere.
 //
 // Even if the real |Components| doesn't exist, we might shim in a simple JS
 // placebo for compat. An easy way to differentiate this from the real thing
 // is whether the property is read-only or not.
-var c = Object.getOwnPropertyDescriptor(this, 'Components');
-if ((!c.value || c.writable) && typeof SpecialPowers === 'object')
+var c = Object.getOwnPropertyDescriptor(this, "Components");
+if ((!c.value || c.writable) && typeof SpecialPowers === "object") {
+  // eslint-disable-next-line no-native-reassign
   Components = SpecialPowers.Components;
+}
 
 function executeSoon(aFun)
 {
   let comp = SpecialPowers.wrap(Components);
 
   let thread = comp.classes["@mozilla.org/thread-manager;1"]
                    .getService(comp.interfaces.nsIThreadManager)
                    .mainThread;
 
   thread.dispatch({
-    run: function() {
+    run() {
       aFun();
     }
   }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
 }
 
 function clearAllDatabases(callback) {
   let qms = SpecialPowers.Services.qms;
   let principal = SpecialPowers.wrap(document).nodePrincipal;
@@ -149,17 +154,17 @@ function* testHarnessSteps() {
           nextTestHarnessStep();
           break;
 
         case "expectUncaughtException":
           worker._expectingUncaughtException = message.expecting;
           break;
 
         case "clearAllDatabases":
-          clearAllDatabases(function(){
+          clearAllDatabases(function() {
             worker.postMessage({ op: "clearAllDatabasesDone" });
           });
           break;
 
         case "getWasmBinary":
           worker.postMessage({ op: "getWasmBinaryDone",
                                wasmBinary: getWasmBinarySync(message.text) });
           break;
@@ -183,17 +188,17 @@ function* testHarnessSteps() {
     worker.terminate();
     worker = null;
 
     clearAllDatabases(nextTestHarnessStep);
     yield undefined;
   } else if (testScriptFilename) {
     todo(false,
          "Skipping test in a worker because it is explicitly disabled: " +
-         disableWorkerTest);
+         window.disableWorkerTest);
   } else {
     todo(false,
          "Skipping test in a worker because it's not structured properly");
   }
 
   info("Running test in main thread");
 
   // Now run the test script in the main thread.
@@ -288,17 +293,17 @@ function expectedErrorHandler(name)
 }
 
 function ExpectError(name, preventDefault)
 {
   this._name = name;
   this._preventDefault = preventDefault;
 }
 ExpectError.prototype = {
-  handleEvent: function(event)
+  handleEvent(event)
   {
     is(event.type, "error", "Got an error event");
     is(event.target.error.name, this._name, "Expected error was thrown.");
     if (this._preventDefault) {
       event.preventDefault();
       event.stopPropagation();
     }
     grabEventAndContinueHandler(event);
@@ -476,17 +481,17 @@ function workerScript() {
   };
 
   self.ExpectError = function(_name_, _preventDefault_)
   {
     this._name = _name_;
     this._preventDefault = _preventDefault_;
   }
   self.ExpectError.prototype = {
-    handleEvent: function(_event_)
+    handleEvent(_event_)
     {
       is(_event_.type, "error", "Got an error event");
       is(_event_.target.error.name, this._name, "Expected error was thrown.");
       if (this._preventDefault) {
         _event_.preventDefault();
         _event_.stopPropagation();
       }
       grabEventAndContinueHandler(_event_);
@@ -544,17 +549,17 @@ function workerScript() {
     self.postMessage({ op: "clearAllDatabases" });
   }
 
   self.onerror = function(_message_, _file_, _line_) {
     if (self._expectingUncaughtException) {
       self._expectingUncaughtException = false;
       ok(true, "Worker: expected exception [" + _file_ + ":" + _line_ + "]: '" +
          _message_ + "'");
-      return;
+      return false;
     }
     ok(false,
        "Worker: uncaught exception [" + _file_ + ":" + _line_ + "]: '" +
          _message_ + "'");
     self.finishTest();
     self.close();
     return true;
   };
--- a/dom/indexedDB/test/leaving_page_iframe.html
+++ b/dom/indexedDB/test/leaving_page_iframe.html
@@ -1,17 +1,16 @@
 <!DOCTYPE html>
 <html>
 <head>
   <script>
 var db;
 function startDBWork() {
   indexedDB.open(parent.location, 1).onupgradeneeded = function(e) {
     db = e.target.result;
-    var trans = e.target.transaction;
     if (db.objectStoreNames.contains("mystore")) {
       db.deleteObjectStore("mystore");
     }
     var store = db.createObjectStore("mystore");
     store.add({ hello: "world" }, 42);
     e.target.onsuccess = madeMod;
   };
 }
@@ -28,16 +27,16 @@ function madeMod() {
     // Make this transaction run until the end of time or until the page is
     // navigated away, whichever comes first.
     function doGet() {
       store.get(42).onsuccess = doGet;
     }
     doGet();
     document.location = "about:blank";
   }
-  
+
 }
   </script>
 </head>
 <body onload="startDBWork();">
   This is page one.
 </body>
 </html>
--- a/dom/indexedDB/test/test_bfcache.html
+++ b/dom/indexedDB/test/test_bfcache.html
@@ -4,16 +4,17 @@
 -->
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript">
+    /* import-globals-from helpers.js */
     var gOrigMaxTotalViewers = undefined;
     function setCachePref(enabled) {
       if (enabled) {
         is(typeof gOrigMaxTotalViewers, "undefined",
            "don't double-enable bfcache");
         SpecialPowers.setBoolPref("browser.sessionhistory.cache_subframes",
                                   true);
         gOrigMaxTotalViewers =
@@ -39,17 +40,17 @@
       setCachePref(true);
       window.onmessage = grabEventAndContinueHandler;
 
       iframe.src = "bfcache_iframe1.html";
       var event = yield undefined;
       is(event.data, "go", "set up database successfully");
 
       iframe.src = "bfcache_iframe2.html";
-      res = JSON.parse((yield).data);
+      let res = JSON.parse((yield).data);
       is(res.version, 2, "version was set correctly");
       is(res.storeCount, 1, "correct set of stores");
       ok(!("blockedFired" in res), "blocked shouldn't fire");
       is(res.value, JSON.stringify({ hello: "world" }),
          "correct value found in store");
 
       setCachePref(false);
       finishTest();
--- a/dom/indexedDB/test/test_blob_simple.html
+++ b/dom/indexedDB/test/test_blob_simple.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     info("Setting up test fixtures: create an IndexedDB database and object store.");
 
     let request = indexedDB.open(window.location.pathname, 1);
     request.onerror = errorHandler;
     request.onupgradeneeded = grabEventAndContinueHandler;
     request.onsuccess = unexpectedSuccessHandler;
@@ -30,17 +31,17 @@
     event = yield undefined;
 
 
     info("Let's create a blob and store it in IndexedDB twice.");
 
     const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
     const INDEX_KEY = 5;
     let blob = new Blob(BLOB_DATA, { type: "text/plain" });
-    let data = { blob: blob, index: INDEX_KEY };
+    let data = { blob, index: INDEX_KEY };
 
     objectStore = db.transaction("foo", "readwrite").objectStore("foo");
     objectStore.add(data).onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     info("Added blob to database once");
 
     let key = event.target.result;
@@ -202,16 +203,17 @@
     event = yield undefined;
 
     is(event.target.result, BLOB_DATA[0], "Correct text");
 
 
     info("Send blob to a worker, read its contents there, and verify results.");
 
     function workerScript() {
+      /* eslint-env worker */
       onmessage = function(event) {
         var reader = new FileReaderSync();
         postMessage(reader.readAsText(event.data));
 
         var slice = event.data.slice(1, 2);
         postMessage(reader.readAsText(slice));
 
       }
--- a/dom/indexedDB/test/test_blob_worker_crash.html
+++ b/dom/indexedDB/test/test_blob_worker_crash.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Blob Worker Crash Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   /*
    * This tests ensures that if the last live reference to a Blob is on the
    * worker and the database has already been shutdown, that there is no crash
    * when the owning page gets cleaned up which causes the termination of the
    * worker which in turn garbage collects during its shutdown.
    *
    * We do the IndexedDB stuff in the iframe so we can kill it as part of our
    * test.  Doing it out here is no good.
@@ -22,17 +23,17 @@
 
   function* testSteps()
   {
     info("Open iframe, wait for it to do its IndexedDB stuff.");
 
     let iframe = document.getElementById("iframe1");
     window.addEventListener("message", grabEventAndContinueHandler);
     // Put it in a different origin to be safe
-    iframe.src = //"http://example.org" +
+    iframe.src = // "http://example.org" +
                  window.location.pathname.replace(
                    "test_blob_worker_crash.html",
                    "blob_worker_crash_iframe.html");
 
     let event = yield unexpectedSuccessHandler;
     is(event.data.result, "ready", "worker initialized correctly");
 
     info("Trigger a GC to clean-up the iframe's main-thread IndexedDB");
@@ -41,17 +42,17 @@
 
     info("Kill the iframe, forget about it, trigger a GC.");
     iframe.remove();
     iframe = null;
     scheduleGC();
     yield undefined;
 
     info("If we are still alive, then we win!");
-    ok('Did not crash / trigger an assert!');
+    ok("Did not crash / trigger an assert!");
 
     finishTest();
   }
   </script>
   <script type="text/javascript" src="helpers.js"></script>
 
 </head>
 
--- a/dom/indexedDB/test/test_blob_worker_xhr_post.html
+++ b/dom/indexedDB/test/test_blob_worker_xhr_post.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const BLOB_DATA = ["fun ", "times ", "all ", "around!"];
     const BLOB_TYPE = "text/plain";
     const BLOB_SIZE = BLOB_DATA.join("").length;
 
     info("Setting up");
 
@@ -66,27 +67,28 @@
 
     ok(slice instanceof Blob, "Slice returned a blob");
     is(slice.size, BLOB_DATA[0].length, "Correct size for slice");
     is(slice.type, BLOB_TYPE, "Correct type for slice");
 
     info("Sending slice to a worker");
 
     function workerScript() {
+      /* eslint-env worker */
       onmessage = function(event) {
         var blob = event.data;
         var xhr = new XMLHttpRequest();
         // We just want to make sure the error case doesn't fire; it's fine for
         // us to just want a 404.
-        xhr.open('POST', 'http://mochi.test:8888/does-not-exist', true);
+        xhr.open("POST", "http://mochi.test:8888/does-not-exist", true);
         xhr.onload = function() {
           postMessage({ status: xhr.status });
         };
         xhr.onerror = function() {
-          postMessage({ status: 'error' });
+          postMessage({ status: "error" });
         }
         xhr.send(blob);
       }
     }
 
     let workerScriptUrl =
       URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
 
--- a/dom/indexedDB/test/test_blob_worker_xhr_post_multifile.html
+++ b/dom/indexedDB/test/test_blob_worker_xhr_post_multifile.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   /**
    * Create a composite/multi-file Blob on the worker, then post it as an XHR
    * payload and ensure that we don't hang/generate an assertion/etc. but
    * instead generate the expected 404.  This test is basically the same as
    * test_blob_worker_xhr_post.html except for the composite Blob.
    */
   function* testSteps()
   {
@@ -64,29 +65,30 @@
 
     blob = event.target.result;
 
     ok(blob instanceof Blob, "Got a blob");
     is(blob.size, BLOB_SIZE, "Correct size");
     is(blob.type, BLOB_TYPE, "Correct type");
 
     function workerScript() {
+      /* eslint-env worker */
       onmessage = function(event) {
         var blob = event.data;
         var compositeBlob = new Blob(["preceding string. ", blob],
                                      { type: "text/plain" });
         var xhr = new XMLHttpRequest();
         // We just want to make sure the error case doesn't fire; it's fine for
         // us to just want a 404.
-        xhr.open('POST', 'http://mochi.test:8888/does-not-exist', true);
+        xhr.open("POST", "http://mochi.test:8888/does-not-exist", true);
         xhr.onload = function() {
           postMessage({ status: xhr.status });
         };
         xhr.onerror = function() {
-          postMessage({ status: 'error' });
+          postMessage({ status: "error" });
         }
         xhr.send(compositeBlob);
       }
     }
 
     let workerScriptUrl =
       URL.createObjectURL(new Blob(["(", workerScript.toSource(), ")()"]));
 
--- a/dom/indexedDB/test/test_blob_worker_xhr_read.html
+++ b/dom/indexedDB/test/test_blob_worker_xhr_read.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Blob Read From Worker</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   /**
    * Create an IndexedDB-backed Blob, send it to the worker, try and read the
    * contents of the Blob from the worker using an XHR.  Ideally, we don't
    * deadlock the main thread.
    */
   function* testSteps()
   {
     const BLOB_DATA = ["Green"];
@@ -65,22 +66,23 @@
 
     ok(blob instanceof Blob, "Got a blob");
     is(blob.size, BLOB_SIZE, "Correct size");
     is(blob.type, BLOB_TYPE, "Correct type");
 
     info("Sending blob to a worker");
 
     function workerScript() {
+      /* eslint-env worker */
       onmessage = function(event) {
         var blob = event.data;
         var blobUrl = URL.createObjectURL(blob);
         var xhr = new XMLHttpRequest();
-        xhr.open('GET', blobUrl, true);
-        xhr.responseType = 'text';
+        xhr.open("GET", blobUrl, true);
+        xhr.responseType = "text";
         xhr.onload = function() {
           postMessage({ data: xhr.response });
           URL.revokeObjectURL(blobUrl);
         };
         xhr.onerror = function() {
           postMessage({ data: null });
           URL.revokeObjectURL(blobUrl);
         }
--- a/dom/indexedDB/test/test_blob_worker_xhr_read_slice.html
+++ b/dom/indexedDB/test/test_blob_worker_xhr_read_slice.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Blob Read From Worker</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   /**
    * Create an IndexedDB-backed Blob, send it to the worker, try and read the
    * *SLICED* contents of the Blob from the worker using an XHR.  This is
    * (as of the time of writing this) basically the same as
    * test_blob_worker_xhr_read.html but with slicing added.
    */
   function* testSteps()
   {
@@ -66,23 +67,24 @@
 
     ok(blob instanceof Blob, "Got a blob");
     is(blob.size, BLOB_SIZE, "Correct size");
     is(blob.type, BLOB_TYPE, "Correct type");
 
     info("Sending blob to a worker");
 
     function workerScript() {
+      /* eslint-env worker */
       onmessage = function(event) {
         var blob = event.data;
         var slicedBlob = blob.slice(0, 3, "text/plain");
         var blobUrl = URL.createObjectURL(slicedBlob);
         var xhr = new XMLHttpRequest();
-        xhr.open('GET', blobUrl, true);
-        xhr.responseType = 'text';
+        xhr.open("GET", blobUrl, true);
+        xhr.responseType = "text";
         xhr.onload = function() {
           postMessage({ data: xhr.response });
           URL.revokeObjectURL(blobUrl);
         };
         xhr.onerror = function() {
           postMessage({ data: null });
           URL.revokeObjectURL(blobUrl);
         }
--- a/dom/indexedDB/test/test_bug937006.html
+++ b/dom/indexedDB/test/test_bug937006.html
@@ -13,17 +13,17 @@
 </head>
 <body onload="runTest();">
   <script type="text/javascript">
 
   function runTest() {
     // doing this IDBRequest should not be able to retrieve the filename and
     // line number.
     SimpleTest.requestFlakyTimeout("untriaged");
-    setTimeout(indexedDB.deleteDatabase.bind(indexedDB), 0, 'x');
+    setTimeout(indexedDB.deleteDatabase.bind(indexedDB), 0, "x");
     setTimeout(function() {
       ok(true, "Still alive");
       SimpleTest.finish();
     }, 10);
   }
 
   </script>
 </body>
--- a/dom/indexedDB/test/test_file_array.html
+++ b/dom/indexedDB/test/test_file_array.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
     const b1 = getRandomBlob(10000);
 
--- a/dom/indexedDB/test/test_file_cross_database_copying.html
+++ b/dom/indexedDB/test/test_file_cross_database_copying.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const databaseInfo = [
       { name: window.location.pathname + "1" },
       { name: window.location.pathname + "2" }
     ];
@@ -50,17 +52,17 @@
       databases.push(db);
     }
 
     let refResult;
     for (let db of databases) {
       let request = db.transaction([objectStoreName])
                       .objectStore(objectStoreName).get(fileData.key);
       request.onsuccess = grabEventAndContinueHandler;
-      event = yield undefined;
+      let event = yield undefined;
 
       let result = event.target.result;
       verifyBlob(result, fileData.file, 1);
       yield undefined;
 
       if (!refResult) {
         refResult = result;
         continue;
@@ -70,19 +72,19 @@
     }
 
     for (let i = 1; i < databases.length; i++) {
       let db = databases[i];
 
       let objectStore = db.transaction([objectStoreName], READ_WRITE)
                           .objectStore(objectStoreName);
 
-      request = objectStore.add(refResult, 2);
+      let request = objectStore.add(refResult, 2);
       request.onsuccess = grabEventAndContinueHandler;
-      event = yield undefined;
+      let event = yield undefined;
 
       is(event.target.result, 2, "Got correct key");
 
       request = objectStore.get(2);
       request.onsuccess = grabEventAndContinueHandler;
       event = yield undefined;
 
       let result = event.target.result;
--- a/dom/indexedDB/test/test_file_delete.html
+++ b/dom/indexedDB/test/test_file_delete.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
@@ -75,18 +77,18 @@
       request.onsuccess = grabEventAndContinueHandler;
       let event = yield undefined;
 
       is(event.type, "success", "Got correct event type");
 
       let db = event.target.result;
       db.onerror = errorHandler;
 
-      trans = db.transaction([objectStoreName], READ_WRITE);
-      objectStore = trans.objectStore(objectStoreName);
+      let trans = db.transaction([objectStoreName], READ_WRITE);
+      let objectStore = trans.objectStore(objectStoreName);
 
       request = objectStore.get(fileData2.key);
       request.onsuccess = grabEventAndContinueHandler;
       event = yield undefined;
 
       let result = event.target.result;
       ok(result, "Got result");
 
--- a/dom/indexedDB/test/test_file_os_delete.html
+++ b/dom/indexedDB/test/test_file_os_delete.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
--- a/dom/indexedDB/test/test_file_put_deleted.html
+++ b/dom/indexedDB/test/test_file_put_deleted.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   /**
    * Test that a put of a file-backed Blob/File whose backing file has been
    * deleted results in a failure of that put failure.
    *
    * In order to create a file-backed Blob and ensure that we actually try and
    * copy its contents (rather than triggering a reference-count increment), we
    * use two separate databases.  This test is derived from
    * test_file_cross_database_copying.html.
@@ -64,17 +66,17 @@
     }
 
     // Get a reference to the file-backed File.
     let fileBackedFile;
     for (let db of databases.slice(0, 1)) {
       let request = db.transaction([objectStoreName])
                       .objectStore(objectStoreName).get(fileData.key);
       request.onsuccess = grabEventAndContinueHandler;
-      event = yield undefined;
+      let event = yield undefined;
 
       let result = event.target.result;
       verifyBlob(result, fileData.file, 1);
       yield undefined;
 
       fileBackedFile = result;
     }
 
@@ -84,17 +86,17 @@
     // must not start with a directory separator.)
     let fileRelPath =
       fileFullPath.substring(fileFullPath.search(/[/\\]storage[/\\]default[/\\]/) + 1);
     info("trying to delete: " + fileRelPath);
     // by using the existing SpecialPowers mechanism to create files and clean
     // them up.  We clobber our existing content, then trigger deletion to
     // clean up after it.
     SpecialPowers.createFiles(
-      [{ name: fileRelPath, data: '' }],
+      [{ name: fileRelPath, data: "" }],
       grabEventAndContinueHandler, errorCallbackHandler);
     yield undefined;
     // This is async without a callback because it's intended for cleanup.
     // Since IDB is PBackground, we can't depend on serial ordering, so we need
     // to use another async action.
     SpecialPowers.removeFiles();
     SpecialPowers.executeAfterFlushingMessageQueue(grabEventAndContinueHandler);
     yield undefined;
@@ -104,44 +106,44 @@
     // request and transaction.
     info("attempt to store deleted file-backed blob"); // context for NS_WARN_IF
     for (let i = 1; i < databases.length; i++) {
       let db = databases[i];
 
       let trans = db.transaction([objectStoreName], READ_WRITE);
       let objectStore = trans.objectStore(objectStoreName);
 
-      request = objectStore.add(fileBackedFile, 2);
+      let request = objectStore.add(fileBackedFile, 2);
       request.onsuccess = unexpectedSuccessHandler;
       request.onerror = expectedErrorHandler("UnknownError");
       trans.onsuccess = unexpectedSuccessHandler;
       trans.onerror = expectedErrorHandler("UnknownError");
       // the database will also throw an error.
       db.onerror = expectedErrorHandler("UnknownError");
-      event = yield undefined;
-      event = yield undefined;
-      event = yield undefined;
+      yield undefined;
+      yield undefined;
+      yield undefined;
       // the database shouldn't throw any more errors now.
       db.onerror = errorHandler;
     }
 
     // Ensure there's nothing with that key in the target database.
     info("now that the transaction failed, make sure our put got rolled back");
     for (let i = 1; i < databases.length; i++) {
       let db = databases[i];
 
       let objectStore = db.transaction([objectStoreName], "readonly")
                           .objectStore(objectStoreName);
 
       // Attempt to fetch the key to verify there's nothing in the DB rather
       // than the value which could return undefined as a misleading error.
-      request = objectStore.getKey(2);
+      let request = objectStore.getKey(2);
       request.onsuccess = grabEventAndContinueHandler;
       request.onerror = errorHandler;
-      event = yield undefined;
+      let event = yield undefined;
 
       let result = event.target.result;
       is(result, undefined, "no key found"); // (the get returns undefined)
     }
 
     finishTest();
   }
   </script>
--- a/dom/indexedDB/test/test_file_put_get_object.html
+++ b/dom/indexedDB/test/test_file_put_get_object.html
@@ -5,20 +5,20 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
-    const READ_WRITE = "readwrite";
-
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
     const blob = getRandomBlob(1000);
     const file = getRandomFile("random.bin", 100000);
 
     const objectData1 = { key: 1, object: { foo: blob, bar: blob } };
--- a/dom/indexedDB/test/test_file_put_get_values.html
+++ b/dom/indexedDB/test/test_file_put_get_values.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
--- a/dom/indexedDB/test/test_file_replace.html
+++ b/dom/indexedDB/test/test_file_replace.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
     const blobData = { key: 42, blobs: [] };
 
--- a/dom/indexedDB/test/test_file_resurrection_delete.html
+++ b/dom/indexedDB/test/test_file_resurrection_delete.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
@@ -77,17 +79,17 @@
       let event = yield undefined;
 
       is(event.type, "success", "Got correct event type");
 
       let db = event.target.result;
       db.onerror = errorHandler;
 
       let trans = db.transaction([objectStoreName], READ_WRITE);
-      objectStore = trans.objectStore(objectStoreName);
+      let objectStore = trans.objectStore(objectStoreName);
 
       request = objectStore.get(fileData.key);
       request.onsuccess = grabEventAndContinueHandler;
       event = yield undefined;
 
       let result = event.target.result;
       ok(result, "Got result");
 
--- a/dom/indexedDB/test/test_file_resurrection_transaction_abort.html
+++ b/dom/indexedDB/test/test_file_resurrection_transaction_abort.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
--- a/dom/indexedDB/test/test_file_sharing.html
+++ b/dom/indexedDB/test/test_file_sharing.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreInfo = [
       { name: "Blobs", options: { } },
--- a/dom/indexedDB/test/test_file_transaction_abort.html
+++ b/dom/indexedDB/test/test_file_transaction_abort.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
--- a/dom/indexedDB/test/test_filehandle_compat.html
+++ b/dom/indexedDB/test/test_filehandle_compat.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
--- a/dom/indexedDB/test/test_filehandle_disabled_pref.html
+++ b/dom/indexedDB/test/test_filehandle_disabled_pref.html
@@ -8,16 +8,18 @@
 
     <script type="text/javascript"
             src="/tests/SimpleTest/SimpleTest.js">
     </script>
 
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
     <script type="text/javascript">
+      /* import-globals-from file.js */
+      /* import-globals-from helpers.js */
       function* testSteps()
       {
         const databaseName = window.location.pathname;
         const databaseVersion = 1;
         const objectStoreName = "foo";
         const mutableFileName = "bar";
         const mutableFileKey = 42;
 
@@ -162,16 +164,17 @@
 
         info("getting mutable file");
 
         request = objectStore.get(mutableFileKey);
         request.onsuccess = grabEventAndContinueHandler;
         event = yield undefined;
 
         try {
+          // eslint-disable-next-line no-unused-vars
           let result = request.result;
           ok(false, "Should have thrown!");
         }
         catch (e) {
           ok(e instanceof DOMException, "Got exception.");
           is(e.name, "InvalidStateError", "Good error.");
           is(e.code, DOMException.INVALID_STATE_ERR, "Good error code.");
         }
--- a/dom/indexedDB/test/test_filehandle_getFile.html
+++ b/dom/indexedDB/test/test_filehandle_getFile.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
@@ -28,21 +29,21 @@
     event = yield undefined;
 
     let mutableFile = event.target.result;
     mutableFile.onerror = errorHandler;
 
     request = mutableFile.getFile();
     ok(request instanceof DOMRequest, "Correct interface");
     ok(!(request instanceof IDBFileRequest), "Correct interface");
-    ok(!('fileHandle' in request), "Property should not exist");
+    ok(!("fileHandle" in request), "Property should not exist");
     ok(request.fileHandle === undefined, "Property should not exist");
-    ok(!('lockedFile' in request), "Property should not exist");
+    ok(!("lockedFile" in request), "Property should not exist");
     ok(request.lockedFile === undefined, "Property should not exist");
-    ok(!('onprogress' in request), "Property should not exist");
+    ok(!("onprogress" in request), "Property should not exist");
     ok(request.onprogress === undefined, "Property should not exist");
 
     finishTest();
   }
   </script>
   <script type="text/javascript" src="helpers.js"></script>
 
 </head>
--- a/dom/indexedDB/test/test_filehandle_iteration.html
+++ b/dom/indexedDB/test/test_filehandle_iteration.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const dbName = window.location.pathname;
     const dbVersion = 1;
     const objectStoreName = "foo";
     const entryCount = 10;
 
     let request = indexedDB.open(dbName, dbVersion);
--- a/dom/indexedDB/test/test_filehandle_lifetimes.html
+++ b/dom/indexedDB/test/test_filehandle_lifetimes.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
--- a/dom/indexedDB/test/test_filehandle_lifetimes_nested.html
+++ b/dom/indexedDB/test/test_filehandle_lifetimes_nested.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
@@ -25,17 +26,17 @@
     request = db.createMutableFile("test.txt");
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     let mutableFile = event.target.result;
     mutableFile.onerror = errorHandler;
 
-    let fileHandle = mutableFile.open();
+    mutableFile.open();
 
     let fileHandle2;
 
     let comp = SpecialPowers.wrap(SpecialPowers.Components);
     let thread = comp.classes["@mozilla.org/thread-manager;1"]
                      .getService(comp.interfaces.nsIThreadManager)
                      .currentThread;
 
--- a/dom/indexedDB/test/test_filehandle_location.html
+++ b/dom/indexedDB/test/test_filehandle_location.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
--- a/dom/indexedDB/test/test_filehandle_ordering.html
+++ b/dom/indexedDB/test/test_filehandle_ordering.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
@@ -28,18 +29,18 @@
     event = yield undefined;
 
     let mutableFile = event.target.result;
     mutableFile.onerror = errorHandler;
 
     let fileHandle1 = mutableFile.open("readwrite");
     let fileHandle2 = mutableFile.open("readwrite");
 
-    let request1 = fileHandle2.write("2");
-    let request2 = fileHandle1.write("1");
+    fileHandle2.write("2");
+    fileHandle1.write("1");
 
     fileHandle1.oncomplete = grabEventAndContinueHandler;
     fileHandle2.oncomplete = grabEventAndContinueHandler;
 
     yield undefined;
     yield undefined;
 
     let fileHandle3 = mutableFile.open("readonly");
--- a/dom/indexedDB/test/test_filehandle_overlapping.html
+++ b/dom/indexedDB/test/test_filehandle_overlapping.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
@@ -50,17 +51,17 @@
       request = mutableFile.open("readwrite").append("string3");
       request.onsuccess = function(event) {
         is(stepNumber, 3, "This callback came third");
         stepNumber++;
         event.target.fileHandle.oncomplete = grabEventAndContinueHandler;
       }
 
       stepNumber++;
-      yield undefined; yield undefined; yield undefined;;
+      yield undefined; yield undefined; yield undefined;
 
       is(stepNumber, 4, "All callbacks received");
     }
 
     finishTest();
   }
   </script>
   <script type="text/javascript" src="helpers.js"></script>
--- a/dom/indexedDB/test/test_filehandle_progress_events.html
+++ b/dom/indexedDB/test/test_filehandle_progress_events.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     var testBuffer = getRandomBuffer(100000);
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
--- a/dom/indexedDB/test/test_filehandle_readonly_exceptions.html
+++ b/dom/indexedDB/test/test_filehandle_readonly_exceptions.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
--- a/dom/indexedDB/test/test_filehandle_request_readyState.html
+++ b/dom/indexedDB/test/test_filehandle_request_readyState.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     is(request.readyState, "pending", "Correct readyState");
 
     request.onerror = errorHandler;
--- a/dom/indexedDB/test/test_filehandle_serialization.html
+++ b/dom/indexedDB/test/test_filehandle_serialization.html
@@ -5,29 +5,29 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const databaseInfo = [
       { name: window.location.pathname + "1" },
       { name: window.location.pathname + "2" }
     ];
 
     const objectStoreName = "Blobs";
 
-    const testFile = getRandomFile("random.bin", 100000);
-
     let databases = [];
     for (let info of databaseInfo) {
       let request = indexedDB.open(info.name, 1);
       request.onerror = errorHandler;
       request.onupgradeneeded = grabEventAndContinueHandler;
       request.onsuccess = grabEventAndContinueHandler;
       let event = yield undefined;
 
@@ -44,17 +44,17 @@
 
       databases.push(db);
     }
 
     let db1 = databases[0];
 
     let request = db1.createMutableFile("random.bin", "binary/random");
     request.onsuccess = grabEventAndContinueHandler;
-    event = yield undefined;
+    let event = yield undefined;
 
     let mutableFile = event.target.result;
     is(getFileId(mutableFile), 1, "Correct file id");
     is(mutableFile.name, "random.bin", "Correct name");
     is(mutableFile.type, "binary/random", "Correct type");
 
     let trans = db1.transaction([objectStoreName], READ_WRITE);
     let objectStore = trans.objectStore(objectStoreName);
--- a/dom/indexedDB/test/test_filehandle_store_snapshot.html
+++ b/dom/indexedDB/test/test_filehandle_store_snapshot.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const READ_WRITE = "readwrite";
 
     const name = window.location.pathname;
 
     const objectStoreName = "Blobs";
 
--- a/dom/indexedDB/test/test_filehandle_stream_tracking.html
+++ b/dom/indexedDB/test/test_filehandle_stream_tracking.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     var testBuffer = getRandomBuffer(100000);
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
--- a/dom/indexedDB/test/test_filehandle_success_events_after_abort.html
+++ b/dom/indexedDB/test/test_filehandle_success_events_after_abort.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     let event = yield undefined;
--- a/dom/indexedDB/test/test_filehandle_truncate.html
+++ b/dom/indexedDB/test/test_filehandle_truncate.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     var testBuffer = getRandomBuffer(100000);
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
--- a/dom/indexedDB/test/test_filehandle_workers.html
+++ b/dom/indexedDB/test/test_filehandle_workers.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     let testBuffer = getRandomBuffer(100000);
 
     let request = indexedDB.open(name, 1);
     request.onerror = errorHandler;
@@ -38,18 +40,19 @@
     request = db.createMutableFile("test.txt");
     request.onerror = errorHandler;
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     let mutableFile = event.target.result;
 
     function dummyWorkerScript() {
+      /* eslint-env worker */
       onmessage = function(event) {
-        throw("Shouldn't be called!");
+        throw ("Shouldn't be called!");
       }
     }
 
     let url =
       URL.createObjectURL(new Blob(["(", dummyWorkerScript.toSource(), ")()"]));
 
     let worker1 = new Worker(url);
     try {
@@ -100,16 +103,17 @@
         var name = event.data;
         var request = indexedDB.open(name, 1);
         request.onsuccess = function(event) {
           var db = event.target.result;
           let objectStore = db.transaction("Foo").objectStore("Foo");
           request = objectStore.get(42);
           request.onsuccess = function(event) {
             try {
+              // eslint-disable-next-line no-unused-vars
               let result = request.result;
               postMessage("error");
             }
             catch (e) {
               postMessage("success");
             }
           }
           request.onerror = function(event) {
--- a/dom/indexedDB/test/test_filehandle_write_read_data.html
+++ b/dom/indexedDB/test/test_filehandle_write_read_data.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     var testString = "Lorem ipsum his ponderum delicatissimi ne, at noster dolores urbanitas pro, cibo elaboraret no his. Ea dicunt maiorum usu. Ad appareat facilisis mediocritatem eos. Tale graeci mentitum in eos, hinc insolens at nam. Graecis nominavi aliquyam eu vix. Id solet assentior sadipscing pro. Et per atqui graecis, usu quot viris repudiandae ei, mollis evertitur an nam. At nam dolor ignota, liber labore omnesque ea mei, has movet voluptaria in. Vel an impetus omittantur. Vim movet option salutandi ex, ne mei ignota corrumpit. Mucius comprehensam id per. Est ea putant maiestatis.";
     for (let i = 0; i < 5; i++) {
       testString += testString;
     }
--- a/dom/indexedDB/test/test_getFileId.html
+++ b/dom/indexedDB/test/test_getFileId.html
@@ -5,16 +5,18 @@
 <html>
 <head>
   <title>File Handle Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from file.js */
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     let id = getFileId(null);
     ok(id == -1, "Correct id");
 
     id = getFileId(getRandomBlob(100));
     ok(id == -1, "Correct id");
 
--- a/dom/indexedDB/test/test_globalObjects_content.html
+++ b/dom/indexedDB/test/test_globalObjects_content.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     const name = window.location.pathname;
 
     // Test for IDBKeyRange and indexedDB availability in content windows.
     let keyRange = IDBKeyRange.only(42);
     ok(keyRange, "Got keyRange");
 
--- a/dom/indexedDB/test/test_leaving_page.html
+++ b/dom/indexedDB/test/test_leaving_page.html
@@ -10,19 +10,20 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 
 <body onload="runTest();">
   <iframe id="inner"></iframe>
   <a id="a" href="leaving_page_iframe.html"></a>
 
   <script type="text/javascript">
-    onmessage = function(e) {
+    /* import-globals-from helpers.js */
+    window.addEventListener("message", function(e) {
       ok(false, "gotmessage: " + e.data);
-    }
+    });
 
     function* testSteps()
     {
       var iframe = $("inner");
       iframe.src = "leaving_page_iframe.html";
       iframe.onload = continueToNextStep;
       yield undefined;
       is(iframe.contentWindow.location.href, $("a").href,
--- a/dom/indexedDB/test/test_message_manager_ipc.html
+++ b/dom/indexedDB/test/test_message_manager_ipc.html
@@ -6,16 +6,17 @@
     </script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   </head>
   <body onload="setup();">
     <script type="application/javascript">
 "use strict";
 
 function childFrameScript() {
+  /* eslint-env mozilla/frame-script */
   "use strict";
 
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
   const mmName = "test:idb-and-mm";
 
   const dbName = "test_message_manager_ipc.html - CHILD";
   const dbVersion = 1;
@@ -24,35 +25,35 @@ function childFrameScript() {
 
   const blobData = ["So", " ", "many", " ", "blobs!"];
   const blobText = blobData.join("");
   const blobType = "text/plain";
 
   Cu.importGlobalProperties(["indexedDB"]);
 
   function info(msg) {
-    sendAsyncMessage(mmName, { op: "info", msg: msg });
+    sendAsyncMessage(mmName, { op: "info", msg });
   }
 
   function ok(condition, name, diag) {
     sendAsyncMessage(mmName,
                      { op: "ok",
-                       condition: condition,
-                       name: name,
-                       diag: diag });
+                       condition,
+                       name,
+                       diag });
   }
 
   function is(a, b, name) {
     let pass = a == b;
     let diag = pass ? "" : "got " + a + ", expected " + b;
     ok(pass, name, diag);
   }
 
   function finish(result) {
-    sendAsyncMessage(mmName, { op: "done", result: result });
+    sendAsyncMessage(mmName, { op: "done", result });
   }
 
   function grabAndContinue(arg) {
     testGenerator.next(arg);
   }
 
   function errorHandler(event) {
     ok(false,
--- a/dom/indexedDB/test/test_open_for_principal.html
+++ b/dom/indexedDB/test/test_open_for_principal.html
@@ -5,16 +5,17 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+  /* import-globals-from helpers.js */
   function* testSteps()
   {
     is("open" in indexedDB, true, "open() defined");
     is("openForPrincipal" in indexedDB, false, "openForPrincipal() not defined");
 
     is("deleteDatabase" in indexedDB, true, "deleteDatabase() defined");
     is("deleteForPrincipal" in indexedDB, false, "deleteForPrincipal() not defined");
 
--- a/dom/indexedDB/test/test_persistenceType.html
+++ b/dom/indexedDB/test/test_persistenceType.html
@@ -5,37 +5,38 @@
 <html>
 <head>
   <title>Indexed Database Property Test</title>
 
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 
   <script type="text/javascript">
+    /* import-globals-from helpers.js */
     function* testSteps()
     {
       const name = window.location.pathname;
       const version = 1;
       const storages = ["persistent", "temporary", "default"];
 
       const objectStoreName = "Foo";
       const data = { key: 1, value: "bar" };
 
       try {
-        indexedDB.open(name, { version: version, storage: "unknown" });
+        indexedDB.open(name, { version, storage: "unknown" });
         ok(false, "Should have thrown!");
       }
       catch (e) {
         ok(e instanceof TypeError, "Got TypeError.");
         is(e.name, "TypeError", "Good error name.");
       }
 
       for (let storage of storages) {
-        let request = indexedDB.open(name, { version: version,
-                                             storage: storage });
+        let request = indexedDB.open(name, { version,
+                                             storage });
 
         if (storage == "persistent" &&
             SpecialPowers.Services.appinfo.widgetToolkit == "android") {
           request.onerror = expectedErrorHandler("InvalidStateError");
           request.onupgradeneeded = unexpectedSuccessHandler;
           request.onsuccess = unexpectedSuccessHandler;
           let event = yield undefined;
 
--- a/dom/indexedDB/test/test_sandbox.html
+++ b/dom/indexedDB/test/test_sandbox.html
@@ -9,93 +9,95 @@
 <script type="application/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
 // This runs inside a same-origin sandbox.
 // The intent being to show that the data store is the same.
 function storeValue() {
   function createDB_inner() {
-    var op = indexedDB.open('db');
+    var op = indexedDB.open("db");
     op.onupgradeneeded = e => {
       var db = e.target.result;
-      db.createObjectStore('store');
+      db.createObjectStore("store");
     };
     return new Promise(resolve => {
       op.onsuccess = e => resolve(e.target.result);
     });
   }
 
   function add(k, v) {
     return createDB_inner().then(db => {
-      var tx = db.transaction('store', 'readwrite');
-      var store = tx.objectStore('store');
+      var tx = db.transaction("store", "readwrite");
+      var store = tx.objectStore("store");
       var op = store.add(v, k);
       return new Promise((resolve, reject) => {
         op.onsuccess = e => resolve(e.target.result);
         op.onerror = _ => reject(op.error);
         tx.onabort = _ => reject(tx.error);
       });
     });
   }
 
-  return add('x', [ 10, {} ])
+  return add("x", [ 10, {} ])
     .then(_ => step_done(),
-          _ => ok(false, 'failed to store'));
+          _ => ok(false, "failed to store"));
 }
 
 function createDB_outer() {
-  var op = indexedDB.open('db');
+  var op = indexedDB.open("db");
   op.onupgradeneeded = e => {
-    ok(false, 'upgrade should not be needed');
+    ok(false, "upgrade should not be needed");
     var db = e.target.result;
-    db.createObjectStore('store');
+    db.createObjectStore("store");
   };
   return new Promise(resolve => {
     op.onsuccess = e => resolve(e.target.result);
   });
 }
 
 function get(k) {
   return createDB_outer().then(db => {
-      var tx = db.transaction('store', 'readonly');
-      var store = tx.objectStore('store');
+      var tx = db.transaction("store", "readonly");
+      var store = tx.objectStore("store");
       var op = store.get(k);
       return new Promise((resolve, reject) => {
         op.onsuccess = e => resolve(e.target.result);
         op.onerror = _ => reject(op.error);
         tx.onabort = _ => reject(tx.error);
       });
   });
 }
 
 function runInSandbox(sandbox, testFunc) {
-  is(typeof testFunc, 'function');
+  is(typeof testFunc, "function");
   var resolvePromise;
+  // Step-done is defined in the sandbox and used in the add() function above.
+  /* global step_done */
   var testPromise = new Promise(r => resolvePromise = r);
   SpecialPowers.Cu.exportFunction(_ => resolvePromise(), sandbox,
-                                  { defineAs: 'step_done' });
-  SpecialPowers.Cu.evalInSandbox('(' + testFunc.toSource() + ')()' +
-                                 '.then(step_done);', sandbox);
+                                  { defineAs: "step_done" });
+  SpecialPowers.Cu.evalInSandbox("(" + testFunc.toSource() + ")()" +
+                                 ".then(step_done);", sandbox);
   return testPromise;
 }
 
 // Use the window principal for the sandbox; location.origin is not sufficient.
 var sb = new SpecialPowers.Cu.Sandbox(window,
-                                      { wantGlobalProperties: ['indexedDB'] });
+                                      { wantGlobalProperties: ["indexedDB"] });
 
 sb.ok = SpecialPowers.Cu.exportFunction(ok, sb);
 
 Promise.resolve()
   .then(_ => runInSandbox(sb, storeValue))
-  .then(_ => get('x'))
+  .then(_ => get("x"))
   .then(x => {
-    ok(x, 'a value should be present');
+    ok(x, "a value should be present");
     is(x.length, 2);
     is(x[0], 10);
-    is(typeof x[1], 'object');
+    is(typeof x[1], "object");
     is(Object.keys(x[1]).length, 0);
   })
   .then(_ => SimpleTest.finish());
 
 </script>
 </body>
 </html>
--- a/dom/indexedDB/test/test_third_party.html
+++ b/dom/indexedDB/test/test_third_party.html
@@ -57,17 +57,17 @@
     function setiframe() {
       let iframe = document.getElementById("iframe1");
 
       if (!testRunning) {
         testRunning = true;
         iframe.addEventListener("load", iframeLoaded);
       }
       SpecialPowers.pushPrefEnv({
-        'set': [["network.cookie.cookieBehavior", testData[testIndex].cookieBehavior]]
+        "set": [["network.cookie.cookieBehavior", testData[testIndex].cookieBehavior]]
       }, () => {
         iframe.src = testData[testIndex].host + iframe1Path;
       });
       // SpecialPowers.setIntPref("network.cookie.cookieBehavior", testData[testIndex].cookieBehavior);
     }
 
     function messageListener(event) {
       // eslint-disable-next-line no-eval
--- a/dom/indexedDB/test/unit/.eslintrc.js
+++ b/dom/indexedDB/test/unit/.eslintrc.js
@@ -1,15 +1,13 @@
 "use strict";
 
 module.exports = {
   "extends": [
-    "plugin:mozilla/recommended",
     "plugin:mozilla/xpcshell-test",
   ],
   "plugins": [
     "mozilla"
   ],
   "rules": {
-    "brace-style": "off",
     "no-shadow": "off",
   }
 };
--- a/dom/media/FileBlockCache.cpp
+++ b/dom/media/FileBlockCache.cpp
@@ -26,21 +26,21 @@ FileBlockCache::SetCacheFile(PRFileDesc*
   LOG("SetFD(aFD=%p) mIsOpen=%d", aFD, mIsOpen);
 
   if (!aFD) {
     // Failed to get a temporary file. Shutdown.
     Close();
     return;
   }
   {
-    MonitorAutoLock lock(mFileMonitor);
+    MutexAutoLock lock(mFileMutex);
     mFD = aFD;
   }
   {
-    MonitorAutoLock lock(mDataMonitor);
+    MutexAutoLock lock(mDataMutex);
     if (!mIsOpen) {
       // We've been closed while waiting for the file descriptor. Bail out.
       // Rely on the destructor to close the file descriptor.
       return;
     }
     mInitialized = true;
     if (mIsWriteScheduled) {
       // A write was scheduled while waiting for FD. We need to run/dispatch a
@@ -50,17 +50,17 @@ FileBlockCache::SetCacheFile(PRFileDesc*
   }
 }
 
 nsresult
 FileBlockCache::Init()
 {
   LOG("Init()");
 
-  MonitorAutoLock mon(mDataMonitor);
+  MutexAutoLock mon(mDataMutex);
   nsresult rv = NS_NewNamedThread("FileBlockCache",
                                   getter_AddRefs(mThread),
                                   nullptr,
                                   SharedThreadPool::kStackSize);
   if (NS_FAILED(rv)) {
     return rv;
   }
   mIsOpen = true;
@@ -86,50 +86,51 @@ FileBlockCache::Init()
   if (NS_FAILED(rv)) {
     Close();
   }
 
   return rv;
 }
 
 FileBlockCache::FileBlockCache()
-  : mFileMonitor("MediaCache.Writer.IO.Monitor"),
+  : mFileMutex("MediaCache.Writer.IO.Mutex"),
     mFD(nullptr),
     mFDCurrentPos(0),
-    mDataMonitor("MediaCache.Writer.Data.Monitor"),
+    mDataMutex("MediaCache.Writer.Data.Mutex"),
     mIsWriteScheduled(false),
+    mIsReading(false),
     mIsOpen(false)
 {
 }
 
 FileBlockCache::~FileBlockCache()
 {
   NS_ASSERTION(!mIsOpen, "Should Close() FileBlockCache before destroying");
 }
 
 void FileBlockCache::Close()
 {
   LOG("Close()");
 
   nsCOMPtr<nsIThread> thread;
   {
-    MonitorAutoLock mon(mDataMonitor);
+    MutexAutoLock mon(mDataMutex);
     if (!mIsOpen) {
       return;
     }
     mIsOpen = false;
     if (!mThread) {
       return;
     }
     thread.swap(mThread);
   }
 
   PRFileDesc* fd;
   {
-    MonitorAutoLock lock(mFileMonitor);
+    MutexAutoLock lock(mFileMutex);
     fd = mFD;
     mFD = nullptr;
   }
 
   // Let the thread close the FD, and then trigger its own shutdown.
   // Note that mThread is now empty, so no other task will be posted there.
   // Also mThread and mFD are empty and therefore can be reused immediately.
   nsresult rv = thread->Dispatch(NS_NewRunnableFunction([thread, fd] {
@@ -159,17 +160,17 @@ ContainerContains(const Container& aCont
   return std::find(aContainer.begin(), aContainer.end(), value)
          != aContainer.end();
 }
 
 nsresult
 FileBlockCache::WriteBlock(uint32_t aBlockIndex,
   Span<const uint8_t> aData1, Span<const uint8_t> aData2)
 {
-  MonitorAutoLock mon(mDataMonitor);
+  MutexAutoLock mon(mDataMutex);
 
   if (!mIsOpen)
     return NS_ERROR_FAILURE;
 
   // Check if we've already got a pending write scheduled for this block.
   mBlockChanges.EnsureLengthAtLeast(aBlockIndex + 1);
   bool blockAlreadyHadPendingChange = mBlockChanges[aBlockIndex] != nullptr;
   mBlockChanges[aBlockIndex] = new BlockChange(aData1, aData2);
@@ -187,34 +188,34 @@ FileBlockCache::WriteBlock(uint32_t aBlo
 
   EnsureWriteScheduled();
 
   return NS_OK;
 }
 
 void FileBlockCache::EnsureWriteScheduled()
 {
-  mDataMonitor.AssertCurrentThreadOwns();
+  mDataMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mIsOpen);
 
-  if (mIsWriteScheduled) {
+  if (mIsWriteScheduled || mIsReading) {
     return;
   }
   mIsWriteScheduled = true;
   if (!mInitialized) {
     // We're still waiting on a file descriptor. When it arrives,
     // the write will be scheduled.
     return;
   }
   mThread->Dispatch(this, NS_DISPATCH_NORMAL);
 }
 
 nsresult FileBlockCache::Seek(int64_t aOffset)
 {
-  mFileMonitor.AssertCurrentThreadOwns();
+  mFileMutex.AssertCurrentThreadOwns();
 
   if (mFDCurrentPos != aOffset) {
     MOZ_ASSERT(mFD);
     int64_t result = PR_Seek64(mFD, aOffset, PR_SEEK_SET);
     if (result != aOffset) {
       NS_WARNING("Failed to seek media cache file");
       return NS_ERROR_FAILURE;
     }
@@ -224,17 +225,17 @@ nsresult FileBlockCache::Seek(int64_t aO
 }
 
 nsresult FileBlockCache::ReadFromFile(int64_t aOffset,
                                       uint8_t* aDest,
                                       int32_t aBytesToRead,
                                       int32_t& aBytesRead)
 {
   LOG("ReadFromFile(offset=%" PRIu64 ", len=%u)", aOffset, aBytesToRead);
-  mFileMonitor.AssertCurrentThreadOwns();
+  mFileMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mFD);
 
   nsresult res = Seek(aOffset);
   if (NS_FAILED(res)) return res;
 
   aBytesRead = PR_Read(mFD, aDest, aBytesToRead);
   if (aBytesRead <= 0)
     return NS_ERROR_FAILURE;
@@ -243,17 +244,17 @@ nsresult FileBlockCache::ReadFromFile(in
   return NS_OK;
 }
 
 nsresult FileBlockCache::WriteBlockToFile(int32_t aBlockIndex,
                                           const uint8_t* aBlockData)
 {
   LOG("WriteBlockToFile(index=%u)", aBlockIndex);
 
-  mFileMonitor.AssertCurrentThreadOwns();
+  mFileMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mFD);
 
   nsresult rv = Seek(BlockIndexToOffset(aBlockIndex));
   if (NS_FAILED(rv)) return rv;
 
   int32_t amount = PR_Write(mFD, aBlockData, BLOCK_SIZE);
   if (amount < BLOCK_SIZE) {
     NS_WARNING("Failed to write media cache block!");
@@ -264,95 +265,110 @@ nsresult FileBlockCache::WriteBlockToFil
   return NS_OK;
 }
 
 nsresult FileBlockCache::MoveBlockInFile(int32_t aSourceBlockIndex,
                                          int32_t aDestBlockIndex)
 {
   LOG("MoveBlockInFile(src=%u, dest=%u)", aSourceBlockIndex, aDestBlockIndex);
 
-  mFileMonitor.AssertCurrentThreadOwns();
+  mFileMutex.AssertCurrentThreadOwns();
 
   uint8_t buf[BLOCK_SIZE];
   int32_t bytesRead = 0;
   if (NS_FAILED(ReadFromFile(BlockIndexToOffset(aSourceBlockIndex),
                              buf,
                              BLOCK_SIZE,
                              bytesRead))) {
     return NS_ERROR_FAILURE;
   }
   return WriteBlockToFile(aDestBlockIndex, buf);
 }
 
 nsresult FileBlockCache::Run()
 {
   NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
-  MonitorAutoLock mon(mDataMonitor);
+  MutexAutoLock mon(mDataMutex);
   NS_ASSERTION(!mChangeIndexList.empty(), "Only dispatch when there's work to do");
   NS_ASSERTION(mIsWriteScheduled, "Should report write running or scheduled.");
   MOZ_ASSERT(mFD);
 
   LOG("Run() mFD=%p mIsOpen=%d", mFD, mIsOpen);
 
   while (!mChangeIndexList.empty()) {
     if (!mIsOpen) {
       // We've been closed, abort, discarding unwritten changes.
       mIsWriteScheduled = false;
       return NS_ERROR_FAILURE;
     }
 
+    if (mIsReading) {
+      // We're trying to read; postpone all writes. (Reader will resume writes.)
+      mIsWriteScheduled = false;
+      return NS_OK;
+    }
+
     // Process each pending change. We pop the index out of the change
     // list, but leave the BlockChange in mBlockChanges until the change
     // is written to file. This is so that any read which happens while
-    // we drop mDataMonitor to write will refer to the data's source in
+    // we drop mDataMutex to write will refer to the data's source in
     // memory, rather than the not-yet up to date data written to file.
     // This also ensures we will insert a new index into mChangeIndexList
     // when this happens.
 
     // Hold a reference to the change, in case another change
     // overwrites the mBlockChanges entry for this block while we drop
-    // mDataMonitor to take mFileMonitor.
+    // mDataMutex to take mFileMutex.
     int32_t blockIndex = mChangeIndexList.front();
     mChangeIndexList.pop_front();
     RefPtr<BlockChange> change = mBlockChanges[blockIndex];
     MOZ_ASSERT(change,
                "Change index list should only contain entries for blocks "
                "with changes");
     {
-      MonitorAutoUnlock unlock(mDataMonitor);
-      MonitorAutoLock lock(mFileMonitor);
+      MutexAutoUnlock unlock(mDataMutex);
+      MutexAutoLock lock(mFileMutex);
       if (change->IsWrite()) {
         WriteBlockToFile(blockIndex, change->mData.get());
       } else if (change->IsMove()) {
         MoveBlockInFile(change->mSourceBlockIndex, blockIndex);
       }
     }
     // If a new change has not been made to the block while we dropped
-    // mDataMonitor, clear reference to the old change. Otherwise, the old
+    // mDataMutex, clear reference to the old change. Otherwise, the old
     // reference has been cleared already.
     if (mBlockChanges[blockIndex] == change) {
       mBlockChanges[blockIndex] = nullptr;
     }
   }
 
   mIsWriteScheduled = false;
 
   return NS_OK;
 }
 
 nsresult FileBlockCache::Read(int64_t aOffset,
                               uint8_t* aData,
                               int32_t aLength,
                               int32_t* aBytes)
 {
-  MonitorAutoLock mon(mDataMonitor);
+  MutexAutoLock mon(mDataMutex);
 
   if (!mIsOpen || (aOffset / BLOCK_SIZE) > INT32_MAX)
     return NS_ERROR_FAILURE;
 
+  mIsReading = true;
+  auto exitRead = MakeScopeExit([&] {
+    mIsReading = false;
+    if (!mChangeIndexList.empty()) {
+      // mReading has stopped or prevented pending writes, resume them.
+      EnsureWriteScheduled();
+    }
+  });
+
   int32_t bytesToRead = aLength;
   int64_t offset = aOffset;
   uint8_t* dst = aData;
   while (bytesToRead > 0) {
     int32_t blockIndex = static_cast<int32_t>(offset / BLOCK_SIZE);
     int32_t start = offset % BLOCK_SIZE;
     int32_t amount = std::min(BLOCK_SIZE - start, bytesToRead);
 
@@ -375,18 +391,18 @@ nsresult FileBlockCache::Read(int64_t aO
         // which happened *after* this move was recorded.
         blockIndex = mBlockChanges[blockIndex]->mSourceBlockIndex;
       }
       // Block has been written to file, either as the source block of a move,
       // or as a stable (all changes made) block. Read the data directly
       // from file.
       nsresult res;
       {
-        MonitorAutoUnlock unlock(mDataMonitor);
-        MonitorAutoLock lock(mFileMonitor);
+        MutexAutoUnlock unlock(mDataMutex);
+        MutexAutoLock lock(mFileMutex);
         res = ReadFromFile(BlockIndexToOffset(blockIndex) + start,
                            dst,
                            amount,
                            bytesRead);
       }
       NS_ENSURE_SUCCESS(res,res);
     }
     dst += bytesRead;
@@ -394,17 +410,17 @@ nsresult FileBlockCache::Read(int64_t aO
     bytesToRead -= bytesRead;
   }
   *aBytes = aLength - bytesToRead;
   return NS_OK;
 }
 
 nsresult FileBlockCache::MoveBlock(int32_t aSourceBlockIndex, int32_t aDestBlockIndex)
 {
-  MonitorAutoLock mon(mDataMonitor);
+  MutexAutoLock mon(mDataMutex);
 
   if (!mIsOpen)
     return NS_ERROR_FAILURE;
 
   mBlockChanges.EnsureLengthAtLeast(std::max(aSourceBlockIndex, aDestBlockIndex) + 1);
 
   // The source block's contents may be the destination of another pending
   // move, which in turn can be the destination of another pending move,
--- a/dom/media/FileBlockCache.h
+++ b/dom/media/FileBlockCache.h
@@ -3,18 +3,18 @@
 /* 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/. */
 
 #ifndef FILE_BLOCK_CACHE_H_
 #define FILE_BLOCK_CACHE_H_
 
 #include "mozilla/Attributes.h"
-#include "mozilla/Monitor.h"
 #include "mozilla/MozPromise.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/AbstractThread.h"
 #include "nsTArray.h"
 #include "MediaCache.h"
 #include "nsDeque.h"
 #include "nsThreadUtils.h"
 #include <deque>
 
@@ -138,20 +138,20 @@ public:
 
 private:
   int64_t BlockIndexToOffset(int32_t aBlockIndex) {
     return static_cast<int64_t>(aBlockIndex) * BLOCK_SIZE;
   }
 
   void SetCacheFile(PRFileDesc* aFD);
 
-  // Monitor which controls access to mFD and mFDCurrentPos. Don't hold
-  // mDataMonitor while holding mFileMonitor! mFileMonitor must be owned
+  // Mutex which controls access to mFD and mFDCurrentPos. Don't hold
+  // mDataMutex while holding mFileMutex! mFileMutex must be owned
   // while accessing any of the following data fields or methods.
-  Monitor mFileMonitor;
+  Mutex mFileMutex;
   // Moves a block already committed to file.
   nsresult MoveBlockInFile(int32_t aSourceBlockIndex,
                            int32_t aDestBlockIndex);
   // Seeks file pointer.
   nsresult Seek(int64_t aOffset);
   // Reads data from file offset.
   nsresult ReadFromFile(int64_t aOffset,
                         uint8_t* aDest,
@@ -159,24 +159,24 @@ private:
                         int32_t& aBytesRead);
   nsresult WriteBlockToFile(int32_t aBlockIndex, const uint8_t* aBlockData);
   // File descriptor we're writing to. This is created externally, but
   // shutdown by us.
   PRFileDesc* mFD;
   // The current file offset in the file.
   int64_t mFDCurrentPos;
 
-  // Monitor which controls access to all data in this class, except mFD
-  // and mFDCurrentPos. Don't hold mDataMonitor while holding mFileMonitor!
-  // mDataMonitor must be owned while accessing any of the following data
+  // Mutex which controls access to all data in this class, except mFD
+  // and mFDCurrentPos. Don't hold mDataMutex while holding mFileMutex!
+  // mDataMutex must be owned while accessing any of the following data
   // fields or methods.
-  Monitor mDataMonitor;
+  Mutex mDataMutex;
   // Ensures we either are running the event to preform IO, or an event
   // has been dispatched to preform the IO.
-  // mDataMonitor must be owned while calling this.
+  // mDataMutex must be owned while calling this.
   void EnsureWriteScheduled();
 
   // Array of block changes to made. If mBlockChanges[offset/BLOCK_SIZE] == nullptr,
   // then the block has no pending changes to be written, but if
   // mBlockChanges[offset/BLOCK_SIZE] != nullptr, then either there's a block
   // cached in memory waiting to be written, or this block is the target of a
   // block move.
   nsTArray< RefPtr<BlockChange> > mBlockChanges;
@@ -184,19 +184,22 @@ private:
   // created upon open, and shutdown (asynchronously) upon close (on the
   // main thread).
   nsCOMPtr<nsIThread> mThread;
   // Queue of pending block indexes that need to be written or moved.
   std::deque<int32_t> mChangeIndexList;
   // True if we've dispatched an event to commit all pending block changes
   // to file on mThread.
   bool mIsWriteScheduled;
+  // True when a read is happening. Pending writes may be postponed, to give
+  // higher priority to reads (which may be blocking the caller).
+  bool mIsReading;
   // True if the writer is ready to enqueue writes.
   bool mIsOpen;
   // True if we've got a temporary file descriptor. Note: we don't use mFD
-  // directly as that's synchronized via mFileMonitor and we need to make
-  // decisions about whether we can write while holding mDataMonitor.
+  // directly as that's synchronized via mFileMutex and we need to make
+  // decisions about whether we can write while holding mDataMutex.
   bool mInitialized = false;
 };
 
 } // End namespace mozilla.
 
 #endif /* FILE_BLOCK_CACHE_H_ */
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -772,46 +772,46 @@ MediaDecoder::GetCurrentPrincipal()
   return mResource ? mResource->GetCurrentPrincipal() : nullptr;
 }
 
 void
 MediaDecoder::OnMetadataUpdate(TimedMetadata&& aMetadata)
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetOwner()->RemoveMediaTracks();
-  MetadataLoaded(nsAutoPtr<MediaInfo>(new MediaInfo(*aMetadata.mInfo)),
-                 Move(aMetadata.mTags),
+  MetadataLoaded(MakeUnique<MediaInfo>(*aMetadata.mInfo),
+                 UniquePtr<MetadataTags>(aMetadata.mTags.forget()),
                  MediaDecoderEventVisibility::Observable);
   FirstFrameLoaded(Move(aMetadata.mInfo),
                    MediaDecoderEventVisibility::Observable);
 }
 
 void
-MediaDecoder::MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
-                             nsAutoPtr<MetadataTags> aTags,
+MediaDecoder::MetadataLoaded(UniquePtr<MediaInfo> aInfo,
+                             UniquePtr<MetadataTags> aTags,
                              MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
 
   LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
       aInfo->mAudio.mChannels, aInfo->mAudio.mRate,
       aInfo->HasAudio(), aInfo->HasVideo());
 
   mMediaSeekable = aInfo->mMediaSeekable;
   mMediaSeekableOnlyInBufferedRanges = aInfo->mMediaSeekableOnlyInBufferedRanges;
-  mInfo = aInfo.forget();
+  mInfo = aInfo.release();
   GetOwner()->ConstructMediaTracks(mInfo);
 
   // Make sure the element and the frame (if any) are told about
   // our new size.
   if (aEventVisibility != MediaDecoderEventVisibility::Suppressed) {
     mFiredMetadataLoaded = true;
-    GetOwner()->MetadataLoaded(mInfo,
-                               nsAutoPtr<const MetadataTags>(aTags.forget()));
+    GetOwner()->MetadataLoaded(
+      mInfo, nsAutoPtr<const MetadataTags>(aTags.release()));
   }
   // Invalidate() will end up calling GetOwner()->UpdateMediaSize with the last
   // dimensions retrieved from the video frame container. The video frame
   // container contains more up to date dimensions than aInfo.
   // So we call Invalidate() after calling GetOwner()->MetadataLoaded to ensure
   // the media element has the latest dimensions.
   Invalidate();
 
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -567,18 +567,18 @@ protected:
   static constexpr auto DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED =
     media::TimeUnit::FromMicroseconds(250000);
 
 private:
   nsCString GetDebugInfo();
 
   // Called when the metadata from the media file has been loaded by the
   // state machine. Call on the main thread only.
-  void MetadataLoaded(nsAutoPtr<MediaInfo> aInfo,
-                      nsAutoPtr<MetadataTags> aTags,
+  void MetadataLoaded(UniquePtr<MediaInfo> aInfo,
+                      UniquePtr<MetadataTags> aTags,
                       MediaDecoderEventVisibility aEventVisibility);
 
   MediaEventSource<void>*
   DataArrivedEvent() override { return &mDataArrivedEvent; }
 
   // Called when the owner's activity changed.
   void NotifyCompositor();
 
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -212,33 +212,36 @@ MediaDecoderReader::GetBuffered()
 
 RefPtr<MediaDecoderReader::MetadataPromise>
 MediaDecoderReader::AsyncReadMetadata()
 {
   MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("MediaDecoderReader::AsyncReadMetadata");
 
   // Attempt to read the metadata.
-  RefPtr<MetadataHolder> metadata = new MetadataHolder();
-  nsresult rv = ReadMetadata(&metadata->mInfo, getter_Transfers(metadata->mTags));
-  metadata->mInfo.AssertValid();
+  MetadataHolder metadata;
+  metadata.mInfo = MakeUnique<MediaInfo>();
+  MetadataTags* tags = nullptr;
+  nsresult rv = ReadMetadata(metadata.mInfo.get(), &tags);
+  metadata.mTags.reset(tags);
+  metadata.mInfo->AssertValid();
 
   // Update the buffer ranges before resolving the metadata promise. Bug 1320258.
   UpdateBuffered();
 
   // We're not waiting for anything. If we didn't get the metadata, that's an
   // error.
-  if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) {
+  if (NS_FAILED(rv) || !metadata.mInfo->HasValidMedia()) {
     DECODER_WARN("ReadMetadata failed, rv=%" PRIx32 " HasValidMedia=%d",
-                 static_cast<uint32_t>(rv), metadata->mInfo.HasValidMedia());
+                 static_cast<uint32_t>(rv), metadata.mInfo->HasValidMedia());
     return MetadataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
   }
 
   // Success!
-  return MetadataPromise::CreateAndResolve(metadata, __func__);
+  return MetadataPromise::CreateAndResolve(Move(metadata), __func__);
 }
 
 class ReRequestVideoWithSkipTask : public Runnable
 {
 public:
   ReRequestVideoWithSkipTask(MediaDecoderReader* aReader,
                              const media::TimeUnit& aTimeThreshold)
     : mReader(aReader)
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -50,25 +50,20 @@ struct SeekRejectValue
   MOZ_IMPLICIT SeekRejectValue(nsresult aResult)
     : mType(MediaData::NULL_DATA), mError(aResult) { }
   SeekRejectValue(MediaData::Type aType, const MediaResult& aError)
     : mType(aType), mError(aError) { }
   MediaData::Type mType;
   MediaResult mError;
 };
 
-class MetadataHolder
+struct MetadataHolder
 {
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataHolder)
-  MediaInfo mInfo;
-  nsAutoPtr<MetadataTags> mTags;
-
-private:
-  virtual ~MetadataHolder() { }
+  UniquePtr<MediaInfo> mInfo;
+  UniquePtr<MetadataTags> mTags;
 };
 
 // Encapsulates the decoding and reading of media data. Reading can either
 // synchronous and done on the calling "decode" thread, or asynchronous and
 // performed on a background thread, with the result being returned by
 // callback.
 // Unless otherwise specified, methods and fields of this class can only
 // be accessed on the decode task queue.
@@ -77,18 +72,17 @@ class MediaDecoderReader
   friend class ReRequestVideoWithSkipTask;
   friend class ReRequestAudioTask;
 
   static const bool IsExclusive = true;
 
 public:
   using TrackSet = EnumSet<TrackInfo::TrackType>;
 
-  using MetadataPromise =
-    MozPromise<RefPtr<MetadataHolder>, MediaResult, IsExclusive>;
+  using MetadataPromise = MozPromise<MetadataHolder, MediaResult, IsExclusive>;
 
   template <typename Type>
   using DataPromise = MozPromise<RefPtr<Type>, MediaResult, IsExclusive>;
   using AudioDataPromise = DataPromise<AudioData>;
   using VideoDataPromise = DataPromise<VideoData>;
 
   using SeekPromise = MozPromise<media::TimeUnit, SeekRejectValue, IsExclusive>;
 
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -133,28 +133,28 @@ MediaDecoderReaderWrapper::Shutdown()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   mShutdown = true;
   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                      &MediaDecoderReader::Shutdown);
 }
 
 RefPtr<MediaDecoderReaderWrapper::MetadataPromise>
-MediaDecoderReaderWrapper::OnMetadataRead(RefPtr<MetadataHolder> aMetadata)
+MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder&& aMetadata)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   if (mShutdown) {
     return MetadataPromise::CreateAndReject(
       NS_ERROR_DOM_MEDIA_ABORT_ERR, __func__);
   }
 
   if (mStartTime.isNothing()) {
-    mStartTime.emplace(aMetadata->mInfo.mStartTime);
+    mStartTime.emplace(aMetadata.mInfo->mStartTime);
   }
-  return MetadataPromise::CreateAndResolve(aMetadata.forget(), __func__);
+  return MetadataPromise::CreateAndResolve(Move(aMetadata), __func__);
 }
 
 RefPtr<MediaDecoderReaderWrapper::MetadataPromise>
 MediaDecoderReaderWrapper::OnMetadataNotRead(const MediaResult& aError)
 {
   return MetadataPromise::CreateAndReject(aError, __func__);
 }
 
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -85,17 +85,17 @@ public:
   }
 
   void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
 
   void SetVideoBlankDecode(bool aIsBlankDecode);
 
 private:
   ~MediaDecoderReaderWrapper();
-  RefPtr<MetadataPromise> OnMetadataRead(RefPtr<MetadataHolder> aMetadata);
+  RefPtr<MetadataPromise> OnMetadataRead(MetadataHolder&& aMetadata);
   RefPtr<MetadataPromise> OnMetadataNotRead(const MediaResult& aError);
 
   const RefPtr<AbstractThread> mOwnerThread;
   const RefPtr<MediaDecoderReader> mReader;
 
   bool mShutdown = false;
   Maybe<media::TimeUnit> mStartTime;
 };
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -355,18 +355,18 @@ public:
 
     // Set mode to METADATA since we are about to read metadata.
     Resource()->SetReadMode(MediaCacheStream::MODE_METADATA);
 
     // We disconnect mMetadataRequest in Exit() so it is fine to capture
     // a raw pointer here.
     Reader()->ReadMetadata()
       ->Then(OwnerThread(), __func__,
-        [this] (RefPtr<MetadataHolder> aMetadata) {
-          OnMetadataRead(aMetadata);
+        [this] (MetadataHolder&& aMetadata) {
+          OnMetadataRead(Move(aMetadata));
         },
         [this] (const MediaResult& aError) {
           OnMetadataNotRead(aError);
         })
       ->Track(mMetadataRequest);
   }
 
   void Exit() override
@@ -392,17 +392,17 @@ public:
 
   void HandleResumeVideoDecoding(const TimeUnit&) override
   {
     // We never suspend video decoding in this state.
     MOZ_ASSERT(false, "Shouldn't have suspended video decoding.");
   }
 
 private:
-  void OnMetadataRead(MetadataHolder* aMetadata);
+  void OnMetadataRead(MetadataHolder&& aMetadata);
 
   void OnMetadataNotRead(const MediaResult& aError)
   {
     mMetadataRequest.Complete();
     SLOGW("Decode metadata failed, shutting down decoder");
     mMaster->DecodeError(aError);
   }
 
@@ -2125,24 +2125,24 @@ StateObject::SetSeekingState(SeekJob&& a
   }
 
   MOZ_ASSERT_UNREACHABLE("Unknown SeekTarget::Type.");
   return nullptr;
 }
 
 void
 MediaDecoderStateMachine::
-DecodeMetadataState::OnMetadataRead(MetadataHolder* aMetadata)
+DecodeMetadataState::OnMetadataRead(MetadataHolder&& aMetadata)
 {
   mMetadataRequest.Complete();
 
   // Set mode to PLAYBACK after reading metadata.
   Resource()->SetReadMode(MediaCacheStream::MODE_PLAYBACK);
 
-  mMaster->mInfo.emplace(aMetadata->mInfo);
+  mMaster->mInfo.emplace(*aMetadata.mInfo);
   mMaster->mMediaSeekable = Info().mMediaSeekable;
   mMaster->mMediaSeekableOnlyInBufferedRanges =
     Info().mMediaSeekableOnlyInBufferedRanges;
 
   if (Info().mMetadataDuration.isSome()) {
     mMaster->RecomputeDuration();
   } else if (Info().mUnadjustedMetadataEndTime.isSome()) {
     const TimeUnit unadjusted = Info().mUnadjustedMetadataEndTime.ref();
@@ -2161,18 +2161,18 @@ DecodeMetadataState::OnMetadataRead(Meta
          Reader()->IsAsync(),
          Reader()->VideoIsHardwareAccelerated(),
          mMaster->GetAmpleVideoFrames());
   }
 
   MOZ_ASSERT(mMaster->mDuration.Ref().isSome());
 
   mMaster->mMetadataLoadedEvent.Notify(
-    nsAutoPtr<MediaInfo>(new MediaInfo(aMetadata->mInfo)),
-    Move(aMetadata->mTags),
+    Move(aMetadata.mInfo),
+    Move(aMetadata.mTags),
     MediaDecoderEventVisibility::Observable);
 
   if (Info().IsEncrypted() && !mMaster->mCDMProxy) {
     // Metadata parsing was successful but we're still waiting for CDM caps
     // to become available so that we can build the correct decryptor/decoder.
     SetState<WaitForCDMState>();
   } else {
     SetState<DecodingFirstFrameState>();
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -209,18 +209,18 @@ public:
   }
 
   TimedMetadataEventSource& TimedMetadataEvent() {
     return mMetadataManager.TimedMetadataEvent();
   }
 
   MediaEventSource<void>& OnMediaNotSeekable() const;
 
-  MediaEventSourceExc<nsAutoPtr<MediaInfo>,
-                      nsAutoPtr<MetadataTags>,
+  MediaEventSourceExc<UniquePtr<MediaInfo>,
+                      UniquePtr<MetadataTags>,
                       MediaDecoderEventVisibility>&
   MetadataLoadedEvent() { return mMetadataLoadedEvent; }
 
   MediaEventSourceExc<nsAutoPtr<MediaInfo>,
                       MediaDecoderEventVisibility>&
   FirstFrameLoadedEvent() { return mFirstFrameLoadedEvent; }
 
   MediaEventSource<MediaEventType>&
@@ -664,18 +664,18 @@ private:
   MozPromiseRequestHolder<GenericPromise> mMediaSinkAudioPromise;
   MozPromiseRequestHolder<GenericPromise> mMediaSinkVideoPromise;
 
   MediaEventListener mAudioQueueListener;
   MediaEventListener mVideoQueueListener;
   MediaEventListener mAudibleListener;
   MediaEventListener mOnMediaNotSeekable;
 
-  MediaEventProducerExc<nsAutoPtr<MediaInfo>,
-                        nsAutoPtr<MetadataTags>,
+  MediaEventProducerExc<UniquePtr<MediaInfo>,
+                        UniquePtr<MetadataTags>,
                         MediaDecoderEventVisibility> mMetadataLoadedEvent;
   MediaEventProducerExc<nsAutoPtr<MediaInfo>,
                         MediaDecoderEventVisibility> mFirstFrameLoadedEvent;
 
   MediaEventProducer<MediaEventType> mOnPlaybackEvent;
   MediaEventProducer<MediaResult> mOnPlaybackErrorEvent;
 
   MediaEventProducer<DecoderDoctorEvent> mOnDecoderDoctorEvent;
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1313,20 +1313,19 @@ RefPtr<MediaDecoderReader::MetadataPromi
 MediaFormatReader::AsyncReadMetadata()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   MOZ_DIAGNOSTIC_ASSERT(mMetadataPromise.IsEmpty());
 
   if (mInitDone) {
     // We are returning from dormant.
-    RefPtr<MetadataHolder> metadata = new MetadataHolder();
-    metadata->mInfo = mInfo;
-    metadata->mTags = nullptr;
-    return MetadataPromise::CreateAndResolve(metadata, __func__);
+    MetadataHolder metadata;
+    metadata.mInfo = MakeUnique<MediaInfo>(mInfo);
+    return MetadataPromise::CreateAndResolve(Move(metadata), __func__);
   }
 
   RefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);
 
   mDemuxer->Init()
     ->Then(OwnerThread(), __func__, this,
            &MediaFormatReader::OnDemuxerInitDone,
            &MediaFormatReader::OnDemuxerInitFailed)
@@ -1491,26 +1490,26 @@ MediaFormatReader::MaybeResolveMetadataP
   TimeUnit startTime =
     std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
              mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));
 
   if (!startTime.IsInfinite()) {
     mInfo.mStartTime = startTime; // mInfo.mStartTime is initialized to 0.
   }
 
-  RefPtr<MetadataHolder> metadata = new MetadataHolder();
-  metadata->mInfo = mInfo;
-  metadata->mTags = mTags->Count() ? mTags.release() : nullptr;
+  MetadataHolder metadata;
+  metadata.mInfo = MakeUnique<MediaInfo>(mInfo);
+  metadata.mTags = mTags->Count() ? Move(mTags) : nullptr;
 
   // We now have all the informations required to calculate the initial buffered
   // range.
   mHasStartTime = true;
   UpdateBuffered();
 
-  mMetadataPromise.Resolve(metadata, __func__);
+  mMetadataPromise.Resolve(Move(metadata), __func__);
 }
 
 bool
 MediaFormatReader::IsEncrypted() const
 {
   return (HasAudio() && mInfo.mAudio.mCrypto.mValid)
          || (HasVideo() && mInfo.mVideo.mCrypto.mValid);
 }
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -657,31 +657,18 @@ GetSupportedCapabilities(const CodecType
     // parameters. The case-sensitivity of string comparisons is determined by
     // the appropriate RFC or other specification.
     // (Note: codecs array is 'parameter').
 
     // If media types is empty:
     if (codecs.IsEmpty()) {
       // If container normatively implies a specific set of codecs and codec constraints:
       // Let parameters be that set.
-      if (isMP4) {
-        if (aCodecType == Audio) {
-          codecs.AppendElement(EME_CODEC_AAC);
-        } else if (aCodecType == Video) {
-          codecs.AppendElement(EME_CODEC_H264);
-        }
-      } else if (isWebM) {
-        if (aCodecType == Audio) {
-          codecs.AppendElement(EME_CODEC_VORBIS);
-        } else if (aCodecType == Video) {
-          codecs.AppendElement(EME_CODEC_VP8);
-        }
-      }
       // Otherwise: Continue to the next iteration.
-      // (Note: all containers we support have implied codecs, so don't continue here.)
+      continue;
     }
 
     // If container type is not strictly a audio/video type, continue to the next iteration.
     const auto majorType = GetMajorType(containerType.Type());
     if (majorType == Invalid) {
       EME_LOG("MediaKeySystemConfiguration (label='%s') "
               "MediaKeySystemMediaCapability('%s','%s') unsupported; "
               "MIME type is not an audio or video MIME type.",
@@ -934,18 +921,23 @@ GetSupportedConfig(const KeySystemConfig
       config.mPersistentState = MediaKeysRequirement::Required;
     }
   }
   // Set the sessionTypes member of accumulated configuration to session types.
   config.mSessionTypes.Construct(Move(sessionTypes));
 
   // If the videoCapabilities and audioCapabilities members in candidate
   // configuration are both empty, return NotSupported.
-  // TODO: Most sites using EME still don't pass capabilities, so we
-  // can't reject on it yet without breaking them. So add this later.
+  if (aCandidate.mAudioCapabilities.IsEmpty() &&
+      aCandidate.mVideoCapabilities.IsEmpty()) {
+    EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+            "no supported audio or video capabilities specified",
+            NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+    return false;
+  }
 
   // If the videoCapabilities member in candidate configuration is non-empty:
   if (!aCandidate.mVideoCapabilities.IsEmpty()) {
     // Let video capabilities be the result of executing the Get Supported
     // Capabilities for Audio/Video Type algorithm on Video, candidate
     // configuration's videoCapabilities member, accumulated configuration,
     // and restrictions.
     Sequence<MediaKeySystemMediaCapability> caps =
--- a/dom/media/fmp4/MP4Demuxer.cpp
+++ b/dom/media/fmp4/MP4Demuxer.cpp
@@ -182,17 +182,16 @@ MP4Demuxer::Init()
   if (NS_FAILED(audioTrackCount.Result()) && result == NS_OK) {
     result = Move(audioTrackCount.Result());
   }
   if (NS_FAILED(videoTrackCount.Result()) && result == NS_OK) {
     result = Move(videoTrackCount.Result());
   }
 
   if (audioTrackCount.Ref() != 0) {
-    mAudioDemuxers.SetLength(audioTrackCount.Ref());
     for (size_t i = 0; i < audioTrackCount.Ref(); i++) {
       mp4_demuxer::MP4Metadata::ResultAndTrackInfo info =
         metadata.GetTrackInfo(TrackInfo::kAudioTrack, i);
       if (!info.Ref()) {
         if (MediaPrefs::MediaWarningsAsErrors()) {
           return InitPromise::CreateAndReject(
             MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
                         RESULT_DETAIL("Invalid MP4 audio track (%s)",
@@ -211,23 +210,22 @@ MP4Demuxer::Init()
       mp4_demuxer::MP4Metadata::ResultAndIndice indices =
         metadata.GetTrackIndice(info.Ref()->mTrackId);
       if (!indices.Ref()) {
         if (NS_FAILED(info.Result()) && result == NS_OK) {
           result = Move(indices.Result());
         }
         continue;
       }
-      mAudioDemuxers[i] =
-        new MP4TrackDemuxer(this, Move(info.Ref()), *indices.Ref().get());
+      mAudioDemuxers.AppendElement(
+        new MP4TrackDemuxer(this, Move(info.Ref()), *indices.Ref().get()));
     }
   }
 
   if (videoTrackCount.Ref() != 0) {
-    mVideoDemuxers.SetLength(videoTrackCount.Ref());
     for (size_t i = 0; i < videoTrackCount.Ref(); i++) {
       mp4_demuxer::MP4Metadata::ResultAndTrackInfo info =
         metadata.GetTrackInfo(TrackInfo::kVideoTrack, i);
       if (!info.Ref()) {
         if (MediaPrefs::MediaWarningsAsErrors()) {
           return InitPromise::CreateAndReject(
             MediaResult(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
                         RESULT_DETAIL("Invalid MP4 video track (%s)",
@@ -246,18 +244,18 @@ MP4Demuxer::Init()
       mp4_demuxer::MP4Metadata::ResultAndIndice indices =
         metadata.GetTrackIndice(info.Ref()->mTrackId);
       if (!indices.Ref()) {
         if (NS_FAILED(info.Result()) && result == NS_OK) {
           result = Move(indices.Result());
         }
         continue;
       }
-      mVideoDemuxers[i] =
-        new MP4TrackDemuxer(this, Move(info.Ref()), *indices.Ref().get());
+      mVideoDemuxers.AppendElement(
+        new MP4TrackDemuxer(this, Move(info.Ref()), *indices.Ref().get()));
     }
   }
 
   mp4_demuxer::MP4Metadata::ResultAndCryptoFile cryptoFile =
     metadata.Crypto();
   if (NS_FAILED(cryptoFile.Result()) && result == NS_OK) {
     result = Move(cryptoFile.Result());
   }
--- a/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegAudioDecoder.cpp
@@ -148,30 +148,30 @@ FFmpegAudioDecoder<LIBAV_VER>::ProcessDe
     if (bytesConsumed < 0) {
       NS_WARNING("FFmpeg audio decoder error.");
       return DecodePromise::CreateAndReject(
         MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
                     RESULT_DETAIL("FFmpeg audio error:%d", bytesConsumed)),
         __func__);
     }
 
-    if (mFrame->format != AV_SAMPLE_FMT_FLT &&
-        mFrame->format != AV_SAMPLE_FMT_FLTP &&
-        mFrame->format != AV_SAMPLE_FMT_S16 &&
-        mFrame->format != AV_SAMPLE_FMT_S16P &&
-        mFrame->format != AV_SAMPLE_FMT_S32 &&
-        mFrame->format != AV_SAMPLE_FMT_S32P) {
-      return DecodePromise::CreateAndReject(
-        MediaResult(
-          NS_ERROR_DOM_MEDIA_DECODE_ERR,
-          RESULT_DETAIL("FFmpeg audio decoder outputs unsupported audio format")),
-        __func__);
-    }
-
     if (decoded) {
+      if (mFrame->format != AV_SAMPLE_FMT_FLT &&
+          mFrame->format != AV_SAMPLE_FMT_FLTP &&
+          mFrame->format != AV_SAMPLE_FMT_S16 &&
+          mFrame->format != AV_SAMPLE_FMT_S16P &&
+          mFrame->format != AV_SAMPLE_FMT_S32 &&
+          mFrame->format != AV_SAMPLE_FMT_S32P) {
+        return DecodePromise::CreateAndReject(
+          MediaResult(
+            NS_ERROR_DOM_MEDIA_DECODE_ERR,
+            RESULT_DETAIL(
+              "FFmpeg audio decoder outputs unsupported audio format")),
+          __func__);
+      }
       uint32_t numChannels = mCodecContext->channels;
       AudioConfig::ChannelLayout layout(numChannels);
       if (!layout.IsValid()) {
         return DecodePromise::CreateAndReject(
           MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                       RESULT_DETAIL("Unsupported channel layout:%u", numChannels)),
           __func__);
       }
--- a/dom/media/test/eme.js
+++ b/dom/media/test/eme.js
@@ -1,14 +1,14 @@
 const CLEARKEY_KEYSYSTEM = "org.w3.clearkey";
 
 const gCencMediaKeySystemConfig = [{
   initDataTypes: ['cenc'],
-  videoCapabilities: [{ contentType: 'video/mp4' }],
-  audioCapabilities: [{ contentType: 'audio/mp4' }],
+  videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }],
+  audioCapabilities: [{ contentType: 'audio/mp4; codecs="mp4a.40.2"' }],
 }];
 
 function IsMacOSSnowLeopardOrEarlier() {
   var re = /Mac OS X (\d+)\.(\d+)/;
   var ver = navigator.userAgent.match(re);
   if (!ver || ver.length != 3) {
     return false;
   }
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -541,16 +541,23 @@ var gErrorTests = [
 // Windows' H.264 decoder cannot handle H.264 streams with resolution
 // less than 48x48 pixels. We refuse to play and error on such streams.
 if (manifestNavigator().userAgent.includes