merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 23 Nov 2015 14:08:50 +0100
changeset 273760 d3d286102ba7f8801e9dfe12d534f49554ba50c0
parent 273759 8b1fc0961a076e35646d0472a81feefc0074558c (current diff)
parent 273679 e722799fa71f653c31454612564c213d09566219 (diff)
child 273761 eff4131a3e4caf33761bd52cb9a5f2dd5601c4f1
child 273839 8d9c87b4c312bd778eb9c06ddde0249b57393551
child 273847 f73d0397bce2f1c66e12ec8553d29550e5c0d90f
child 273995 5052c7b0634f91e50ee9fbf4d1c741c7eff074ae
push id68366
push usercbook@mozilla.com
push dateMon, 23 Nov 2015 13:31:58 +0000
treeherdermozilla-inbound@eff4131a3e4c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.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 mozilla-inbound to mozilla-central a=merge
browser/base/content/browser.js
dom/devicestorage/ipc/ipc.json
dom/devicestorage/ipc/mochitest.ini
dom/devicestorage/ipc/test_ipc.html
dom/quota/QuotaManager.cpp
dom/quota/Utilities.h
dom/quota/nsIQuotaManager.idl
dom/quota/nsIQuotaRequest.idl
dom/quota/nsIUsageCallback.idl
layout/reftests/reftest.list
--- a/b2g/branding/browserhtml/content/jar.mn
+++ b/b2g/branding/browserhtml/content/jar.mn
@@ -1,10 +1,10 @@
 # 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/.
 
 chrome.jar:
-% content branding %content/branding/
+% content branding %content/branding/ contentaccessible=yes
   content/branding/about.png                     (about.png)
   content/branding/logoWordmark.png              (logoWordmark.png)
   content/branding/logo.png                      (logo.png)
   content/branding/favicon32.png                 (favicon32.png)
--- a/b2g/branding/horizon/content/jar.mn
+++ b/b2g/branding/horizon/content/jar.mn
@@ -1,10 +1,10 @@
 # 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/.
 
 chrome.jar:
-% content branding %content/branding/
+% content branding %content/branding/ contentaccessible=yes
   content/branding/about.png                     (about.png)
   content/branding/logoWordmark.png              (logoWordmark.png)
   content/branding/logo.png                      (logo.png)
   content/branding/favicon32.png                 (favicon32.png)
--- a/b2g/branding/official/content/jar.mn
+++ b/b2g/branding/official/content/jar.mn
@@ -1,10 +1,10 @@
 # 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/.
 
 chrome.jar:
-% content branding %content/branding/
+% content branding %content/branding/ contentaccessible=yes
   content/branding/about.png                     (about.png)
   content/branding/logoWordmark.png              (logoWordmark.png)
   content/branding/logo.png                      (logo.png)
   content/branding/favicon32.png                 (favicon32.png)
--- a/b2g/branding/unofficial/content/jar.mn
+++ b/b2g/branding/unofficial/content/jar.mn
@@ -1,10 +1,10 @@
 # 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/.
 
 chrome.jar:
-% content branding %content/branding/
+% content branding %content/branding/ contentaccessible=yes
   content/branding/about.png                     (about.png)
   content/branding/logoWordmark.png              (logoWordmark.png)
   content/branding/logo.png                      (logo.png)
   content/branding/favicon32.png                 (favicon32.png)
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -1,16 +1,16 @@
 #filter substitution
 # 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/.
 
 
 chrome.jar:
-% content branding %content/branding/
+% content branding %content/branding/ contentaccessible=yes
 % content b2g %content/
 
   content/arrow.svg                     (content/arrow.svg)
 * content/settings.js                   (content/settings.js)
 * content/shell.html                    (content/shell.html)
 * content/shell.js                      (content/shell.js)
   content/shell_remote.html             (content/shell_remote.html)
   content/shell_remote.js               (content/shell_remote.js)
--- a/b2g/locales/en-US/chrome/overrides/appstrings.properties
+++ b/b2g/locales/en-US/chrome/overrides/appstrings.properties
@@ -27,13 +27,14 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -418,16 +418,17 @@
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
         <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
+        <h1 id="et_forbiddenBlocked">&forbiddenBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
         <h1 id="et_weakCryptoUsed">&weakCryptoUsed.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
@@ -447,16 +448,17 @@
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
         <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
+        <div id="ed_forbiddenBlocked">&forbiddenBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc;</div>
         <div id="learn_more_ssl3">&sslv3Used.learnMore;</div>
         <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc;</div>
         <div id="learn_more_weak_crypto">&weakCryptoUsed.learnMore;</div>
       </div>
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -63,121 +63,86 @@
       function getHostString()
       {
         try {
           return document.location.hostname;
         } catch (e) {
           return getURL();
         }
       }
-      
+
       function initPage()
       {
-        // Handoff to the appropriate initializer, based on error code
+        var error = "";
         switch (getErrorCode()) {
           case "malwareBlocked" :
-            initPage_malware();
+            error = "malware";
             break;
           case "phishingBlocked" :
-            initPage_phishing();
+            error = "phishing";
             break;
           case "unwantedBlocked" :
-            initPage_unwanted();
+            error = "unwanted";
             break;
+          case "forbiddenBlocked" :
+            error = "forbidden";
+            break;
+          default:
+            return;
         }
-      }        
-      
-      /**
-       * Initialize custom strings and functionality for blocked malware case
-       */
-      function initPage_malware()
-      {
-        // Remove phishing and unwanted strings
-        var el = document.getElementById("errorTitleText_phishing");
-        el.parentNode.removeChild(el);
+
+        var el;
+
+        if (error !== "malware") {
+          el = document.getElementById("errorTitleText_malware");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_malware");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_malware");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorShortDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_phishing");
-        el.parentNode.removeChild(el);
+        if (error !== "phishing") {
+          el = document.getElementById("errorTitleText_phishing");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_phishing");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_phishing");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorTitleText_unwanted");
-        el.parentNode.removeChild(el);
+        if (error !== "unwanted") {
+          el = document.getElementById("errorTitleText_unwanted");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_unwanted");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorLongDescText_unwanted");
+          el.parentNode.removeChild(el);
+        }
 
-        el = document.getElementById("errorShortDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_unwanted");
-        el.parentNode.removeChild(el);
+        if (error !== "forbidden") {
+          el = document.getElementById("errorTitleText_forbidden");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("errorShortDescText_forbidden");
+          el.parentNode.removeChild(el);
+        } else {
+          el = document.getElementById("ignoreWarningButton");
+          el.parentNode.removeChild(el);
+          el = document.getElementById("reportButton");
+          el.parentNode.removeChild(el);
+        }
 
         // Set sitename
-        document.getElementById("malware_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_malware")
+        document.getElementById(error + "_sitename").textContent = getHostString();
+        document.title = document.getElementById("errorTitleText_" + error)
                                  .innerHTML;
-      }
-      
-      /**
-       * Initialize custom strings and functionality for blocked malware case
-       */
-      function initPage_unwanted()
-      {
-        // Remove phishing and malware strings
-        var el = document.getElementById("errorTitleText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_phishing");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorTitleText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_malware");
-        el.parentNode.removeChild(el);
 
-        // Set sitename
-        document.getElementById("unwanted_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_unwanted")
-                                 .innerHTML;
-      }
-      
-      /**
-       * Initialize custom strings and functionality for blocked phishing case
-       */
-      function initPage_phishing()
-      {
-        // Remove malware and unwanted strings
-        var el = document.getElementById("errorTitleText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_malware");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorTitleText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorShortDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        el = document.getElementById("errorLongDescText_unwanted");
-        el.parentNode.removeChild(el);
-
-        // Set sitename
-        document.getElementById("phishing_sitename").textContent = getHostString();
-        document.title = document.getElementById("errorTitleText_phishing")
-                                 .innerHTML;
+        // Inform the test harness that we're done loading the page
+        var event = new CustomEvent("AboutBlockedLoaded");
+        document.dispatchEvent(event);
       }
     ]]></script>
     <style type="text/css">
       /* Style warning button to look like a small text link in the
          bottom right. This is preferable to just using a text link
          since there is already a mechanism in browser.js for trapping
          oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
       #ignoreWarningButton {
@@ -208,25 +173,27 @@
   <body dir="&locale.dir;">
     <div id="errorPageContainer">
     
       <!-- Error Title -->
       <div id="errorTitle">
         <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
         <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
         <h1 id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
+        <h1 id="errorTitleText_forbidden">&safeb.blocked.forbiddenPage.title;</h1>
       </div>
       
       <div id="errorLongContent">
       
         <!-- Short Description -->
         <div id="errorShortDesc">
           <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
           <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
           <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
+          <p id="errorShortDescText_forbidden">&safeb.blocked.forbiddenPage.shortDesc;</p>
         </div>
 
         <!-- Long Description -->
         <div id="errorLongDesc">
           <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
           <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
           <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
         </div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2953,44 +2953,55 @@ var BrowserOnClick = {
         break;
 
     }
   },
 
   onAboutBlocked: function (elementId, reason, isTopFrame, location) {
     // Depending on what page we are displaying here (malware/phishing/unwanted)
     // use the right strings and links for each.
-    let bucketName = "WARNING_PHISHING_PAGE_";
+    let bucketName = "";
+    let sendTelemetry = false;
     if (reason === 'malware') {
+      sendTelemetry = true;
       bucketName = "WARNING_MALWARE_PAGE_";
+    } else if (reason === 'phishing') {
+      sendTelemetry = true;
+      bucketName = "WARNING_PHISHING_PAGE_";
     } else if (reason === 'unwanted') {
+      sendTelemetry = true;
       bucketName = "WARNING_UNWANTED_PAGE_";
     }
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
     let nsISecTel = Ci.nsISecurityUITelemetry;
     bucketName += isTopFrame ? "TOP_" : "FRAME_";
     switch (elementId) {
       case "getMeOutButton":
-        secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+        if (sendTelemetry) {
+          secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+        }
         getMeOutOfHere();
         break;
 
       case "reportButton":
         // This is the "Why is this site blocked" button. We redirect
         // to the generic page describing phishing/malware protection.
 
         // We log even if malware/phishing/unwanted info URL couldn't be found:
         // the measurement is for how many users clicked the WHY BLOCKED button
-        secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
-
+        if (sendTelemetry) {
+          secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
+        }
         openHelpLink("phishing-malware", false, "current");
         break;
 
       case "ignoreWarningButton":
-        secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+        if (sendTelemetry) {
+          secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+        }
         this.ignoreWarningButton(reason);
         break;
     }
   },
 
   /**
    * This functions prevents navigation from happening directly through the <a>
    * link in about:newtab (which is loaded in the parent and therefore would load
@@ -3049,16 +3060,18 @@ var BrowserOnClick = {
         callback: function() {
           openUILinkIn(gSafeBrowsing.getReportURL('PhishMistake'), 'tab');
         }
       };
     } else if (reason === 'unwanted') {
       title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
       // There is no button for reporting errors since Google doesn't currently
       // provide a URL endpoint for these reports.
+    } else {
+      return; // no notifications for forbidden sites
     }
 
     let notificationBox = gBrowser.getNotificationBox();
     let value = "blocked-badware-page";
 
     let previousNotification = notificationBox.getNotificationWithValue(value);
     if (previousNotification) {
       notificationBox.removeNotification(previousNotification);
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -455,16 +455,18 @@ var ClickEventHandler = {
   },
 
   onAboutBlocked: function (targetElement, ownerDoc) {
     var reason = 'phishing';
     if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
       reason = 'malware';
     } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
       reason = 'unwanted';
+    } else if (/e=forbiddenBlocked/.test(ownerDoc.documentURI)) {
+      reason = 'forbidden';
     }
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
       reason: reason,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
     });
   },
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.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/. */
 
 Components.utils.import("resource:///modules/SitePermissions.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 
-const nsIQuotaManager = Components.interfaces.nsIQuotaManager;
+const nsIQuotaManagerService = Components.interfaces.nsIQuotaManagerService;
 
 var gPermURI;
 var gUsageRequest;
 
 var gPermissions = SitePermissions.listPermissions();
 gPermissions.push("plugins");
 
 var permissionObserver = {
@@ -181,68 +181,70 @@ function setRadioState(aPartId, aValue)
 
 function initIndexedDBRow()
 {
   let row = document.getElementById("perm-indexedDB-row");
   let extras = document.getElementById("perm-indexedDB-extras");
 
   row.appendChild(extras);
 
-  var quotaManager = Components.classes["@mozilla.org/dom/quota/manager;1"]
-                               .getService(nsIQuotaManager);
+  var quotaManagerService =
+    Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+              .getService(nsIQuotaManagerService);
   let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                             .getService(Components.interfaces.nsIScriptSecurityManager)
                             .createCodebasePrincipal(gPermURI, {});
   gUsageRequest =
-    quotaManager.getUsageForPrincipal(principal, onIndexedDBUsageCallback);
+    quotaManagerService.getUsageForPrincipal(principal,
+                                             onIndexedDBUsageCallback);
 
   var status = document.getElementById("indexedDBStatus");
   var button = document.getElementById("indexedDBClear");
 
   status.value = "";
   status.setAttribute("hidden", "true");
   button.setAttribute("hidden", "true");
 }
 
 function onIndexedDBClear()
 {
   let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                             .getService(Components.interfaces.nsIScriptSecurityManager)
                             .createCodebasePrincipal(gPermURI, {});
 
-  Components.classes["@mozilla.org/dom/quota/manager;1"]
-            .getService(nsIQuotaManager)
+  Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+            .getService(nsIQuotaManagerService)
             .clearStoragesForPrincipal(principal);
 
   Components.classes["@mozilla.org/serviceworkers/manager;1"]
             .getService(Components.interfaces.nsIServiceWorkerManager)
             .removeAndPropagate(gPermURI.host);
 
   SitePermissions.remove(gPermURI, "indexedDB");
   initIndexedDBRow();
 }
 
-function onIndexedDBUsageCallback(principal, usage, fileUsage)
+function onIndexedDBUsageCallback(request)
 {
-  let uri = principal.URI;
+  let uri = request.principal.URI;
   if (!uri.equals(gPermURI)) {
     throw new Error("Callback received for bad URI: " + uri);
   }
 
-  if (usage) {
+  if (request.usage) {
     if (!("DownloadUtils" in window)) {
       Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
     }
 
     var status = document.getElementById("indexedDBStatus");
     var button = document.getElementById("indexedDBClear");
 
     status.value =
       gBundle.getFormattedString("indexedDBUsage",
-                                 DownloadUtils.convertByteUnits(usage));
+                                 DownloadUtils.convertByteUnits(request.usage));
     status.removeAttribute("hidden");
     button.removeAttribute("hidden");
   }
 }
 
 function fillInPluginPermissionTemplate(aPluginName, aPermissionString) {
   let permPluginTemplate = document.getElementById("permPluginTemplate").cloneNode(true);
   permPluginTemplate.setAttribute("permString", aPermissionString);
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -355,25 +355,35 @@ var gTests = [
         attributeFilter: ["aria-expanded"],
       });
       yield deferred.promise;
 
       // Empty the search input, causing the suggestions to be hidden.
       EventUtils.synthesizeKey("a", { accelKey: true });
       EventUtils.synthesizeKey("VK_DELETE", {});
       ok(table.hidden, "Search suggestion table hidden");
+
+      try {
+        Services.search.removeEngine(engine);
+      } catch (ex) { }
     });
   }
 },
 {
   desc: "Clicking suggestion list while composing",
   setup: function() {},
   run: function()
   {
     return Task.spawn(function* () {
+      // Add a test engine that provides suggestions and switch to it.
+      let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+      let p = promiseContentSearchChange(engine.name);
+      Services.search.currentEngine = engine;
+      yield p;
+
       // Start composition and type "x"
       let input = gBrowser.contentDocument.getElementById("searchText");
       input.focus();
       EventUtils.synthesizeComposition({ type: "compositionstart", data: "" },
                                        gBrowser.contentWindow);
       EventUtils.synthesizeCompositionChange({
         composition: {
           string: "x",
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/bug_1182546.xml
@@ -0,0 +1,5 @@
+<!DOCTYPE html [
+  <!ENTITY % passwordManagerDTD SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd">
+  %passwordManagerDTD;
+]>
+<window>&savedLogins.title;</window>
--- a/browser/base/content/test/general/mochitest.ini
+++ b/browser/base/content/test/general/mochitest.ini
@@ -20,21 +20,23 @@ support-files =
   offlineChild2.cacheManifest
   offlineChild2.cacheManifest^headers^
   offlineChild2.html
   offlineEvent.cacheManifest
   offlineEvent.cacheManifest^headers^
   offlineEvent.html
   subtst_contextmenu.html
   video.ogg
+  bug_1182546.xml
 
 [test_bug364677.html]
 [test_bug395533.html]
 [test_contextmenu.html]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" || (os == 'mac' && os_version != '10.6') # disabled on Linux due to bug 513558, on Mac after 10.6 due to bug 792304
 [test_contextmenu_input.html]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" || e10s # disabled on Linux due to bug 513558
 [test_feed_discovery.html]
 skip-if = e10s
 [test_offlineNotification.html]
 skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
 [test_offline_gzip.html]
 skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
+[test_bug1182546.html]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/test_bug1182546.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1182546
+-->
+<head>
+  <title>Bug 1182546 - Test block loading DTD from random page</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe id="testframe" src="bug_1182546.xml"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+  // make sure the DTD loader (nsExpatDriver) prevents accessing chrome: from random pages
+  var childNodes = testframe.contentDocument.documentElement.childNodes;
+
+  // make sure '&savedLogins.title;' from bug_1182546.xml does not translate into 'Saved Logins'
+  // the URL 'chrome://passwordmgr/locale/passwordManager.dtd' should not be accessible from content
+  var nodeValue = childNodes[0].nodeValue;
+  isnot(nodeValue, "Saved Logins",
+        "expatDriver should prevent accessing &savedLogins.title;");
+  ok(nodeValue.startsWith("XML Parsing Error: undefined entity"),
+  	 "expatDriver should not allow accessing chrome:");
+});
+
+addLoadEvent(SimpleTest.finish);
+
+</script>
+</body>
+</html>
--- a/browser/components/migration/tests/unit/test_Chrome_cookies.js
+++ b/browser/components/migration/tests/unit/test_Chrome_cookies.js
@@ -23,27 +23,28 @@ add_task(function* () {
   const PROFILE = {
     id: "Default",
     name: "Person 1",
   };
 
   // Migrate unencrypted cookies.
   yield promiseMigration(migrator, MigrationUtils.resourceTypes.COOKIES, PROFILE);
 
-  do_register_cleanup(() => {
-    ForgetAboutSite.removeDataFromDomain(COOKIE.host);
-    Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 0,
-                 "There are no cookies after cleanup");
-  });
   Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 1,
                "Migrated the expected number of unencrypted cookies");
   Assert.equal(Services.cookies.countCookiesFromHost("encryptedcookie.invalid"), 0,
                "Migrated the expected number of encrypted cookies");
 
   // Now check the cookie details.
   let enumerator = Services.cookies.getCookiesFromHost(COOKIE.host);
   Assert.ok(enumerator.hasMoreElements(), "Cookies available");
   let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
 
   for (let prop of Object.keys(COOKIE)) {
     Assert.equal(foundCookie[prop], COOKIE[prop], "Check cookie " + prop);
   }
+
+  // Cleanup.
+  ForgetAboutSite.removeDataFromDomain(COOKIE.host);
+  Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 0,
+               "There are no cookies after cleanup");
+
 });
--- a/browser/components/safebrowsing/content/test/browser.ini
+++ b/browser/components/safebrowsing/content/test/browser.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 support-files = head.js
 
+[browser_forbidden.js]
 [browser_bug400731.js]
 skip-if = e10s
 [browser_bug415846.js]
 skip-if = true
 # Disabled because it seems to now touch network resources
 # skip-if = os == "mac"
 # Disabled on Mac because of its bizarre special-and-unique
 # snowflake of a help menu.
new file mode 100644
--- /dev/null
+++ b/browser/components/safebrowsing/content/test/browser_forbidden.js
@@ -0,0 +1,40 @@
+/* Ensure that pages in the forbidden list are blocked. */
+
+const PREF_FORBIDDEN_ENABLED = "browser.safebrowsing.forbiddenURIs.enabled";
+const BENIGN_PAGE = "http://example.com/";
+const FORBIDDEN_PAGE = "http://www.itisatrap.org/firefox/forbidden.html";
+var tabbrowser = null;
+
+registerCleanupFunction(function() {
+  tabbrowser = null;
+  Services.prefs.clearUserPref(PREF_FORBIDDEN_ENABLED);
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+});
+
+function testBenignPage(window) {
+  info("Non-forbidden content must not be blocked");
+  var getmeout_button = window.document.getElementById("getMeOutButton");
+  var ignorewarning_button = window.document.getElementById("ignoreWarningButton");
+  ok(!getmeout_button, "GetMeOut button not present");
+  ok(!ignorewarning_button, "IgnoreWarning button not present");
+}
+
+function testForbiddenPage(window) {
+  info("Forbidden content must be blocked");
+  ok(true, "about:blocked was shown");
+}
+
+add_task(function* testNormalBrowsing() {
+  tabbrowser = gBrowser;
+  let tab = tabbrowser.selectedTab = tabbrowser.addTab();
+
+  info("Load a test page that's not forbidden");
+  yield promiseTabLoadEvent(tab, BENIGN_PAGE, "load");
+  testBenignPage(tab.ownerDocument.defaultView);
+
+  info("Load a test page that is forbidden");
+  yield promiseTabLoadEvent(tab, FORBIDDEN_PAGE, "AboutBlockedLoaded");
+  testForbiddenPage(tab.ownerDocument.defaultView);
+});
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -1,5 +1,57 @@
-// Force SafeBrowsing to be initialized for the tests
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+  "resource://gre/modules/Task.jsm");
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ *        The tab to load into.
+ * @param [optional] url
+ *        The url to load, or the current url.
+ * @param [optional] event
+ *        The load event type to wait for.  Defaults to "load".
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url, eventType="load")
+{
+  let deferred = Promise.defer();
+  info("Wait tab event: " + eventType);
+
+  function handle(event) {
+    if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+        event.target.location.href == "about:blank" ||
+        (url && event.target.location.href != url)) {
+      info("Skipping spurious '" + eventType + "'' event" +
+           " for " + event.target.location.href);
+      return;
+    }
+    clearTimeout(timeout);
+    tab.linkedBrowser.removeEventListener(eventType, handle, true);
+    info("Tab event received: " + eventType);
+    deferred.resolve(event);
+  }
+
+  let timeout = setTimeout(() => {
+    tab.linkedBrowser.removeEventListener(eventType, handle, true);
+    deferred.reject(new Error("Timed out while waiting for a '" + eventType + "'' event"));
+  }, 30000);
+
+  tab.linkedBrowser.addEventListener(eventType, handle, true, true);
+  if (url) {
+    tab.linkedBrowser.loadURI(url);
+  }
+  return deferred.promise;
+}
+
+Services.prefs.setCharPref("urlclassifier.forbiddenTable", "test-forbid-simple");
 Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
 Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
+Services.prefs.setBoolPref("browser.safebrowsing.forbiddenURIs.enabled", true);
 SafeBrowsing.init();
-
--- a/browser/installer/windows/moz.build
+++ b/browser/installer/windows/moz.build
@@ -2,10 +2,10 @@
 # vim: set filetype=python:
 # 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/.
 
 DEFINES['APP_VERSION'] = CONFIG['FIREFOX_VERSION']
 
 DEFINES['MOZ_APP_NAME'] = CONFIG['MOZ_APP_NAME']
-DEFINES['MOZ_APP_DISPLAYNAME'] = "'%s'" % CONFIG['MOZ_APP_DISPLAYNAME']
+DEFINES['MOZ_APP_DISPLAYNAME'] = CONFIG['MOZ_APP_DISPLAYNAME']
 DEFINES['MOZILLA_VERSION'] = CONFIG['MOZILLA_VERSION']
--- a/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
+++ b/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
@@ -3,21 +3,26 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY safeb.palm.accept.label "Get me out of here!">
 <!ENTITY safeb.palm.decline.label "Ignore this warning">
 <!ENTITY safeb.palm.notforgery.label2 "This isn't a web forgery…">
 <!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
 
 <!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
-<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!-- Localization note (safeb.blocked.malwarePage.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
 
 <!ENTITY safeb.blocked.unwantedPage.title "Reported Unwanted Software Page!">
-<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!-- Localization note (safeb.blocked.unwantedPage.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.unwantedPage.longDesc "<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>">
 
 <!ENTITY safeb.blocked.phishingPage.title "Reported Web Forgery!">
-<!-- Localization note (safeb.blocked.phishing.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!-- Localization note (safeb.blocked.phishingPage.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.phishingPage.shortDesc "This web page at <span id='phishing_sitename'/> has been reported as a web forgery and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.phishingPage.longDesc "<p>Web forgeries are designed to trick you into revealing personal or financial information by imitating sources you may trust.</p><p>Entering any information on this web page may result in identity theft or other fraud.</p>">
+
+<!ENTITY safeb.blocked.forbiddenPage.title "Forbidden Site">
+<!-- Localization note (safeb.blocked.forbiddenPage.shortDesc) - Please don't translate the contents of the <span id="forbidden_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.forbiddenPage.shortDesc "This Web page at <span id='forbidden_sitename'/> has been blocked based on your browser configuration.">
+
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -27,15 +27,16 @@ externalProtocolTitle=External Protocol 
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
 unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
+forbiddenBlocked=The site at %S has been blocked by your browser configuration.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 ## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
 ## LOCALIZATION NOTE (weakCryptoUsed) - Do not translate "%S".
 weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, Firefox has not connected to this website.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -170,16 +170,20 @@ be temporary, and you can try again late
 ">
 
 <!ENTITY phishingBlocked.title "Suspected Web Forgery!">
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
+<!ENTITY forbiddenBlocked.title "Forbidden Site">
+<!ENTITY forbiddenBlocked.longDesc "<p>&brandShortName; prevented this page from loading because it is configured to block it.</p>
+">
+
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
 
 <!ENTITY corruptedContentError.title "Corrupted Content Error">
 <!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
 
 
 <!ENTITY securityOverride.linkText "Or you can add an exception…">
--- a/browser/themes/shared/incontent-icons/cert-error.svg
+++ b/browser/themes/shared/incontent-icons/cert-error.svg
@@ -1,10 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 
 <svg version="1.1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
      width="45"
      height="45"
      viewBox="0 0 45 45">
 
--- a/devtools/shared/heapsnapshot/DominatorTree.cpp
+++ b/devtools/shared/heapsnapshot/DominatorTree.cpp
@@ -1,22 +1,53 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/devtools/DominatorTree.h"
+
+#include "js/Debug.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/dom/DominatorTreeBinding.h"
 
 namespace mozilla {
 namespace devtools {
 
+dom::Nullable<uint64_t>
+DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
+{
+  JS::ubi::Node::Id id(aNodeId);
+  auto node = mHeapSnapshot->getNodeById(id);
+  if (node.isNothing())
+    return dom::Nullable<uint64_t>();
+
+  auto ccrt = CycleCollectedJSRuntime::Get();
+  MOZ_ASSERT(ccrt);
+  auto rt = ccrt->Runtime();
+  MOZ_ASSERT(rt);
+  auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(rt);
+  MOZ_ASSERT(mallocSizeOf);
+
+  JS::ubi::Node::Size size = 0;
+  if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return dom::Nullable<uint64_t>();
+  }
+
+  MOZ_ASSERT(size != 0,
+             "The node should not have been unknown since we got it from the heap snapshot.");
+  return dom::Nullable<uint64_t>(size);
+}
+
+
 /*** Cycle Collection Boilerplate *****************************************************************/
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent, mHeapSnapshot)
+
 NS_IMPL_CYCLE_COLLECTING_ADDREF(DominatorTree)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(DominatorTree)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DominatorTree)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
--- a/devtools/shared/heapsnapshot/DominatorTree.h
+++ b/devtools/shared/heapsnapshot/DominatorTree.h
@@ -1,16 +1,17 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* 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 mozilla_devtools_DominatorTree__
 #define mozilla_devtools_DominatorTree__
 
+#include "mozilla/devtools/HeapSnapshot.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/RefCounted.h"
 #include "js/UbiNodeDominatorTree.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace devtools {
@@ -20,32 +21,40 @@ class DominatorTree final : public nsISu
 {
 protected:
   nsCOMPtr<nsISupports> mParent;
 
   virtual ~DominatorTree() { }
 
 private:
   JS::ubi::DominatorTree mDominatorTree;
+  RefPtr<HeapSnapshot> mHeapSnapshot;
 
 public:
-  explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree, nsISupports* aParent)
+  explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree, HeapSnapshot* aHeapSnapshot,
+                         nsISupports* aParent)
     : mParent(aParent)
     , mDominatorTree(Move(aDominatorTree))
+    , mHeapSnapshot(aHeapSnapshot)
   {
     MOZ_ASSERT(aParent);
+    MOZ_ASSERT(aHeapSnapshot);
   };
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DominatorTree);
 
   nsISupports* GetParentObject() const { return mParent; }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  // readonly attribute NodeId root
   uint64_t Root() const { return mDominatorTree.root().identifier(); }
+
+  // [Throws] NodeSize getRetainedSize(NodeId node)
+  dom::Nullable<uint64_t> GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv);
 };
 
 } // namespace devtools
 } // namespace mozilla
 
 #endif // mozilla_devtools_DominatorTree__
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -6,22 +6,25 @@
 #include "HeapSnapshot.h"
 
 #include <google/protobuf/io/coded_stream.h>
 #include <google/protobuf/io/gzip_stream.h>
 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 
 #include "js/Debug.h"
 #include "js/TypeDecls.h"
+#include "js/UbiNodeBreadthFirst.h"
 #include "js/UbiNodeCensus.h"
-#include "js/UbiNodeBreadthFirst.h"
+#include "js/UbiNodeDominatorTree.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/devtools/AutoMemMap.h"
 #include "mozilla/devtools/CoreDump.pb.h"
 #include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/devtools/DominatorTree.h"
 #include "mozilla/devtools/FileDescriptorOutputStream.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
 #include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/HeapSnapshotBinding.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Telemetry.h"
@@ -533,17 +536,17 @@ HeapSnapshot::ComputeDominatorTree(Error
     maybeTree = JS::ubi::DominatorTree::Create(rt, nogc, getRoot());
   }
 
   if (maybeTree.isNothing()) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
-  return MakeAndAddRef<DominatorTree>(Move(*maybeTree), mParent);
+  return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
 }
 
 
 /*** Saving Heap Snapshots ************************************************************************/
 
 // If we are only taking a snapshot of the heap affected by the given set of
 // globals, find the set of zones the globals are allocated within. Returns
 // false on OOM failure.
--- a/devtools/shared/heapsnapshot/HeapSnapshot.h
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_devtools_HeapSnapshot__
 #define mozilla_devtools_HeapSnapshot__
 
 #include "js/HashTable.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/devtools/DeserializedNode.h"
-#include "mozilla/devtools/DominatorTree.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefCounted.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
@@ -25,16 +24,18 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsISupports.h"
 #include "nsWrapperCache.h"
 #include "nsXPCOM.h"
 
 namespace mozilla {
 namespace devtools {
 
+class DominatorTree;
+
 struct NSFreePolicy {
   void operator()(void* ptr) {
     NS_Free(ptr);
   }
 };
 
 using UniqueTwoByteString = UniquePtr<char16_t[], NSFreePolicy>;
 using UniqueOneByteString = UniquePtr<char[], NSFreePolicy>;
@@ -143,16 +144,23 @@ public:
   JS::ubi::Node getRoot() {
     MOZ_ASSERT(nodes.initialized());
     auto p = nodes.lookup(rootId);
     MOZ_ASSERT(p);
     const DeserializedNode& node = *p;
     return JS::ubi::Node(const_cast<DeserializedNode*>(&node));
   }
 
+  Maybe<JS::ubi::Node> getNodeById(JS::ubi::Node::Id nodeId) {
+    auto p = nodes.lookup(nodeId);
+    if (!p)
+      return Nothing();
+    return Some(JS::ubi::Node(const_cast<DeserializedNode*>(&*p)));
+  }
+
   void TakeCensus(JSContext* cx, JS::HandleObject options,
                   JS::MutableHandleValue rval, ErrorResult& rv);
 
   already_AddRefed<DominatorTree> ComputeDominatorTree(ErrorResult& rv);
 
   dom::Nullable<uint64_t> GetCreationTime() {
     static const uint64_t maxTime = uint64_t(1) << 53;
     if (timestamp.isSome() && timestamp.ref() <= maxTime) {
--- a/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp
+++ b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp
@@ -74,18 +74,18 @@ ZeroCopyNSIOutputStream::Next(void** dat
   *size = BUFFER_SIZE - amountUsed;
   amountUsed = BUFFER_SIZE;
   return true;
 }
 
 void
 ZeroCopyNSIOutputStream::BackUp(int count)
 {
-  MOZ_ASSERT(count > 0,
-             "Must back up a positive number of bytes.");
+  MOZ_ASSERT(count >= 0,
+             "Cannot back up a negative amount of bytes.");
   MOZ_ASSERT(amountUsed == BUFFER_SIZE,
              "Can only call BackUp directly after calling Next.");
   MOZ_ASSERT(count <= amountUsed,
              "Can't back up further than we've given out.");
 
   amountUsed -= count;
 }
 
new file mode 100644
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_04.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can get the retained sizes of dominator trees.
+
+function run_test() {
+  const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
+  equal(typeof dominatorTree.getRetainedSize, "function",
+        "getRetainedSize should be a function");
+
+  const size = dominatorTree.getRetainedSize(dominatorTree.root);
+  ok(size, "should get a size for the root");
+  equal(typeof size, "number", "retained sizes should be a number");
+  equal(Math.floor(size), size, "size should be an integer");
+  ok(size > 0, "size should be positive");
+  ok(size <= Math.pow(2, 64), "size should be less than or equal to 2^64");
+
+  const bad = dominatorTree.getRetainedSize(1);
+  equal(bad, null, "null is returned for unknown node ids");
+
+  do_test_finished();
+}
--- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -26,16 +26,17 @@ support-files =
 [test_census-tree-node-04.js]
 [test_census-tree-node-05.js]
 [test_census-tree-node-06.js]
 [test_census-tree-node-07.js]
 [test_census-tree-node-08.js]
 [test_DominatorTree_01.js]
 [test_DominatorTree_02.js]
 [test_DominatorTree_03.js]
+[test_DominatorTree_04.js]
 [test_HeapAnalyses_getCreationTime_01.js]
 [test_HeapAnalyses_readHeapSnapshot_01.js]
 [test_HeapAnalyses_takeCensusDiff_01.js]
 [test_HeapAnalyses_takeCensusDiff_02.js]
 [test_HeapAnalyses_takeCensus_01.js]
 [test_HeapAnalyses_takeCensus_02.js]
 [test_HeapAnalyses_takeCensus_03.js]
 [test_HeapAnalyses_takeCensus_04.js]
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4904,46 +4904,53 @@ nsDocShell::DisplayLoadError(nsresult aE
         }
 
       } else {
         error.AssignLiteral("nssFailure2");
       }
     }
   } else if (NS_ERROR_PHISHING_URI == aError ||
              NS_ERROR_MALWARE_URI == aError ||
-             NS_ERROR_UNWANTED_URI == aError) {
+             NS_ERROR_UNWANTED_URI == aError ||
+             NS_ERROR_FORBIDDEN_URI == aError) {
     nsAutoCString host;
     aURI->GetHost(host);
     CopyUTF8toUTF16(host, formatStrs[0]);
     formatStrCount = 1;
 
     // Malware and phishing detectors may want to use an alternate error
     // page, but if the pref's not set, we'll fall back on the standard page
     nsAdoptingCString alternateErrorPage =
       Preferences::GetCString("urlclassifier.alternate_error_page");
     if (alternateErrorPage) {
       errorPage.Assign(alternateErrorPage);
     }
 
     uint32_t bucketId;
+    bool sendTelemetry = false;
     if (NS_ERROR_PHISHING_URI == aError) {
+      sendTelemetry = true;
       error.AssignLiteral("phishingBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
     } else if (NS_ERROR_MALWARE_URI == aError) {
+      sendTelemetry = true;
       error.AssignLiteral("malwareBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
-    } else {
+    } else if (NS_ERROR_UNWANTED_URI == aError) {
+      sendTelemetry = true;
       error.AssignLiteral("unwantedBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP;
-    }
-
-    if (errorPage.EqualsIgnoreCase("blocked")) {
+    } else if (NS_ERROR_FORBIDDEN_URI == aError) {
+      error.AssignLiteral("forbiddenBlocked");
+    }
+
+    if (sendTelemetry && errorPage.EqualsIgnoreCase("blocked")) {
       Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId);
     }
 
     cssClass.AssignLiteral("blacklist");
   } else if (NS_ERROR_CONTENT_CRASHED == aError) {
     errorPage.AssignLiteral("tabcrashed");
     error.AssignLiteral("tabcrashed");
 
@@ -7694,16 +7701,17 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
                aStatus == NS_ERROR_REDIRECT_LOOP ||
                aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
                aStatus == NS_ERROR_NET_INTERRUPT ||
                aStatus == NS_ERROR_NET_RESET ||
                aStatus == NS_ERROR_OFFLINE ||
                aStatus == NS_ERROR_MALWARE_URI ||
                aStatus == NS_ERROR_PHISHING_URI ||
                aStatus == NS_ERROR_UNWANTED_URI ||
+               aStatus == NS_ERROR_FORBIDDEN_URI ||
                aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
                aStatus == NS_ERROR_REMOTE_XUL ||
                aStatus == NS_ERROR_INTERCEPTION_FAILED ||
                NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
       // Errors to be shown for any frame
       DisplayLoadError(aStatus, url, nullptr, aChannel);
     } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
       // Non-caching channels will simply return NS_ERROR_OFFLINE.
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -290,16 +290,17 @@
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
         <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
+        <h1 id="et_forbiddenBlocked">&forbiddenBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
@@ -317,16 +318,17 @@
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
         <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
+        <div id="ed_forbiddenBlocked">&forbiddenBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
--- a/dom/archivereader/ArchiveReader.cpp
+++ b/dom/archivereader/ArchiveReader.cpp
@@ -32,17 +32,17 @@ ArchiveReader::Constructor(const GlobalO
   if (!window) {
     aError.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsAutoCString encoding;
   if (!EncodingUtils::FindEncodingForLabelNoReplacement(aOptions.mEncoding,
                                                         encoding)) {
-    aError.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(&aOptions.mEncoding);
+    aError.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(aOptions.mEncoding);
     return nullptr;
   }
 
   RefPtr<ArchiveReader> reader =
     new ArchiveReader(aBlob, window, encoding);
   return reader.forget();
 }
 
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -324,45 +324,16 @@ protected:
 
   RefPtr<QuotaObject> mQuotaObject;
   int64_t mFileSize;
   PRFileDesc* mFileDesc;
   PRFileMap* mFileMap;
   void* mMappedMemory;
 };
 
-class UnlockDirectoryRunnable final
-  : public nsRunnable
-{
-  RefPtr<DirectoryLock> mDirectoryLock;
-
-public:
-  explicit
-  UnlockDirectoryRunnable(already_AddRefed<DirectoryLock> aDirectoryLock)
-    : mDirectoryLock(Move(aDirectoryLock))
-  { }
-
-private:
-  ~UnlockDirectoryRunnable()
-  {
-    MOZ_ASSERT(!mDirectoryLock);
-  }
-
-  NS_IMETHOD
-  Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mDirectoryLock);
-
-    mDirectoryLock = nullptr;
-
-    return NS_OK;
-  }
-};
-
 // A runnable that implements a state machine required to open a cache entry.
 // It executes in the parent for a cache access originating in the child.
 // This runnable gets registered as an IPDL subprotocol actor so that it
 // can communicate with the corresponding ChildRunnable.
 class ParentRunnable final
   : public FileDescriptorHolder
   , public quota::OpenDirectoryListener
   , public PAsmJSCacheEntryParent
@@ -498,32 +469,35 @@ private:
   }
 
   void
   InitPersistenceType();
 
   nsresult
   InitOnMainThread();
 
+  void
+  OpenDirectory();
+
   nsresult
   ReadMetadata();
 
   nsresult
   OpenCacheFileForWrite();
 
   nsresult
   OpenCacheFileForRead();
 
   void
   FinishOnOwningThread();
 
   void
   DispatchToIOThread()
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnOwningThread();
 
     // If shutdown just started, the QuotaManager may have been deleted.
     QuotaManager* qm = QuotaManager::Get();
     if (!qm) {
       FailOnNonOwningThread();
       return;
     }
 
@@ -588,18 +562,18 @@ private:
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
     MOZ_ASSERT(mOpenMode == eOpenForRead);
 
     // A cache entry has been selected to open.
 
     mModuleIndex = aModuleIndex;
-    mState = eDispatchToMainThread;
-    NS_DispatchToMainThread(this);
+    mState = eReadyToOpenCacheFileForRead;
+    DispatchToIOThread();
 
     return true;
   }
 
   bool
   RecvCacheMiss() override
   {
     AssertIsOnOwningThread();
@@ -625,22 +599,23 @@ private:
   nsCOMPtr<nsIFile> mMetadataFile;
   Metadata mMetadata;
 
   // State initialized during eWaitingToOpenCacheFileForRead
   unsigned mModuleIndex;
 
   enum State {
     eInitial, // Just created, waiting to be dispatched to main thread
+    eWaitingToFinishInit, // Waiting to finish initialization
+    eWaitingToOpenDirectory, // Waiting to open directory
     eWaitingToOpenMetadata, // Waiting to be called back from OpenDirectory
     eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
     eFailedToReadMetadata, // Waiting to be dispatched to owning thread after fail
     eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
     eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
-    eDispatchToMainThread, // IO thread dispatch allowed from main thread only
     eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
     eSendingCacheFile, // Waiting to send OnOpenCacheFile on the owning thread
     eOpened, // Finished calling OnOpenCacheFile, waiting to be closed
     eFailing, // Just failed, waiting to be dispatched to the owning thread
     eFinished, // Terminal state
   };
   State mState;
   JS::AsmJSCacheResult mResult;
@@ -706,31 +681,48 @@ ParentRunnable::InitOnMainThread()
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> principal =
     PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  QuotaManager* qm = QuotaManager::GetOrCreate();
-  NS_ENSURE_STATE(qm);
-
   rv = QuotaManager::GetInfoFromPrincipal(principal, &mGroup, &mOrigin,
                                           &mIsApp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   InitPersistenceType();
 
   mEnforcingQuota =
     QuotaManager::IsQuotaEnforced(mPersistence, mOrigin, mIsApp);
 
   return NS_OK;
 }
 
+void
+ParentRunnable::OpenDirectory()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == eWaitingToFinishInit ||
+             mState == eWaitingToOpenDirectory);
+  MOZ_ASSERT(QuotaManager::Get());
+
+  mState = eWaitingToOpenMetadata;
+
+  // XXX The exclusive lock shouldn't be needed for read operations.
+  QuotaManager::Get()->OpenDirectory(mPersistence,
+                                     mGroup,
+                                     mOrigin,
+                                     mIsApp,
+                                     quota::Client::ASMJS,
+                                     /* aExclusive */ true,
+                                     this);
+}
+
 nsresult
 ParentRunnable::ReadMetadata()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == eReadyToReadMetadata);
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
@@ -898,22 +890,17 @@ void
 ParentRunnable::FinishOnOwningThread()
 {
   AssertIsOnOwningThread();
 
   // Per FileDescriptorHolder::Finish()'s comment, call before
   // releasing the directory lock.
   FileDescriptorHolder::Finish();
 
-  if (mDirectoryLock) {
-    RefPtr<UnlockDirectoryRunnable> runnable =
-      new UnlockDirectoryRunnable(mDirectoryLock.forget());
-
-    NS_DispatchToMainThread(runnable);
-  }
+  mDirectoryLock = nullptr;
 }
 
 NS_IMETHODIMP
 ParentRunnable::Run()
 {
   nsresult rv;
 
   // All success/failure paths must eventually call Finish() to avoid leaving
@@ -923,30 +910,54 @@ ParentRunnable::Run()
       MOZ_ASSERT(NS_IsMainThread());
 
       rv = InitOnMainThread();
       if (NS_FAILED(rv)) {
         FailOnNonOwningThread();
         return NS_OK;
       }
 
-      mState = eWaitingToOpenMetadata;
+      mState = eWaitingToFinishInit;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)));
+
+      return NS_OK;
+    }
+
+    case eWaitingToFinishInit: {
+      AssertIsOnOwningThread();
+
+      if (QuotaManager::IsShuttingDown()) {
+        Fail();
+        return NS_OK;
+      }
 
-      // XXX The exclusive lock shouldn't be needed for read operations.
-      QuotaManager::Get()->OpenDirectory(mPersistence,
-                                         mGroup,
-                                         mOrigin,
-                                         mIsApp,
-                                         quota::Client::ASMJS,
-                                         /* aExclusive */ true,
-                                         this);
+      if (QuotaManager::Get()) {
+        OpenDirectory();
+        return NS_OK;
+      }
+
+      mState = eWaitingToOpenDirectory;
+      QuotaManager::GetOrCreate(this);
 
       return NS_OK;
     }
 
+    case eWaitingToOpenDirectory: {
+      AssertIsOnOwningThread();
+
+      if (NS_WARN_IF(!QuotaManager::Get())) {
+        Fail();
+        return NS_OK;
+      }
+
+      OpenDirectory();
+      return NS_OK;
+    }
+
     case eReadyToReadMetadata: {
       AssertIsOnIOThread();
 
       rv = ReadMetadata();
       if (NS_FAILED(rv)) {
         mState = eFailedToReadMetadata;
         MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
           mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL)));
@@ -994,24 +1005,16 @@ ParentRunnable::Run()
       // Metadata is now open.
       if (!SendOnOpenMetadataForRead(mMetadata)) {
         Unused << Send__delete__(this, JS::AsmJSCache_InternalError);
       }
 
       return NS_OK;
     }
 
-    case eDispatchToMainThread: {
-      MOZ_ASSERT(NS_IsMainThread());
-
-      mState = eReadyToOpenCacheFileForRead;
-      DispatchToIOThread();
-      return NS_OK;
-    }
-
     case eReadyToOpenCacheFileForRead: {
       AssertIsOnIOThread();
       MOZ_ASSERT(mOpenMode == eOpenForRead);
 
       rv = OpenCacheFileForRead();
       if (NS_FAILED(rv)) {
         FailOnNonOwningThread();
         return NS_OK;
@@ -1059,34 +1062,34 @@ ParentRunnable::Run()
 
   MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
   return NS_OK;
 }
 
 void
 ParentRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == eWaitingToOpenMetadata);
   MOZ_ASSERT(!mDirectoryLock);
 
   mDirectoryLock = aLock;
 
   mState = eReadyToReadMetadata;
   DispatchToIOThread();
 }
 
 void
 ParentRunnable::DirectoryLockFailed()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == eWaitingToOpenMetadata);
   MOZ_ASSERT(!mDirectoryLock);
 
-  FailOnNonOwningThread();
+  Fail();
 }
 
 NS_IMPL_ISUPPORTS_INHERITED0(ParentRunnable, FileDescriptorHolder)
 
 bool
 FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
               unsigned* aModuleIndex)
 {
@@ -1807,17 +1810,21 @@ public:
   AbortOperations(const nsACString& aOrigin) override
   { }
 
   virtual void
   AbortOperationsForProcess(ContentParentId aContentParentId) override
   { }
 
   virtual void
-  PerformIdleMaintenance() override
+  StartIdleMaintenance() override
+  { }
+
+  virtual void
+  StopIdleMaintenance() override
   { }
 
   virtual void
   ShutdownWorkThreads() override
   { }
 
 private:
   nsAutoRefCnt mRefCnt;
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -300,24 +300,23 @@ FindJSContext(nsIGlobalObject* aGlobalOb
   }
   return cx;
 }
 
 AutoJSAPI::AutoJSAPI()
   : mCx(nullptr)
   , mOwnErrorReporting(false)
   , mOldAutoJSAPIOwnsErrorReporting(false)
+  , mIsMainThread(false) // For lack of anything better
 {
 }
 
 AutoJSAPI::~AutoJSAPI()
 {
   if (mOwnErrorReporting) {
-    MOZ_ASSERT(NS_IsMainThread(), "See corresponding assertion in TakeOwnershipOfErrorReporting()");
-
     ReportException();
 
     // We need to do this _after_ processing the existing exception, because the
     // JS engine can throw while doing that, and uses this bit to determine what
     // to do in that case: squelch the exception if the bit is set, otherwise
     // call the error reporter. Calling WarningOnlyErrorReporter with a
     // non-warning will assert, so we need to make sure we do the former.
     JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
@@ -327,17 +326,20 @@ AutoJSAPI::~AutoJSAPI()
     JS_SetErrorReporter(JS_GetRuntime(cx()), mOldErrorReporter.value());
   }
 }
 
 void
 AutoJSAPI::InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread)
 {
   MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
+
   mCx = aCx;
+  mIsMainThread = aIsMainThread;
   if (aIsMainThread) {
     // This Rooted<> is necessary only as long as AutoCxPusher::AutoCxPusher
     // can GC, which is only possible because XPCJSContextStack::Push calls
     // nsIPrincipal.Equals. Once that is removed, the Rooted<> will no longer
     // be necessary.
     JS::Rooted<JSObject*> global(JS_GetRuntime(aCx), aGlobal);
     mCxPusher.emplace(mCx);
     mAutoNullableCompartment.emplace(mCx, global);
@@ -352,21 +354,22 @@ AutoJSAPI::InitInternal(JSObject* aGloba
   }
 }
 
 AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
                      bool aIsMainThread,
                      JSContext* aCx)
   : mOwnErrorReporting(false)
   , mOldAutoJSAPIOwnsErrorReporting(false)
+  , mIsMainThread(aIsMainThread)
 {
   MOZ_ASSERT(aGlobalObject);
   MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
   MOZ_ASSERT(aCx);
-  MOZ_ASSERT_IF(aIsMainThread, NS_IsMainThread());
+  MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
 
   InitInternal(aGlobalObject->GetGlobalJSObject(), aCx, aIsMainThread);
 }
 
 void
 AutoJSAPI::Init()
 {
   MOZ_ASSERT(!mCx, "An AutoJSAPI should only be initialised once");
@@ -455,35 +458,42 @@ AutoJSAPI::InitWithLegacyErrorReporting(
 // reports to the JSErrorReporter as soon as they are generated. These go
 // directly to the console, so we can handle them easily here.
 //
 // Eventually, SpiderMonkey will have a special-purpose callback for warnings
 // only.
 void
 WarningOnlyErrorReporter(JSContext* aCx, const char* aMessage, JSErrorReport* aRep)
 {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(JSREPORT_IS_WARNING(aRep->flags));
+
   RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
   nsPIDOMWindow* win = xpc::CurrentWindowOrNull(aCx);
   xpcReport->Init(aRep, aMessage, nsContentUtils::IsCallerChrome(),
                   win ? win->WindowID() : 0);
   xpcReport->LogToConsole();
 }
 
 void
 AutoJSAPI::TakeOwnershipOfErrorReporting()
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Can't own error reporting off-main-thread yet");
   MOZ_ASSERT(!mOwnErrorReporting);
   mOwnErrorReporting = true;
 
   JSRuntime *rt = JS_GetRuntime(cx());
   mOldAutoJSAPIOwnsErrorReporting = JS::ContextOptionsRef(cx()).autoJSAPIOwnsErrorReporting();
   JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(true);
-  JS_SetErrorReporter(rt, WarningOnlyErrorReporter);
+  // Workers have their own error reporting mechanism which deals with warnings
+  // as well, so don't change the worker error reporter for now.  Once we switch
+  // all of workers to TakeOwnershipOfErrorReporting(), we will just make the
+  // default worker error reporter assert that it only sees warnings.
+  if (mIsMainThread) {
+    JS_SetErrorReporter(rt, WarningOnlyErrorReporter);
+  }
 }
 
 void
 AutoJSAPI::ReportException()
 {
   MOZ_ASSERT(OwnsErrorReporting(), "This is not our exception to report!");
   if (!HasException()) {
     return;
@@ -493,38 +503,55 @@ AutoJSAPI::ReportException()
   // compartment when the destructor is called. However, the JS engine
   // requires us to be in a compartment when we fetch the pending exception.
   // In this case, we enter the privileged junk scope and don't dispatch any
   // error events.
   JS::Rooted<JSObject*> errorGlobal(cx(), JS::CurrentGlobalOrNull(cx()));
   if (!errorGlobal)
     errorGlobal = xpc::PrivilegedJunkScope();
   JSAutoCompartment ac(cx(), errorGlobal);
-  nsCOMPtr<nsPIDOMWindow> win = xpc::WindowGlobalOrNull(errorGlobal);
   JS::Rooted<JS::Value> exn(cx());
   js::ErrorReport jsReport(cx());
   if (StealException(&exn) && jsReport.init(cx(), exn)) {
-    RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
-    xpcReport->Init(jsReport.report(), jsReport.message(),
-                    nsContentUtils::IsCallerChrome(),
-                    win ? win->WindowID() : 0);
-    if (win) {
-      DispatchScriptErrorEvent(win, JS_GetRuntime(cx()), xpcReport, exn);
+    if (mIsMainThread) {
+      RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
+      nsCOMPtr<nsPIDOMWindow> win = xpc::WindowGlobalOrNull(errorGlobal);
+      xpcReport->Init(jsReport.report(), jsReport.message(),
+                      nsContentUtils::IsCallerChrome(),
+                      win ? win->WindowID() : 0);
+      if (win) {
+        DispatchScriptErrorEvent(win, JS_GetRuntime(cx()), xpcReport, exn);
+      } else {
+        xpcReport->LogToConsole();
+      }
     } else {
-      xpcReport->LogToConsole();
+      // On a worker, we just use the worker error reporting mechanism and don't
+      // bother with xpc::ErrorReport.  This will ensure that all the right
+      // events (which are a lot more complicated than in the window case) get
+      // fired.
+      workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
+      MOZ_ASSERT(worker);
+      MOZ_ASSERT(worker->GetJSContext() == cx());
+      // Before invoking ReportError, put the exception back on the context,
+      // because it may want to put it in its error events and has no other way
+      // to get hold of it.  After we invoke ReportError, clear the exception on
+      // cx(), just in case ReportError didn't.
+      JS_SetPendingException(cx(), exn);
+      worker->ReportError(cx(), jsReport.message(), jsReport.report());
+      ClearException();
     }
   } else {
     NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
   }
 }
 
 bool
 AutoJSAPI::StealException(JS::MutableHandle<JS::Value> aVal)
 {
-    MOZ_ASSERT(CxPusherIsStackTop());
+    MOZ_ASSERT_IF(mIsMainThread, CxPusherIsStackTop());
     MOZ_ASSERT(HasException());
     MOZ_ASSERT(js::GetContextCompartment(cx()));
     if (!JS_GetPendingException(cx(), aVal)) {
       return false;
     }
     JS_ClearPendingException(cx());
     return true;
 }
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -257,47 +257,47 @@ public:
   bool Init(nsGlobalWindow* aWindow);
   bool Init(nsGlobalWindow* aWindow, JSContext* aCx);
 
   bool InitWithLegacyErrorReporting(nsPIDOMWindow* aWindow);
   bool InitWithLegacyErrorReporting(nsGlobalWindow* aWindow);
 
   JSContext* cx() const {
     MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
-    MOZ_ASSERT_IF(NS_IsMainThread(), CxPusherIsStackTop());
+    MOZ_ASSERT_IF(mIsMainThread, CxPusherIsStackTop());
     return mCx;
   }
 
   bool CxPusherIsStackTop() const { return mCxPusher->IsStackTop(); }
 
   // We're moving towards a world where the AutoJSAPI always handles
   // exceptions that bubble up from the JS engine. In order to make this
   // process incremental, we allow consumers to opt-in to the new behavior
   // while keeping the old behavior as the default.
   void TakeOwnershipOfErrorReporting();
   bool OwnsErrorReporting() { return mOwnErrorReporting; }
   // If HasException, report it.  Otherwise, a no-op.  This must be
   // called only if OwnsErrorReporting().
   void ReportException();
 
   bool HasException() const {
-    MOZ_ASSERT(CxPusherIsStackTop());
+    MOZ_ASSERT_IF(NS_IsMainThread(), CxPusherIsStackTop());
     return JS_IsExceptionPending(cx());
   };
 
   // Transfers ownership of the current exception from the JS engine to the
   // caller. Callers must ensure that HasException() is true, and that cx()
   // is in a non-null compartment.
   //
   // Note that this fails if and only if we OOM while wrapping the exception
   // into the current compartment.
   bool StealException(JS::MutableHandle<JS::Value> aVal);
 
   void ClearException() {
-    MOZ_ASSERT(CxPusherIsStackTop());
+    MOZ_ASSERT_IF(NS_IsMainThread(), CxPusherIsStackTop());
     JS_ClearPendingException(cx());
   }
 
 protected:
   // Protected constructor, allowing subclasses to specify a particular cx to
   // be used. This constructor initialises the AutoJSAPI, so Init must NOT be
   // called on subclasses that use this.
   // If aGlobalObject, its associated JS global or aCx are null this will cause
@@ -307,16 +307,18 @@ protected:
 private:
   mozilla::Maybe<danger::AutoCxPusher> mCxPusher;
   mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
   JSContext *mCx;
 
   // Track state between the old and new error reporting modes.
   bool mOwnErrorReporting;
   bool mOldAutoJSAPIOwnsErrorReporting;
+  // Whether we're mainthread or not; set when we're initialized.
+  bool mIsMainThread;
   Maybe<JSErrorReporter> mOldErrorReporter;
 
   void InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread);
 
   AutoJSAPI(const AutoJSAPI&) = delete;
   AutoJSAPI& operator= (const AutoJSAPI&) = delete;
 };
 
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -73,33 +73,33 @@ URL::Constructor(const GlobalObject& aGl
 /* static */ already_AddRefed<URL>
 URL::Constructor(nsISupports* aParent, const nsAString& aUrl,
                  const nsAString& aBase, ErrorResult& aRv)
 {
   nsCOMPtr<nsIURI> baseUri;
   nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase, nullptr, nullptr,
                           nsContentUtils::GetIOService());
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.ThrowTypeError<MSG_INVALID_URL>(&aBase);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aBase);
     return nullptr;
   }
 
   return Constructor(aParent, aUrl, baseUri, aRv);
 }
 
 /* static */
 already_AddRefed<URL>
 URL::Constructor(nsISupports* aParent, const nsAString& aUrl, nsIURI* aBase,
                  ErrorResult& aRv)
 {
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBase,
                           nsContentUtils::GetIOService());
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.ThrowTypeError<MSG_INVALID_URL>(&aUrl);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aUrl);
     return nullptr;
   }
 
   RefPtr<URL> url = new URL(aParent, uri.forget());
   return url.forget();
 }
 
 void
@@ -224,18 +224,17 @@ URL::SetHref(const nsAString& aHref, Err
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   nsCOMPtr<nsIURI> uri;
   rv = ioService->NewURI(href, nullptr, nullptr, getter_AddRefs(uri));
   if (NS_FAILED(rv)) {
-    nsAutoString label(aHref);
-    aRv.ThrowTypeError<MSG_INVALID_URL>(&label);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aHref);
     return;
   }
 
   mURI = uri;
   UpdateURLSearchParams();
 }
 
 void
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -153,17 +153,17 @@ bool
 WindowNamedPropertiesHandler::defineProperty(JSContext* aCx,
                                              JS::Handle<JSObject*> aProxy,
                                              JS::Handle<jsid> aId,
                                              JS::Handle<JSPropertyDescriptor> aDesc,
                                              JS::ObjectOpResult &result) const
 {
   ErrorResult rv;
   rv.ThrowTypeError<MSG_DEFINEPROPERTY_ON_GSP>();
-  rv.ReportErrorWithMessage(aCx);
+  rv.MaybeSetPendingException(aCx);
   return false;
 }
 
 bool
 WindowNamedPropertiesHandler::ownPropNames(JSContext* aCx,
                                            JS::Handle<JSObject*> aProxy,
                                            unsigned flags,
                                            JS::AutoIdVector& aProps) const
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -5110,29 +5110,27 @@ nsDocument::DocumentStatesChanged(EventS
   mGotDocumentState &= ~aStateMask;
   mDocumentState &= ~aStateMask;
 
   NS_DOCUMENT_NOTIFY_OBSERVERS(DocumentStatesChanged, (this, aStateMask));
 }
 
 void
 nsDocument::StyleRuleChanged(CSSStyleSheet* aSheet,
-                             css::Rule* aOldStyleRule,
-                             css::Rule* aNewStyleRule)
+                             css::Rule* aStyleRule)
 {
   NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleChanged,
                                (this, aSheet,
-                                aOldStyleRule, aNewStyleRule));
+                                aStyleRule));
 
   if (StyleSheetChangeEventsEnabled()) {
     DO_STYLESHEET_NOTIFICATION(StyleRuleChangeEvent,
                                "StyleRuleChanged",
                                mRule,
-                               aNewStyleRule ? aNewStyleRule->GetDOMRule()
-                                             : nullptr);
+                               aStyleRule ? aStyleRule->GetDOMRule() : nullptr);
   }
 }
 
 void
 nsDocument::StyleRuleAdded(CSSStyleSheet* aSheet,
                            css::Rule* aStyleRule)
 {
   NS_DOCUMENT_NOTIFY_OBSERVERS(StyleRuleAdded,
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -879,18 +879,17 @@ public:
 
   virtual void ContentStateChanged(nsIContent* aContent,
                                    mozilla::EventStates aStateMask)
                                      override;
   virtual void DocumentStatesChanged(
                  mozilla::EventStates aStateMask) override;
 
   virtual void StyleRuleChanged(mozilla::CSSStyleSheet* aStyleSheet,
-                                mozilla::css::Rule* aOldStyleRule,
-                                mozilla::css::Rule* aNewStyleRule) override;
+                                mozilla::css::Rule* aStyleRule) override;
   virtual void StyleRuleAdded(mozilla::CSSStyleSheet* aStyleSheet,
                               mozilla::css::Rule* aStyleRule) override;
   virtual void StyleRuleRemoved(mozilla::CSSStyleSheet* aStyleSheet,
                                 mozilla::css::Rule* aStyleRule) override;
 
   virtual void FlushPendingNotifications(mozFlushType aType) override;
   virtual void FlushExternalResources(mozFlushType aType) override;
   virtual void SetXMLDeclaration(const char16_t *aVersion,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6133,20 +6133,22 @@ nsGlobalWindow::FinishFullscreenChange(b
     if (!pmService) {
       return;
     }
 
     ErrorResult rv;
     mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("DOM_Fullscreen"),
                                        this, rv);
     NS_WARN_IF_FALSE(!rv.Failed(), "Failed to lock the wakelock");
+    rv.SuppressException();
   } else if (mWakeLock && !mFullScreen) {
     ErrorResult rv;
     mWakeLock->Unlock(rv);
     mWakeLock = nullptr;
+    rv.SuppressException();
   }
 }
 
 bool
 nsGlobalWindow::FullScreen() const
 {
   MOZ_ASSERT(IsOuterWindow());
 
@@ -11759,16 +11761,17 @@ nsGlobalWindow::RunTimeoutHandler(nsTime
     nsJSUtils::EvaluateString(entryScript.cx(), nsDependentString(script),
                               global, options);
   } else {
     // Hold strong ref to ourselves while we call the callback.
     nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow *>(this));
     ErrorResult ignored;
     JS::Rooted<JS::Value> ignoredVal(CycleCollectedJSRuntime::Get()->Runtime());
     callback->Call(me, handler->GetArgs(), &ignoredVal, ignored, reason);
+    ignored.SuppressException();
   }
 
   // We ignore any failures from calling EvaluateString() on the context or
   // Call() on a Function here since we're in a loop
   // where we're likely to be running timeouts whose OS timers
   // didn't fire in time and we don't want to not fire those timers
   // now just because execution of one timer failed. We can't
   // propagate the error to anyone who cares about it from this
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1126,17 +1126,20 @@ public:
                        mozilla::ErrorResult& aError);
   void GetContent(JSContext* aCx,
                   JS::MutableHandle<JSObject*> aRetval,
                   mozilla::ErrorResult& aError);
   already_AddRefed<nsIDOMWindow> GetContent()
   {
     MOZ_ASSERT(IsOuterWindow());
     mozilla::ErrorResult ignored;
-    return GetContentInternal(ignored, /* aUnprivilegedCaller = */ false);
+    nsCOMPtr<nsIDOMWindow> win =
+      GetContentInternal(ignored, /* aUnprivilegedCaller = */ false);
+    ignored.SuppressException();
+    return win.forget();
   }
 
   void Get_content(JSContext* aCx,
                    JS::MutableHandle<JSObject*> aRetval,
                    mozilla::ErrorResult& aError)
   {
     if (mDoc) {
       mDoc->WarnOnceAbout(nsIDocument::eWindow_Content);
--- a/dom/base/nsIConsoleReportCollector.h
+++ b/dom/base/nsIConsoleReportCollector.h
@@ -44,29 +44,29 @@ public:
   virtual void
   AddConsoleReport(uint32_t aErrorFlags, const nsACString& aCategory,
                    nsContentUtils::PropertiesFile aPropertiesFile,
                    const nsACString& aSourceFileURI, uint32_t aLineNumber,
                    uint32_t aColumnNumber, const nsACString& aMessageName,
                    const nsTArray<nsString>& aStringParams) = 0;
 
   // A version of AddConsoleReport() that accepts the message parameters
-  // as variable nsString arguments.  Note, the parameters must be exactly
-  // nsString and not another string class.  All other args the same as
-  // AddConsoleReport().
+  // as variable nsString arguments (or really, any sort of const nsAString).
+  // All other args the same as AddConsoleReport().
   template<typename... Params>
   void
   AddConsoleReport(uint32_t aErrorFlags, const nsACString& aCategory,
                    nsContentUtils::PropertiesFile aPropertiesFile,
                    const nsACString& aSourceFileURI, uint32_t aLineNumber,
                    uint32_t aColumnNumber, const nsACString& aMessageName,
-                   Params... aParams)
+                   Params&&... aParams)
   {
     nsTArray<nsString> params;
-    mozilla::dom::StringArrayAppender::Append(params, sizeof...(Params), aParams...);
+    mozilla::dom::StringArrayAppender::Append(params, sizeof...(Params),
+                                              mozilla::Forward<Params>(aParams)...);
     AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile, aSourceFileURI,
                      aLineNumber, aColumnNumber, aMessageName, params);
   }
 
   // Flush all pending reports to the console.  Main thread only.
   //
   // aDocument      An optional document representing where to flush the
   //                reports.  If provided, then the corresponding window's
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -150,18 +150,18 @@ template<typename> class Sequence;
 
 template<typename, typename> class CallbackObjectHolder;
 typedef CallbackObjectHolder<NodeFilter, nsIDOMNodeFilter> NodeFilterHolder;
 
 } // namespace dom
 } // namespace mozilla
 
 #define NS_IDOCUMENT_IID \
-{ 0xecc9e376, 0x6c31, 0x4f04, \
-  { 0xbe, 0xde, 0xd6, 0x27, 0x61, 0xd7, 0x00, 0x84 } }
+{ 0x13011a82, 0x46cd, 0x4c33, \
+  { 0x9d, 0x4e, 0x31, 0x41, 0xbb, 0x3f, 0x18, 0xe9 } }
 
 // Enum for requesting a particular type of document when creating a doc
 enum DocumentFlavor {
   DocumentFlavorLegacyGuess, // compat with old code until made HTML5-compliant
   DocumentFlavorHTML, // HTMLDocument with HTMLness bit set to true
   DocumentFlavorSVG, // SVGDocument
   DocumentFlavorPlain, // Just a Document
 };
@@ -1285,18 +1285,17 @@ public:
   // Notify that a document state has changed.
   // This should only be called by callers whose state is also reflected in the
   // implementation of nsDocument::GetDocumentState.
   virtual void DocumentStatesChanged(mozilla::EventStates aStateMask) = 0;
 
   // Observation hooks for style data to propagate notifications
   // to document observers
   virtual void StyleRuleChanged(mozilla::CSSStyleSheet* aStyleSheet,
-                                mozilla::css::Rule* aOldStyleRule,
-                                mozilla::css::Rule* aNewStyleRule) = 0;
+                                mozilla::css::Rule* aStyleRule) = 0;
   virtual void StyleRuleAdded(mozilla::CSSStyleSheet* aStyleSheet,
                               mozilla::css::Rule* aStyleRule) = 0;
   virtual void StyleRuleRemoved(mozilla::CSSStyleSheet* aStyleSheet,
                                 mozilla::css::Rule* aStyleRule) = 0;
 
   /**
    * Flush notifications for this document and its parent documents
    * (since those may affect the layout of this one).
--- a/dom/base/nsIDocumentObserver.h
+++ b/dom/base/nsIDocumentObserver.h
@@ -16,18 +16,18 @@ class nsIDocument;
 namespace mozilla {
 class CSSStyleSheet;
 namespace css {
 class Rule;
 } // namespace css
 } // namespace mozilla
 
 #define NS_IDOCUMENT_OBSERVER_IID \
-{ 0x21c8ad67, 0x3a7d, 0x4881, \
-  { 0xa5, 0x43, 0xcb, 0xa9, 0xbb, 0xe4, 0x9e, 0x39 } }
+{ 0x71041fa3, 0x6dd7, 0x4cde, \
+  { 0xbb, 0x76, 0xae, 0xcc, 0x69, 0xe1, 0x75, 0x78 } }
 
 typedef uint32_t nsUpdateType;
 
 #define UPDATE_CONTENT_MODEL 0x00000001
 #define UPDATE_STYLE         0x00000002
 #define UPDATE_ALL (UPDATE_CONTENT_MODEL | UPDATE_STYLE)
 
 // Document observer interface
@@ -138,36 +138,23 @@ public:
 
   /**
    * A StyleRule has just been modified within a style sheet.
    * This method is called automatically when the rule gets
    * modified. The style sheet passes this notification to 
    * the document. The notification is passed on to all of 
    * the document observers.
    *
-   * Since nsIStyleRule objects are immutable, there is a new object
-   * replacing the old one.  However, the use of this method (rather
-   * than StyleRuleAdded and StyleRuleRemoved) implies that the new rule
-   * matches the same elements and has the same priority (weight,
-   * origin, specificity) as the old one.  (However, if it is a CSS
-   * style rule, there may be a change in whether it has an important
-   * rule.)
-   *
    * @param aDocument The document being observed
    * @param aStyleSheet the StyleSheet that contians the rule
-   * @param aOldStyleRule The rule being removed.  This rule may not be
-   *                      fully valid anymore -- however, it can still
-   *                      be used for pointer comparison and
-   *                      |QueryInterface|.
-   * @param aNewStyleRule The rule being added.
+   * @param aStyleRule The rule being changed.
    */
   virtual void StyleRuleChanged(nsIDocument *aDocument,
                                 mozilla::CSSStyleSheet* aStyleSheet,
-                                mozilla::css::Rule* aOldStyleRule,
-                                mozilla::css::Rule* aNewStyleRule) = 0;
+                                mozilla::css::Rule* aStyleRule) = 0;
 
   /**
    * A StyleRule has just been added to a style sheet.
    * This method is called automatically when the rule gets
    * added to the sheet. The style sheet passes this
    * notification to the document. The notification is passed on 
    * to all of the document observers.
    *
@@ -233,18 +220,17 @@ NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumen
     virtual void StyleSheetApplicableStateChanged(                           \
         nsIDocument* aDocument,                                              \
         mozilla::CSSStyleSheet* aStyleSheet,                                 \
         bool aApplicable) override;
 
 #define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULECHANGED                         \
     virtual void StyleRuleChanged(nsIDocument* aDocument,                    \
                                   mozilla::CSSStyleSheet* aStyleSheet,       \
-                                  mozilla::css::Rule* aOldStyleRule,         \
-                                  mozilla::css::Rule* aNewStyleRule) override;
+                                  mozilla::css::Rule* aStyleRule) override;
 
 #define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEADDED                           \
     virtual void StyleRuleAdded(nsIDocument* aDocument,                      \
                                 mozilla::CSSStyleSheet* aStyleSheet,         \
                                 mozilla::css::Rule* aStyleRule) override;
 
 #define NS_DECL_NSIDOCUMENTOBSERVER_STYLERULEREMOVED                         \
     virtual void StyleRuleRemoved(nsIDocument* aDocument,                    \
@@ -322,18 +308,17 @@ void                                    
 _class::StyleSheetApplicableStateChanged(nsIDocument* aDocument,          \
                                          mozilla::CSSStyleSheet* aStyleSheet,\
                                          bool aApplicable)                \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::StyleRuleChanged(nsIDocument* aDocument,                          \
                          mozilla::CSSStyleSheet* aStyleSheet,             \
-                         mozilla::css::Rule* aOldStyleRule,               \
-                         mozilla::css::Rule* aNewStyleRule)               \
+                         mozilla::css::Rule* aStyleRule)                  \
 {                                                                         \
 }                                                                         \
 void                                                                      \
 _class::StyleRuleAdded(nsIDocument* aDocument,                            \
                        mozilla::CSSStyleSheet* aStyleSheet,               \
                        mozilla::css::Rule* aStyleRule)                    \
 {                                                                         \
 }                                                                         \
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -267,16 +267,18 @@ skip-if = buildapp == 'b2g' # Requires w
 [test_anonymousContent_style_csp.html]
 [test_applet_alternate_content.html]
 [test_appname_override.html]
 [test_async_setTimeout_stack.html]
 [test_async_setTimeout_stack_across_globals.html]
 [test_audioWindowUtils.html]
 [test_audioNotification.html]
 skip-if = buildapp == 'mulet'
+[test_audioNotificationStream.html]
+skip-if = buildapp == 'mulet'
 [test_audioNotificationStopOnNavigation.html]
 skip-if = buildapp == 'mulet'
 [test_audioNotificationWithEarlyPlay.html]
 skip-if = buildapp == 'mulet'
 [test_bug1091883.html]
 [test_bug116083.html]
 [test_bug793311.html]
 [test_bug913761.html]
copy from dom/base/test/test_audioNotification.html
copy to dom/base/test/test_audioNotificationStream.html
--- a/dom/base/test/test_audioNotification.html
+++ b/dom/base/test/test_audioNotificationStream.html
@@ -22,17 +22,17 @@ var observer = {
     runTest();
   }
 };
 
 var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
                                    .getService(SpecialPowers.Ci.nsIObserverService);
 
 var audio = new Audio();
-audio.src = "audio.ogg";
+audio.srcObject = (new AudioContext()).createMediaStreamDestination().stream;
 
 var tests = [
   function() {
     observerService.addObserver(observer, "audio-playback", false);
     ok(true, "Observer set");
     runTest();
   },
 
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -134,33 +134,20 @@ public:
   }
 
   bool WasPassed() const
   {
     return mImpl.isSome();
   }
 
   // Return InternalType here so we can work with it usefully.
-  InternalType& Construct()
-  {
-    mImpl.emplace();
-    return *mImpl;
-  }
-
-  template <class T1>
-  InternalType& Construct(const T1 &t1)
+  template<typename... Args>
+  InternalType& Construct(Args&&... aArgs)
   {
-    mImpl.emplace(t1);
-    return *mImpl;
-  }
-
-  template <class T1, class T2>
-  InternalType& Construct(const T1 &t1, const T2 &t2)
-  {
-    mImpl.emplace(t1, t2);
+    mImpl.emplace(Forward<Args>(aArgs)...);
     return *mImpl;
   }
 
   void Reset()
   {
     mImpl.reset();
   }
 
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -121,48 +121,16 @@ ThrowInvalidThis(JSContext* aCx, const J
                  const ErrNum aErrorNumber,
                  prototypes::ID aProtoId)
 {
   return ThrowInvalidThis(aCx, aArgs, aErrorNumber,
                           NamesOfInterfacesWithProtos(aProtoId));
 }
 
 bool
-ThrowMethodFailed(JSContext* cx, ErrorResult& rv)
-{
-  if (rv.IsUncatchableException()) {
-    // Nuke any existing exception on aCx, to make sure we're uncatchable.
-    JS_ClearPendingException(cx);
-    // Don't do any reporting.  Just return false, to create an
-    // uncatchable exception.
-    return false;
-  }
-  if (rv.IsJSContextException()) {
-    // Whatever we need to throw is on the JSContext already.  We
-    // can't assert that there is a pending exception on it, though,
-    // because in the uncatchable exception case there won't be one.
-    return false;
-  }
-  if (rv.IsErrorWithMessage()) {
-    rv.ReportErrorWithMessage(cx);
-    return false;
-  }
-  if (rv.IsJSException()) {
-    rv.ReportJSException(cx);
-    return false;
-  }
-  if (rv.IsDOMException()) {
-    rv.ReportDOMException(cx);
-    return false;
-  }
-  rv.ReportGenericError(cx);
-  return false;
-}
-
-bool
 ThrowNoSetterArg(JSContext* aCx, prototypes::ID aProtoId)
 {
   nsPrintfCString errorMessage("%s attribute setter",
                                NamesOfInterfacesWithProtos(aProtoId));
   return ThrowErrorMessage(aCx, MSG_MISSING_ARGUMENTS, errorMessage.get());
 }
 
 } // namespace dom
@@ -217,35 +185,36 @@ ErrorResult::DeserializeMessage(const IP
   mMessage = readMessage.forget();
 #ifdef DEBUG
   mUnionState = HasMessage;
 #endif // DEBUG
   return true;
 }
 
 void
-ErrorResult::ReportErrorWithMessage(JSContext* aCx)
+ErrorResult::SetPendingExceptionWithMessage(JSContext* aCx)
 {
-  MOZ_ASSERT(mMessage, "ReportErrorWithMessage() can be called only once");
+  MOZ_ASSERT(mMessage, "SetPendingExceptionWithMessage() can be called only once");
   MOZ_ASSERT(mUnionState == HasMessage);
 
   Message* message = mMessage;
   MOZ_RELEASE_ASSERT(message->HasCorrectNumberOfArguments());
   const uint32_t argCount = message->mArgs.Length();
   const char16_t* args[JS::MaxNumErrorArguments + 1];
   for (uint32_t i = 0; i < argCount; ++i) {
     args[i] = message->mArgs.ElementAt(i).get();
   }
   args[argCount] = nullptr;
 
   JS_ReportErrorNumberUCArray(aCx, dom::GetErrorMessage, nullptr,
                               static_cast<const unsigned>(message->mErrorNumber),
                               argCount > 0 ? args : nullptr);
 
   ClearMessage();
+  mResult = NS_OK;
 }
 
 void
 ErrorResult::ClearMessage()
 {
   MOZ_ASSERT(IsErrorWithMessage());
   delete mMessage;
   mMessage = nullptr;
@@ -275,50 +244,31 @@ ErrorResult::ThrowJSException(JSContext*
     mResult = NS_ERROR_DOM_JS_EXCEPTION;
 #ifdef DEBUG
     mUnionState = HasJSException;
 #endif // DEBUG
   }
 }
 
 void
-ErrorResult::ReportJSException(JSContext* cx)
+ErrorResult::SetPendingJSException(JSContext* cx)
 {
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Why didn't you tell us you planned to handle JS exceptions?");
   MOZ_ASSERT(mUnionState == HasJSException);
 
   JS::Rooted<JS::Value> exception(cx, mJSException);
   if (JS_WrapValue(cx, &exception)) {
     JS_SetPendingException(cx, exception);
   }
   mJSException = exception;
   // If JS_WrapValue failed, not much we can do about it...  No matter
   // what, go ahead and unroot mJSException.
   js::RemoveRawValueRoot(cx, &mJSException);
 
-  // We no longer have a useful exception but we do want to signal that an error
-  // occured.
-  mResult = NS_ERROR_FAILURE;
-#ifdef DEBUG
-  mUnionState = HasNothing;
-#endif // DEBUG
-}
-
-void
-ErrorResult::StealJSException(JSContext* cx,
-                              JS::MutableHandle<JS::Value> value)
-{
-  MOZ_ASSERT(!mMightHaveUnreportedJSException,
-             "Must call WouldReportJSException unconditionally in all codepaths that might call StealJSException");
-  MOZ_ASSERT(IsJSException(), "No exception to steal");
-  MOZ_ASSERT(mUnionState == HasJSException);
-
-  value.set(mJSException);
-  js::RemoveRawValueRoot(cx, &mJSException);
   mResult = NS_OK;
 #ifdef DEBUG
   mUnionState = HasNothing;
 #endif // DEBUG
 }
 
 struct ErrorResult::DOMExceptionInfo {
   DOMExceptionInfo(nsresult rv, const nsACString& message)
@@ -368,24 +318,26 @@ ErrorResult::ThrowDOMException(nsresult 
   mResult = NS_ERROR_DOM_DOMEXCEPTION;
   mDOMExceptionInfo = new DOMExceptionInfo(rv, message);
 #ifdef DEBUG
   mUnionState = HasDOMExceptionInfo;
 #endif
 }
 
 void
-ErrorResult::ReportDOMException(JSContext* cx)
+ErrorResult::SetPendingDOMException(JSContext* cx)
 {
-  MOZ_ASSERT(mDOMExceptionInfo, "ReportDOMException() can be called only once");
+  MOZ_ASSERT(mDOMExceptionInfo,
+             "SetPendingDOMException() can be called only once");
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
 
   dom::Throw(cx, mDOMExceptionInfo->mRv, mDOMExceptionInfo->mMessage);
 
   ClearDOMExceptionInfo();
+  mResult = NS_OK;
 }
 
 void
 ErrorResult::ClearDOMExceptionInfo()
 {
   MOZ_ASSERT(IsDOMException());
   MOZ_ASSERT(mUnionState == HasDOMExceptionInfo || !mDOMExceptionInfo);
   delete mDOMExceptionInfo;
@@ -409,22 +361,23 @@ ErrorResult::ClearUnionData()
   } else if (IsErrorWithMessage()) {
     ClearMessage();
   } else if (IsDOMException()) {
     ClearDOMExceptionInfo();
   }
 }
 
 void
-ErrorResult::ReportGenericError(JSContext* cx)
+ErrorResult::SetPendingGenericErrorException(JSContext* cx)
 {
   MOZ_ASSERT(!IsErrorWithMessage());
   MOZ_ASSERT(!IsJSException());
   MOZ_ASSERT(!IsDOMException());
   dom::Throw(cx, ErrorCode());
+  mResult = NS_OK;
 }
 
 ErrorResult&
 ErrorResult::operator=(ErrorResult&& aRHS)
 {
   // Clear out any union members we may have right now, before we
   // start writing to it.
   ClearUnionData();
@@ -503,16 +456,49 @@ ErrorResult::SuppressException()
 {
   WouldReportJSException();
   ClearUnionData();
   // We don't use AssignErrorCode, because we want to override existing error
   // states, which AssignErrorCode is not allowed to do.
   mResult = NS_OK;
 }
 
+void
+ErrorResult::SetPendingException(JSContext* cx)
+{
+  if (IsUncatchableException()) {
+    // Nuke any existing exception on cx, to make sure we're uncatchable.
+    JS_ClearPendingException(cx);
+    // Don't do any reporting.  Just return, to create an
+    // uncatchable exception.
+    mResult = NS_OK;
+    return;
+  }
+  if (IsJSContextException()) {
+    // Whatever we need to throw is on the JSContext already.  We
+    // can't assert that there is a pending exception on it, though,
+    // because in the uncatchable exception case there won't be one.
+    mResult = NS_OK;
+    return;
+  }
+  if (IsErrorWithMessage()) {
+    SetPendingExceptionWithMessage(cx);
+    return;
+  }
+  if (IsJSException()) {
+    SetPendingJSException(cx);
+    return;
+  }
+  if (IsDOMException()) {
+    SetPendingDOMException(cx);
+    return;
+  }
+  SetPendingGenericErrorException(cx);
+}
+
 namespace dom {
 
 bool
 DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
                 const ConstantSpec* cs)
 {
   JS::Rooted<JS::Value> value(cx);
   for (; cs->name; ++cs) {
@@ -2772,20 +2758,20 @@ ConvertExceptionToPromise(JSContext* cx,
     // ending up in this code, that means the callee threw an uncatchable
     // exception.  Just propagate that out as-is.
     return false;
   }
 
   JS_ClearPendingException(cx);
   ErrorResult rv;
   RefPtr<Promise> promise = Promise::Reject(global, exn, rv);
-  if (rv.Failed()) {
-    // We just give up.  Make sure to not leak memory on the
-    // ErrorResult, but then just put the original exception back.
-    ThrowMethodFailed(cx, rv);
+  if (rv.MaybeSetPendingException(cx)) {
+    // We just give up.  We put the exception from the ErrorResult on
+    // the JSContext just to make sure to not leak memory on the
+    // ErrorResult, but now just put the original exception back.
     JS_SetPendingException(cx, exn);
     return false;
   }
 
   return GetOrCreateDOMReflector(cx, promise, rval);
 }
 
 /* static */
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -87,19 +87,16 @@ ThrowInvalidThis(JSContext* aCx, const J
                  const ErrNum aErrorNumber,
                  const char* aInterfaceName);
 
 bool
 ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
                  const ErrNum aErrorNumber,
                  prototypes::ID aProtoId);
 
-bool
-ThrowMethodFailed(JSContext* cx, ErrorResult& rv);
-
 // Returns true if the JSClass is used for DOM objects.
 inline bool
 IsDOMClass(const JSClass* clasp)
 {
   return clasp->flags & JSCLASS_IS_DOMJSCLASS;
 }
 
 inline bool
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1658,21 +1658,17 @@ class CGClassConstructor(CGAbstractStati
         else:
             ctorName = self.descriptor.interface.identifier.name
 
         preamble = fill(
             """
             JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
             JS::Rooted<JSObject*> obj(cx, &args.callee());
             $*{chromeOnlyCheck}
-            bool mayInvoke = args.isConstructing();
-            #ifdef RELEASE_BUILD
-            mayInvoke = mayInvoke || nsContentUtils::ThreadsafeIsCallerChrome();
-            #endif // RELEASE_BUILD
-            if (!mayInvoke) {
+            if (!args.isConstructing()) {
               // XXXbz wish I could get the name from the callee instead of
               // Adding more relocations
               return ThrowConstructorWithoutNew(cx, "${ctorName}");
             }
             JS::Rooted<JSObject*> desiredProto(cx);
             if (!GetDesiredProto(cx, args, &desiredProto)) {
               return false;
             }
@@ -1723,19 +1719,17 @@ class CGConstructNavigatorObject(CGAbstr
             GlobalObject global(aCx, aObj);
             if (global.Failed()) {
               return nullptr;
             }
             ErrorResult rv;
             JS::Rooted<JS::Value> v(aCx);
             {  // Scope to make sure |result| goes out of scope while |v| is rooted
               RefPtr<mozilla::dom::${descriptorName}> result = ConstructNavigatorObjectHelper(aCx, global, rv);
-              rv.WouldReportJSException();
-              if (rv.Failed()) {
-                ThrowMethodFailed(aCx, rv);
+              if (rv.MaybeSetPendingException(aCx)) {
                 return nullptr;
               }
               if (!GetOrCreateDOMReflector(aCx, result, &v)) {
                 //XXX Assertion disabled for now, see bug 991271.
                 MOZ_ASSERT(true || JS_IsExceptionPending(aCx));
                 return nullptr;
               }
             }
@@ -5096,18 +5090,17 @@ def getJSToNativeConversionInfo(type, de
                   // least give us the same behavior as if the caller just called
                   // Promise.resolve() themselves.
                   GlobalObject promiseGlobal(cx, JS::CurrentGlobalOrNull(cx));
                   if (promiseGlobal.Failed()) {
                     $*{exceptionCode}
                   }
                   ErrorResult promiseRv;
                   $${declName} = Promise::Resolve(promiseGlobal, $${val}, promiseRv);
-                  if (promiseRv.Failed()) {
-                    ThrowMethodFailed(cx, promiseRv);
+                  if (promiseRv.MaybeSetPendingException(cx)) {
                     $*{exceptionCode}
                   }
                 }
                 """,
                 exceptionCode=exceptionCode)
         elif not descriptor.skipGen and not descriptor.interface.isConsequential() and not descriptor.interface.isExternal():
             if failureCode is not None:
                 templateBody += str(CastableObjectUnwrapper(
@@ -6269,31 +6262,24 @@ def getWrapTemplateForType(type, descrip
 
     if type.isEnum():
         if type.nullable():
             resultLoc = "%s.Value()" % result
         else:
             resultLoc = result
         conversion = fill(
             """
-            {
-              // Scope for resultStr
-              MOZ_ASSERT(uint32_t(${result}) < ArrayLength(${strings}));
-              JSString* resultStr = JS_NewStringCopyN(cx, ${strings}[uint32_t(${result})].value, ${strings}[uint32_t(${result})].length);
-              if (!resultStr) {
-                $*{exceptionCode}
-              }
-              $*{setResultStr}
-            }
+            if (!ToJSValue(cx, ${result}, $${jsvalHandle})) {
+              $*{exceptionCode}
+            }
+            $*{successCode}
             """,
             result=resultLoc,
-            strings=(type.unroll().inner.identifier.name + "Values::" +
-                     ENUM_ENTRY_VARIABLE_NAME),
             exceptionCode=exceptionCode,
-            setResultStr=setString("resultStr"))
+            successCode=successCode)
 
         if type.nullable():
             conversion = CGIfElseWrapper(
                 "%s.IsNull()" % result,
                 CGGeneric(setNull()),
                 CGGeneric(conversion)).define()
         return conversion, False
 
@@ -6659,33 +6645,28 @@ def needScopeObject(returnType, argument
              any(typeNeedsScopeObject(a.type) for a in arguments)))
 
 
 class CGCallGenerator(CGThing):
     """
     A class to generate an actual call to a C++ object.  Assumes that the C++
     object is stored in a variable whose name is given by the |object| argument.
 
-    errorReport should be a CGThing for an error report or None if no
-    error reporting is needed.
+    isFallible is a boolean indicating whether the call should be fallible.
 
     resultVar: If the returnType is not void, then the result of the call is
     stored in a C++ variable named by resultVar. The caller is responsible for
     declaring the result variable. If the caller doesn't care about the result
     value, resultVar can be omitted.
     """
-    def __init__(self, errorReport, arguments, argsPre, returnType,
+    def __init__(self, isFallible, arguments, argsPre, returnType,
                  extendedAttributes, descriptorProvider, nativeMethodName,
                  static, object="self", argsPost=[], resultVar=None):
         CGThing.__init__(self)
 
-        assert errorReport is None or isinstance(errorReport, CGThing)
-
-        isFallible = errorReport is not None
-
         result, resultOutParam, resultRooter, resultArgs, resultConversion = \
             getRetvalDeclarationForType(returnType, descriptorProvider)
 
         args = CGList([CGGeneric(arg) for arg in argsPre], ", ")
         for a, name in arguments:
             arg = CGGeneric(name)
 
             # Now constify the things that need it
@@ -6770,20 +6751,22 @@ class CGCallGenerator(CGThing):
             assert resultOutParam is None
             call = CGWrapper(call, pre=resultVar + " = ")
 
         call = CGWrapper(call, post=";\n")
         self.cgRoot.append(call)
 
         if isFallible:
             self.cgRoot.prepend(CGGeneric("ErrorResult rv;\n"))
-            self.cgRoot.append(CGGeneric("rv.WouldReportJSException();\n"))
-            self.cgRoot.append(CGGeneric("if (MOZ_UNLIKELY(rv.Failed())) {\n"))
-            self.cgRoot.append(CGIndenter(errorReport))
-            self.cgRoot.append(CGGeneric("}\n"))
+            self.cgRoot.append(CGGeneric(dedent(
+                """
+                if (MOZ_UNLIKELY(rv.MaybeSetPendingException(cx))) {
+                  return false;
+                }
+                """)))
 
         self.cgRoot.append(CGGeneric("MOZ_ASSERT(!JS_IsExceptionPending(cx));\n"))
 
     def define(self):
         return self.cgRoot.define()
 
 
 def getUnionMemberName(type):
@@ -7167,17 +7150,17 @@ class CGPerSignatureCall(CGThing):
                                                                   idlNode.maplikeOrSetlikeOrIterable,
                                                                   idlNode.identifier.name))
             else:
                 cgThings.append(CGIterableMethodGenerator(descriptor,
                                                           idlNode.maplikeOrSetlikeOrIterable,
                                                           idlNode.identifier.name))
         else:
             cgThings.append(CGCallGenerator(
-                self.getErrorReport() if self.isFallible() else None,
+                self.isFallible(),
                 self.getArguments(), argsPre, returnType,
                 self.extendedAttributes, descriptor, nativeMethodName,
                 static, argsPost=argsPost, resultVar=resultVar))
 
         if useCounterName:
             # Generate a telemetry call for when [UseCounter] is used.
             code = "SetDocumentAndPageUseCounter(cx, obj, eUseCounter_%s);\n" % useCounterName
             cgThings.append(CGGeneric(code))
@@ -7274,19 +7257,16 @@ class CGPerSignatureCall(CGThing):
                 // And now make sure args.rval() is in the caller compartment
                 return ${maybeWrap}(cx, args.rval());
                 """,
                 wrapCode=wrapCode,
                 postSteps=postSteps,
                 maybeWrap=getMaybeWrapValueFuncForType(self.idlNode.type))
         return wrapCode
 
-    def getErrorReport(self):
-        return CGGeneric('return ThrowMethodFailed(cx, rv);\n')
-
     def define(self):
         return (self.cgRoot.define() + self.wrap_return_value())
 
 
 class CGSwitch(CGList):
     """
     A class to generate code for a switch statement.
 
@@ -8245,19 +8225,18 @@ class CGEnumerateHook(CGAbstractBindingM
             self, descriptor, ENUMERATE_HOOK_NAME,
             args, getThisObj="", callArgs="")
 
     def generate_code(self):
         return CGGeneric(dedent("""
             nsAutoTArray<nsString, 8> names;
             ErrorResult rv;
             self->GetOwnPropertyNames(cx, names, rv);
-            rv.WouldReportJSException();
-            if (rv.Failed()) {
-              return ThrowMethodFailed(cx, rv);
+            if (rv.MaybeSetPendingException(cx)) {
+              return false;
             }
             bool dummy;
             for (uint32_t i = 0; i < names.Length(); ++i) {
               if (!JS_HasUCProperty(cx, obj, names[i].get(), names[i].Length(), &dummy)) {
                 return false;
               }
             }
             return true;
@@ -9063,21 +9042,63 @@ def getEnumValueName(value):
         return "_empty"
     nativeName = MakeNativeName(value)
     if nativeName == "EndGuard_":
         raise SyntaxError('Enum value "' + value + '" cannot be used because it'
                           ' collides with our internal EndGuard_ value.  Please'
                           ' rename our internal EndGuard_ to something else')
     return nativeName
 
+class CGEnumToJSValue(CGAbstractMethod):
+    def __init__(self, enum):
+        enumType = enum.identifier.name
+        self.stringsArray = enumType + "Values::" + ENUM_ENTRY_VARIABLE_NAME
+        CGAbstractMethod.__init__(self, None, "ToJSValue", "bool",
+                                  [Argument("JSContext*", "aCx"),
+                                   Argument(enumType, "aArgument"),
+                                   Argument("JS::MutableHandle<JS::Value>",
+                                            "aValue")])
+
+    def definition_body(self):
+        return fill(
+            """
+            MOZ_ASSERT(uint32_t(aArgument) < ArrayLength(${strings}));
+            JSString* resultStr =
+              JS_NewStringCopyN(aCx, ${strings}[uint32_t(aArgument)].value,
+                                ${strings}[uint32_t(aArgument)].length);
+            if (!resultStr) {
+              return false;
+            }
+            aValue.setString(resultStr);
+            return true;
+            """,
+            strings=self.stringsArray)
+
 
 class CGEnum(CGThing):
     def __init__(self, enum):
         CGThing.__init__(self)
         self.enum = enum
+        strings = CGNamespace(
+            self.stringsNamespace(),
+            CGGeneric(declare=("extern const EnumEntry %s[%d];\n" %
+                               (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())),
+                      define=fill(
+                          """
+                          extern const EnumEntry ${name}[${count}] = {
+                            $*{entries}
+                            { nullptr, 0 }
+                          };
+                          """,
+                          name=ENUM_ENTRY_VARIABLE_NAME,
+                          count=self.nEnumStrings(),
+                          entries=''.join('{"%s", %d},\n' % (val, len(val))
+                                          for val in self.enum.values()))))
+        toJSValue = CGEnumToJSValue(enum)
+        self.cgThings = CGList([strings, toJSValue], "\n")
 
     def stringsNamespace(self):
         return self.enum.identifier.name + "Values"
 
     def nEnumStrings(self):
         return len(self.enum.values()) + 1
 
     def declare(self):
@@ -9088,32 +9109,20 @@ class CGEnum(CGThing):
               EndGuard_
             };
             """,
             name=self.enum.identifier.name,
             enums=",\n".join(map(getEnumValueName, self.enum.values())) + ",\n")
         strings = CGNamespace(self.stringsNamespace(),
                               CGGeneric(declare="extern const EnumEntry %s[%d];\n"
                                         % (ENUM_ENTRY_VARIABLE_NAME, self.nEnumStrings())))
-        return decl + "\n" + strings.declare()
+        return decl + "\n" + self.cgThings.declare()
 
     def define(self):
-        strings = fill(
-            """
-            extern const EnumEntry ${name}[${count}] = {
-              $*{entries}
-              { nullptr, 0 }
-            };
-            """,
-            name=ENUM_ENTRY_VARIABLE_NAME,
-            count=self.nEnumStrings(),
-            entries=''.join('{"%s", %d},\n' % (val, len(val))
-                            for val in self.enum.values()))
-        return CGNamespace(self.stringsNamespace(),
-                           CGGeneric(define=indent(strings))).define()
+        return self.cgThings.define()
 
     def deps(self):
         return self.enum.getDeps()
 
 
 def getUnionAccessorSignatureType(type, descriptorProvider):
     """
     Returns the types that are used in the getter and setter signatures for
@@ -10303,19 +10312,18 @@ class CGEnumerateOwnPropertiesViaGetOwnP
                                          args, getThisObj="",
                                          callArgs="")
 
     def generate_code(self):
         return CGGeneric(dedent("""
             nsAutoTArray<nsString, 8> names;
             ErrorResult rv;
             self->GetOwnPropertyNames(cx, names, rv);
-            rv.WouldReportJSException();
-            if (rv.Failed()) {
-              return ThrowMethodFailed(cx, rv);
+            if (rv.MaybeSetPendingException(cx)) {
+              return false;
             }
             // OK to pass null as "proxy" because it's ignored if
             // shadowPrototypeProperties is true
             return AppendNamedPropertyIds(cx, nullptr, names, true, props);
             """))
 
 
 class CGPrototypeTraitsClass(CGClass):
@@ -12167,21 +12175,19 @@ class CGDictionary(CGThing):
             body.append(CGGeneric(
                 "%s::operator=(aOther);\n" %
                 self.makeClassName(self.dictionary.parent)))
         for m, _ in self.memberInfo:
             memberName = self.makeMemberName(m.identifier.name)
             if m.canHaveMissingValue():
                 memberAssign = CGGeneric(fill(
                     """
+                    ${name}.Reset();
                     if (aOther.${name}.WasPassed()) {
-                      ${name}.Construct();
-                      ${name}.Value() = aOther.${name}.Value();
-                    } else {
-                      ${name}.Reset();
+                      ${name}.Construct(aOther.${name}.Value());
                     }
                     """,
                     name=memberName))
             else:
                 memberAssign = CGGeneric(
                     "%s = aOther.%s;\n" % (memberName, memberName))
             body.append(memberAssign)
         return ClassMethod(
@@ -13009,16 +13015,21 @@ class CGBindingRoot(CGThing):
         bindingHeaders["mozilla/OwningNonNull.h"] = hasCode
         bindingHeaders["mozilla/dom/BindingDeclarations.h"] = (
             not hasCode and enums)
 
         bindingHeaders["WrapperFactory.h"] = descriptors
         bindingHeaders["mozilla/dom/DOMJSClass.h"] = descriptors
         bindingHeaders["mozilla/dom/ScriptSettings.h"] = dictionaries  # AutoJSAPI
         bindingHeaders["xpcpublic.h"] = dictionaries  # xpc::UnprivilegedJunkScope
+        # Ensure we see our enums in the generated .cpp file, for the ToJSValue
+        # method body.  Also ensure that we see jsapi.h.
+        if enums:
+            bindingHeaders[CGHeaders.getDeclarationFilename(enums[0])] = True
+            bindingHeaders["jsapi.h"] = True
 
         # For things that have [UseCounter]
         def descriptorRequiresTelemetry(desc):
             iface = desc.interface
             return any(m.getExtendedAttribute("UseCounter") for m in iface.members)
         bindingHeaders["mozilla/UseCounter.h"] = any(
             descriptorRequiresTelemetry(d) for d in descriptors)
 
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -1,16 +1,27 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=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/. */
 
 /**
  * A struct for tracking exceptions that need to be thrown to JS.
+ *
+ * Conceptually, an ErrorResult represents either success or an exception in the
+ * process of being thrown.  This means that a failing ErrorResult _must_ be
+ * handled in one of the following ways before coming off the stack:
+ *
+ * 1) Suppressed via SuppressException().
+ * 2) Converted to a pure nsresult return value via StealNSResult().
+ * 3) Converted to an actual pending exception on a JSContext via
+ *    MaybeSetPendingException.
+ * 4) Converted to an exception JS::Value (probably to then reject a Promise
+ *    with) via dom::ToJSValue.
  */
 
 #ifndef mozilla_ErrorResult_h
 #define mozilla_ErrorResult_h
 
 #include <stdarg.h>
 
 #include "js/Value.h"
@@ -56,24 +67,24 @@ ThrowErrorMessage(JSContext* aCx, const 
 struct StringArrayAppender
 {
   static void Append(nsTArray<nsString>& aArgs, uint16_t aCount)
   {
     MOZ_RELEASE_ASSERT(aCount == 0, "Must give at least as many string arguments as are required by the ErrNum.");
   }
 
   template<typename... Ts>
-  static void Append(nsTArray<nsString>& aArgs, uint16_t aCount, const nsAString* aFirst, Ts... aOtherArgs)
+  static void Append(nsTArray<nsString>& aArgs, uint16_t aCount, const nsAString& aFirst, Ts&&... aOtherArgs)
   {
     if (aCount == 0) {
       MOZ_ASSERT(false, "There should not be more string arguments provided than are required by the ErrNum.");
       return;
     }
-    aArgs.AppendElement(*aFirst);
-    Append(aArgs, aCount - 1, aOtherArgs...);
+    aArgs.AppendElement(aFirst);
+    Append(aArgs, aCount - 1, Forward<Ts>(aOtherArgs)...);
   }
 };
 
 } // namespace dom
 
 class ErrorResult {
 public:
   ErrorResult()
@@ -82,18 +93,19 @@ public:
     , mMightHaveUnreportedJSException(false)
     , mUnionState(HasNothing)
 #endif
   {
   }
 
 #ifdef DEBUG
   ~ErrorResult() {
-    MOZ_ASSERT_IF(IsErrorWithMessage(), !mMessage);
-    MOZ_ASSERT_IF(IsDOMException(), !mDOMExceptionInfo);
+    // Consumers should have called one of MaybeSetPendingException
+    // (possibly via ToJSValue), StealNSResult, and SuppressException
+    MOZ_ASSERT(!Failed());
     MOZ_ASSERT(!mMightHaveUnreportedJSException);
     MOZ_ASSERT(mUnionState == HasNothing);
   }
 #endif // DEBUG
 
   ErrorResult(ErrorResult&& aRHS)
     // Initialize mResult and whatever else we need to default-initialize, so
     // the ClearUnionData call in our operator= will do the right thing
@@ -129,82 +141,110 @@ public:
   // nsresult that you will then return to a caller.  This will
   // SuppressException(), since there will no longer be a way to report it.
   nsresult StealNSResult() {
     nsresult rv = ErrorCode();
     SuppressException();
     return rv;
   }
 
-  template<dom::ErrNum errorNumber, typename... Ts>
-  void ThrowTypeError(Ts... messageArgs)
+  // Use MaybeSetPendingException to convert an ErrorResult to a pending
+  // exception on the given JSContext.  This is the normal "throw an exception"
+  // codepath.
+  //
+  // The return value is false if the ErrorResult represents success, true
+  // otherwise.  This does mean that in JSAPI method implementations you can't
+  // just use this as |return rv.MaybeSetPendingException(cx)| (though you could
+  // |return !rv.MaybeSetPendingException(cx)|), but in practice pretty much any
+  // consumer would want to do some more work on the success codepath.  So
+  // instead the way you use this is:
+  //
+  //   if (rv.MaybeSetPendingException(cx)) {
+  //     bail out here
+  //   }
+  //   go on to do something useful
+  //
+  // The success path is inline, since it should be the common case and we don't
+  // want to pay the price of a function call in some of the consumers of this
+  // method in the common case.
+  //
+  // Note that a true return value does NOT mean there is now a pending
+  // exception on aCx, due to uncatchable exceptions.  It should still be
+  // considered equivalent to a JSAPI failure in terms of what callers should do
+  // after true is returned.
+  //
+  // After this call, the ErrorResult will no longer return true from Failed(),
+  // since the exception will have moved to the JSContext.
+  bool MaybeSetPendingException(JSContext* cx)
   {
-    ThrowErrorWithMessage<errorNumber>(NS_ERROR_TYPE_ERR, messageArgs...);
+    WouldReportJSException();
+    if (!Failed()) {
+      return false;
+    }
+
+    SetPendingException(cx);
+    return true;
   }
 
   template<dom::ErrNum errorNumber, typename... Ts>
-  void ThrowRangeError(Ts... messageArgs)
+  void ThrowTypeError(Ts&&... messageArgs)
   {
-    ThrowErrorWithMessage<errorNumber>(NS_ERROR_RANGE_ERR, messageArgs...);
+    ThrowErrorWithMessage<errorNumber>(NS_ERROR_TYPE_ERR,
+                                       Forward<Ts>(messageArgs)...);
   }
 
-  void ReportErrorWithMessage(JSContext* cx);
+  template<dom::ErrNum errorNumber, typename... Ts>
+  void ThrowRangeError(Ts&&... messageArgs)
+  {
+    ThrowErrorWithMessage<errorNumber>(NS_ERROR_RANGE_ERR,
+                                       Forward<Ts>(messageArgs)...);
+  }
+
   bool IsErrorWithMessage() const { return ErrorCode() == NS_ERROR_TYPE_ERR || ErrorCode() == NS_ERROR_RANGE_ERR; }
 
   // Facilities for throwing a preexisting JS exception value via this
   // ErrorResult.  The contract is that any code which might end up calling
   // ThrowJSException() must call MightThrowJSException() even if no exception
-  // is being thrown.  Code that would call ReportJSException or
-  // StealJSException as needed must first call WouldReportJSException even if
+  // is being thrown.  Code that conditionally calls ToJSValue on this
+  // ErrorResult only if Failed() must first call WouldReportJSException even if
   // this ErrorResult has not failed.
   //
   // The exn argument to ThrowJSException can be in any compartment.  It does
   // not have to be in the compartment of cx.  If someone later uses it, they
   // will wrap it into whatever compartment they're working in, as needed.
   void ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
-  void ReportJSException(JSContext* cx);
   bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; }
 
   // Facilities for throwing a DOMException.  If an empty message string is
   // passed to ThrowDOMException, the default message string for the given
   // nsresult will be used.  The passed-in string must be UTF-8.  The nsresult
   // passed in must be one we create DOMExceptions for; otherwise you may get an
   // XPConnect Exception.
   void ThrowDOMException(nsresult rv, const nsACString& message = EmptyCString());
-  void ReportDOMException(JSContext* cx);
   bool IsDOMException() const { return ErrorCode() == NS_ERROR_DOM_DOMEXCEPTION; }
 
   // Flag on the ErrorResult that whatever needs throwing has been
   // thrown on the JSContext already and we should not mess with it.
   void NoteJSContextException() {
     mResult = NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
   }
   // Check whether the ErrorResult says to just throw whatever is on
   // the JSContext already.
   bool IsJSContextException() {
     return ErrorCode() == NS_ERROR_DOM_EXCEPTION_ON_JSCONTEXT;
   }
 
-  // Report a generic error.  This should only be used if we're not
-  // some more specific exception type.
-  void ReportGenericError(JSContext* cx);
-
   // Support for uncatchable exceptions.
   void ThrowUncatchableException() {
     Throw(NS_ERROR_UNCATCHABLE_EXCEPTION);
   }
   bool IsUncatchableException() const {
     return ErrorCode() == NS_ERROR_UNCATCHABLE_EXCEPTION;
   }
 
-  // StealJSException steals the JS Exception from the object. This method must
-  // be called only if IsJSException() returns true. This method also resets the
-  // error code to NS_OK.
-  void StealJSException(JSContext* cx, JS::MutableHandle<JS::Value> value);
-
   void MOZ_ALWAYS_INLINE MightThrowJSException()
   {
 #ifdef DEBUG
     mMightHaveUnreportedJSException = true;
 #endif
   }
   void MOZ_ALWAYS_INLINE WouldReportJSException()
   {
@@ -259,28 +299,29 @@ private:
   void SerializeDOMExceptionInfo(IPC::Message* aMsg) const;
   bool DeserializeDOMExceptionInfo(const IPC::Message* aMsg, void** aIter);
 
   // Helper method that creates a new Message for this ErrorResult,
   // and returns the arguments array from that Message.
   nsTArray<nsString>& CreateErrorMessageHelper(const dom::ErrNum errorNumber, nsresult errorType);
 
   template<dom::ErrNum errorNumber, typename... Ts>
-  void ThrowErrorWithMessage(nsresult errorType, Ts... messageArgs)
+  void ThrowErrorWithMessage(nsresult errorType, Ts&&... messageArgs)
   {
 #if defined(DEBUG) && (defined(__clang__) || defined(__GNUC__))
     static_assert(dom::ErrorFormatNumArgs[errorNumber] == sizeof...(messageArgs),
                   "Pass in the right number of arguments");
 #endif
 
     ClearUnionData();
 
     nsTArray<nsString>& messageArgsArray = CreateErrorMessageHelper(errorNumber, errorType);
     uint16_t argCount = dom::GetErrorArgCount(errorNumber);
-    dom::StringArrayAppender::Append(messageArgsArray, argCount, messageArgs...);
+    dom::StringArrayAppender::Append(messageArgsArray, argCount,
+                                     Forward<Ts>(messageArgs)...);
 #ifdef DEBUG
     mUnionState = HasMessage;
 #endif // DEBUG
   }
 
   void AssignErrorCode(nsresult aRv) {
     MOZ_ASSERT(aRv != NS_ERROR_TYPE_ERR, "Use ThrowTypeError()");
     MOZ_ASSERT(aRv != NS_ERROR_RANGE_ERR, "Use ThrowRangeError()");
@@ -301,32 +342,43 @@ private:
   // ClearUnionData will try to clear the data in our
   // mMessage/mJSException/mDOMExceptionInfo union.  After this the union may be
   // in an uninitialized state (e.g. mMessage or mDOMExceptionInfo may be
   // pointing to deleted memory) and the caller must either reinitialize it or
   // change mResult to something that will not involve us touching the union
   // anymore.
   void ClearUnionData();
 
+  // Implementation of MaybeSetPendingException for the case when we're a
+  // failure result.
+  void SetPendingException(JSContext* cx);
+
+  // Methods for setting various specific kinds of pending exceptions.
+  void SetPendingExceptionWithMessage(JSContext* cx);
+  void SetPendingJSException(JSContext* cx);
+  void SetPendingDOMException(JSContext* cx);
+  void SetPendingGenericErrorException(JSContext* cx);
+
+
   // Special values of mResult:
   // NS_ERROR_TYPE_ERR -- ThrowTypeError() called on us.
   // NS_ERROR_RANGE_ERR -- ThrowRangeError() called on us.
   // NS_ERROR_DOM_JS_EXCEPTION -- ThrowJSException() called on us.
   // NS_ERROR_UNCATCHABLE_EXCEPTION -- ThrowUncatchableException called on us.
   // NS_ERROR_DOM_DOMEXCEPTION -- ThrowDOMException() called on us.
   nsresult mResult;
 
   struct Message;
   struct DOMExceptionInfo;
   // mMessage is set by ThrowErrorWithMessage and reported (and deallocated) by
-  // ReportErrorWithMessage.
+  // SetPendingExceptionWithMessage.
   // mJSException is set (and rooted) by ThrowJSException and reported
-  // (and unrooted) by ReportJSException.
+  // (and unrooted) by SetPendingJSException.
   // mDOMExceptionInfo is set by ThrowDOMException and reported
-  // (and deallocated) by ReportDOMException.
+  // (and deallocated) by SetPendingDOMException.
   union {
     Message* mMessage; // valid when IsErrorWithMessage()
     JS::Value mJSException; // valid when IsJSException()
     DOMExceptionInfo* mDOMExceptionInfo; // valid when IsDOMException()
   };
 
 #ifdef DEBUG
   // Used to keep track of codepaths that might throw JS exceptions,
--- a/dom/bindings/ToJSValue.cpp
+++ b/dom/bindings/ToJSValue.cpp
@@ -51,18 +51,18 @@ bool
 ToJSValue(JSContext* aCx,
           ErrorResult& aArgument,
           JS::MutableHandle<JS::Value> aValue)
 {
   MOZ_ASSERT(aArgument.Failed());
   MOZ_ASSERT(!aArgument.IsUncatchableException(),
              "Doesn't make sense to convert uncatchable exception to a JS value!");
   AutoForceSetExceptionOnContext forceExn(aCx);
-  DebugOnly<bool> throwResult = ThrowMethodFailed(aCx, aArgument);
-  MOZ_ASSERT(!throwResult);
+  DebugOnly<bool> throwResult = aArgument.MaybeSetPendingException(aCx);
+  MOZ_ASSERT(throwResult);
   DebugOnly<bool> getPendingResult = JS_GetPendingException(aCx, aValue);
   MOZ_ASSERT(getPendingResult);
   JS_ClearPendingException(aCx);
   return true;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/bluetooth/common/BluetoothService.cpp
+++ b/dom/bluetooth/common/BluetoothService.cpp
@@ -14,16 +14,17 @@
 #include "BluetoothServiceChildProcess.h"
 #include "BluetoothUtils.h"
 
 #include "jsapi.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
+#include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "nsContentUtils.h"
 #include "nsIObserverService.h"
 #include "nsISettingsService.h"
 #include "nsISystemMessagesInternal.h"
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -2032,16 +2032,19 @@ BrowserElementChild.prototype = {
             sendAsyncMsg('error', { type: 'phishingBlocked' });
             return;
           case Cr.NS_ERROR_MALWARE_URI :
             sendAsyncMsg('error', { type: 'malwareBlocked' });
             return;
           case Cr.NS_ERROR_UNWANTED_URI :
             sendAsyncMsg('error', { type: 'unwantedBlocked' });
             return;
+          case Cr.NS_ERROR_FORBIDDEN_URI :
+            sendAsyncMsg('error', { type: 'forbiddenBlocked' });
+            return;
 
           case Cr.NS_ERROR_OFFLINE :
             sendAsyncMsg('error', { type: 'offline' });
             return;
           case Cr.NS_ERROR_MALFORMED_URI :
             sendAsyncMsg('error', { type: 'malformedURI' });
             return;
           case Cr.NS_ERROR_REDIRECT_LOOP :
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -41,32 +41,32 @@ IsValidPutRequestURL(const nsAString& aU
   NS_ConvertUTF16toUTF8 url(aUrl);
 
   TypeUtils::ProcessURL(url, &validScheme, nullptr, nullptr, aRv);
   if (aRv.Failed()) {
     return false;
   }
 
   if (!validScheme) {
-    NS_NAMED_LITERAL_STRING(label, "Request");
-    aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(&label, &aUrl);
+    aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
+                                               aUrl);
     return false;
   }
 
   return true;
 }
 
 static bool
 IsValidPutRequestMethod(const Request& aRequest, ErrorResult& aRv)
 {
   nsAutoCString method;
   aRequest.GetMethod(method);
   if (!method.LowerCaseEqualsLiteral("get")) {
     NS_ConvertASCIItoUTF16 label(method);
-    aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(&label);
+    aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label);
     return false;
   }
 
   return true;
 }
 
 static bool
 IsValidPutRequestMethod(const RequestOrUSVString& aRequest, ErrorResult& aRv)
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -256,18 +256,18 @@ CacheStorage::DefineCaches(JSContext* aC
   MOZ_ASSERT(principal);
 
   ErrorResult rv;
   RefPtr<CacheStorage> storage =
     CreateOnMainThread(DEFAULT_NAMESPACE, xpc::NativeGlobal(aGlobal), principal,
                        false, /* private browsing */
                        true,  /* force trusted */
                        rv);
-  if (NS_WARN_IF(rv.Failed())) {
-    return ThrowMethodFailed(aCx, rv);
+  if (NS_WARN_IF(rv.MaybeSetPendingException(aCx))) {
+    return false;
   }
 
   JS::Rooted<JS::Value> caches(aCx);
   js::AssertSameCompartment(aCx, aGlobal);
   if (NS_WARN_IF(!ToJSValue(aCx, storage, &caches))) {
     return false;
   }
 
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -127,33 +127,35 @@ public:
     MOZ_ASSERT(mInitAction);
   }
 
   nsresult Dispatch()
   {
     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(mState == STATE_INIT);
 
-    mState = STATE_OPEN_DIRECTORY;
+    mState = STATE_GET_INFO;
     nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mState = STATE_COMPLETE;
       Clear();
     }
     return rv;
   }
 
   void Cancel()
   {
     NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
     MOZ_ASSERT(!mCanceled);
     mCanceled = true;
     mInitAction->CancelOnInitiatingThread();
   }
 
+  void OpenDirectory();
+
   // OpenDirectoryListener methods
   virtual void
   DirectoryLockAcquired(DirectoryLock* aLock) override;
 
   virtual void
   DirectoryLockFailed() override;
 
 private:
@@ -190,16 +192,18 @@ private:
     MOZ_ASSERT(mState == STATE_COMPLETE);
     MOZ_ASSERT(!mContext);
     MOZ_ASSERT(!mInitAction);
   }
 
   enum State
   {
     STATE_INIT,
+    STATE_GET_INFO,
+    STATE_CREATE_QUOTA_MANAGER,
     STATE_OPEN_DIRECTORY,
     STATE_WAIT_FOR_DIRECTORY_LOCK,
     STATE_ENSURE_ORIGIN_INITIALIZED,
     STATE_RUN_ON_TARGET,
     STATE_RUNNING,
     STATE_COMPLETING,
     STATE_COMPLETE
   };
@@ -229,33 +233,54 @@ private:
   RefPtr<ThreadsafeHandle> mThreadsafeHandle;
   RefPtr<Manager> mManager;
   RefPtr<Data> mData;
   nsCOMPtr<nsIThread> mTarget;
   RefPtr<Action> mInitAction;
   nsCOMPtr<nsIThread> mInitiatingThread;
   nsresult mResult;
   QuotaInfo mQuotaInfo;
-  nsMainThreadPtrHandle<DirectoryLock> mDirectoryLock;
+  RefPtr<DirectoryLock> mDirectoryLock;
   State mState;
   Atomic<bool> mCanceled;
 
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 };
 
 void
+Context::QuotaInitRunnable::OpenDirectory()
+{
+  NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+  MOZ_ASSERT(mState == STATE_CREATE_QUOTA_MANAGER ||
+             mState == STATE_OPEN_DIRECTORY);
+  MOZ_ASSERT(QuotaManager::Get());
+
+  // QuotaManager::OpenDirectory() will hold a reference to us as
+  // a listener.  We will then get DirectoryLockAcquired() on the owning
+  // thread when it is safe to access our storage directory.
+  mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
+  QuotaManager::Get()->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
+                                     mQuotaInfo.mGroup,
+                                     mQuotaInfo.mOrigin,
+                                     mQuotaInfo.mIsApp,
+                                     quota::Client::DOMCACHE,
+                                     /* aExclusive */ false,
+                                     this);
+}
+
+void
 Context::QuotaInitRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
   MOZ_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
   MOZ_ASSERT(!mDirectoryLock);
 
-  mDirectoryLock = new nsMainThreadPtrHolder<DirectoryLock>(aLock);
+  mDirectoryLock = aLock;
 
   if (mCanceled) {
     Complete(NS_ERROR_ABORT);
     return;
   }
 
   QuotaManager* qm = QuotaManager::Get();
   MOZ_ASSERT(qm);
@@ -266,17 +291,17 @@ Context::QuotaInitRunnable::DirectoryLoc
     Complete(rv);
     return;
   }
 }
 
 void
 Context::QuotaInitRunnable::DirectoryLockFailed()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
   MOZ_ASSERT(mState == STATE_WAIT_FOR_DIRECTORY_LOCK);
   MOZ_ASSERT(!mDirectoryLock);
 
   NS_WARNING("Failed to acquire a directory lock!");
 
   Complete(NS_ERROR_FAILURE);
 }
 
@@ -285,23 +310,33 @@ NS_IMPL_ISUPPORTS(mozilla::dom::cache::C
 // The QuotaManager init state machine is represented in the following diagram:
 //
 //    +---------------+
 //    |     Start     |      Resolve(error)
 //    | (Orig Thread) +---------------------+
 //    +-------+-------+                     |
 //            |                             |
 // +----------v-----------+                 |
-// |    OpenDirectory     |  Resolve(error) |
+// |       GetInfo        |  Resolve(error) |
 // |    (Main Thread)     +-----------------+
 // +----------+-----------+                 |
 //            |                             |
 // +----------v-----------+                 |
+// |  CreateQuotaManager  |  Resolve(error) |
+// |    (Orig Thread)     +-----------------+
+// +----------+-----------+                 |
+//            |                             |
+// +----------v-----------+                 |
+// |    OpenDirectory     |  Resolve(error) |
+// |    (Orig Thread)     +-----------------+
+// +----------+-----------+                 |
+//            |                             |
+// +----------v-----------+                 |
 // | WaitForDirectoryLock |  Resolve(error) |
-// |    (Main Thread)     +-----------------+
+// |    (Orig Thread)     +-----------------+
 // +----------+-----------+                 |
 //            |                             |
 // +----------v------------+                |
 // |EnsureOriginInitialized| Resolve(error) |
 // |   (Quota IO Thread)   +----------------+
 // +----------+------------+                |
 //            |                             |
 // +----------v------------+                |
@@ -325,53 +360,71 @@ Context::QuotaInitRunnable::Run()
 {
   // May run on different threads depending on the state.  See individual
   // state cases for thread assertions.
 
   RefPtr<SyncResolver> resolver = new SyncResolver();
 
   switch(mState) {
     // -----------------------------------
-    case STATE_OPEN_DIRECTORY:
+    case STATE_GET_INFO:
     {
       MOZ_ASSERT(NS_IsMainThread());
 
+      if (mCanceled) {
+        resolver->Resolve(NS_ERROR_ABORT);
+        break;
+      }
+
+      RefPtr<ManagerId> managerId = mManager->GetManagerId();
+      nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
+      nsresult rv = QuotaManager::GetInfoFromPrincipal(principal,
+                                                       &mQuotaInfo.mGroup,
+                                                       &mQuotaInfo.mOrigin,
+                                                       &mQuotaInfo.mIsApp);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        resolver->Resolve(rv);
+        break;
+      }
+
+      mState = STATE_CREATE_QUOTA_MANAGER;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
+      break;
+    }
+    // ----------------------------------
+    case STATE_CREATE_QUOTA_MANAGER:
+    {
+      NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+
       if (mCanceled || QuotaManager::IsShuttingDown()) {
         resolver->Resolve(NS_ERROR_ABORT);
         break;
       }
 
-      QuotaManager* qm = QuotaManager::GetOrCreate();
-      if (!qm) {
+      if (QuotaManager::Get()) {
+        OpenDirectory();
+        return NS_OK;
+      }
+
+      mState = STATE_OPEN_DIRECTORY;
+      QuotaManager::GetOrCreate(this);
+      break;
+    }
+    // ----------------------------------
+    case STATE_OPEN_DIRECTORY:
+    {
+      NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
+
+      if (NS_WARN_IF(!QuotaManager::Get())) {
         resolver->Resolve(NS_ERROR_FAILURE);
         break;
       }
 
-      RefPtr<ManagerId> managerId = mManager->GetManagerId();
-      nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
-      nsresult rv = qm->GetInfoFromPrincipal(principal,
-                                             &mQuotaInfo.mGroup,
-                                             &mQuotaInfo.mOrigin,
-                                             &mQuotaInfo.mIsApp);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        resolver->Resolve(rv);
-        break;
-      }
-
-      // QuotaManager::OpenDirectory() will hold a reference to us as
-      // a listener.  We will then get DirectoryLockAcquired() on the main
-      // thread when it is safe to access our storage directory.
-      mState = STATE_WAIT_FOR_DIRECTORY_LOCK;
-      qm->OpenDirectory(PERSISTENCE_TYPE_DEFAULT,
-                        mQuotaInfo.mGroup,
-                        mQuotaInfo.mOrigin,
-                        mQuotaInfo.mIsApp,
-                        quota::Client::DOMCACHE,
-                        /* aExclusive */ false,
-                        this);
+      OpenDirectory();
       break;
     }
     // ----------------------------------
     case STATE_ENSURE_ORIGIN_INITIALIZED:
     {
       AssertIsOnIOThread();
 
       if (mCanceled) {
@@ -420,17 +473,17 @@ Context::QuotaInitRunnable::Run()
 
       break;
     }
     // -------------------
     case STATE_COMPLETING:
     {
       NS_ASSERT_OWNINGTHREAD(QuotaInitRunnable);
       mInitAction->CompleteOnInitiatingThread(mResult);
-      mContext->OnQuotaInit(mResult, mQuotaInfo, mDirectoryLock);
+      mContext->OnQuotaInit(mResult, mQuotaInfo, mDirectoryLock.forget());
       mState = STATE_COMPLETE;
 
       // Explicitly cleanup here as the destructor could fire on any of
       // the threads we have bounced through.
       Clear();
       break;
     }
     // -----
@@ -982,17 +1035,17 @@ Context::DispatchAction(Action* aAction,
     // for this invariant violation.
     MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
   }
   AddActivity(runnable);
 }
 
 void
 Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
-                     nsMainThreadPtrHandle<DirectoryLock>& aDirectoryLock)
+                     already_AddRefed<DirectoryLock> aDirectoryLock)
 {
   NS_ASSERT_OWNINGTHREAD(Context);
 
   MOZ_ASSERT(mInitRunnable);
   mInitRunnable = nullptr;
 
   mQuotaInfo = aQuotaInfo;
 
--- a/dom/cache/Context.h
+++ b/dom/cache/Context.h
@@ -185,17 +185,18 @@ private:
   };
 
   Context(Manager* aManager, nsIThread* aTarget, Action* aInitAction);
   ~Context();
   void Init(Context* aOldContext);
   void Start();
   void DispatchAction(Action* aAction, bool aDoomData = false);
   void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
-                   nsMainThreadPtrHandle<DirectoryLock>& aDirectoryLock);
+                   already_AddRefed<DirectoryLock> aDirectoryLock);
+
 
   already_AddRefed<ThreadsafeHandle>
   CreateThreadsafeHandle();
 
   void
   SetNextContext(Context* aNextContext);
 
   void
@@ -216,17 +217,17 @@ private:
   typedef nsTObserverArray<Activity*> ActivityList;
   ActivityList mActivityList;
 
   // The ThreadsafeHandle may have a strong ref back to us.  This creates
   // a ref-cycle that keeps the Context alive.  The ref-cycle is broken
   // when ThreadsafeHandle::AllowToClose() is called.
   RefPtr<ThreadsafeHandle> mThreadsafeHandle;
 
-  nsMainThreadPtrHandle<DirectoryLock> mDirectoryLock;
+  RefPtr<DirectoryLock> mDirectoryLock;
   RefPtr<Context> mNextContext;
 
 public:
   NS_INLINE_DECL_REFCOUNTING(cache::Context)
 };
 
 } // namespace cache
 } // namespace dom
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -237,65 +237,71 @@ public:
 
     MOZ_ALWAYS_TRUE(sFactory->mManagerList.RemoveElement(aManager));
 
     // clean up the factory singleton if there are no more managers
     MaybeDestroyInstance();
   }
 
   static void
-  StartAbortOnMainThread(const nsACString& aOrigin)
+  Abort(const nsACString& aOrigin)
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    mozilla::ipc::AssertIsOnBackgroundThread();
 
-    // Lock for sBackgroundThread.
-    StaticMutexAutoLock lock(sMutex);
-
-    if (!sBackgroundThread) {
+    if (!sFactory) {
       return;
     }
 
-    // Guaranteed to succeed because we should get abort only before the
-    // background thread is destroyed.
-    nsCOMPtr<nsIRunnable> runnable = new AbortRunnable(aOrigin);
-    nsresult rv = sBackgroundThread->Dispatch(runnable,
-                                              nsIThread::DISPATCH_NORMAL);
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+    MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
+
+    {
+      ManagerList::ForwardIterator iter(sFactory->mManagerList);
+      while (iter.HasMore()) {
+        RefPtr<Manager> manager = iter.GetNext();
+        if (aOrigin.IsVoid() ||
+            manager->mManagerId->QuotaOrigin() == aOrigin) {
+          manager->Abort();
+        }
+      }
+    }
   }
 
   static void
-  StartShutdownAllOnMainThread()
+  ShutdownAll()
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    mozilla::ipc::AssertIsOnBackgroundThread();
 
-    // Lock for sFactoryShutdown and sBackgroundThread.
-    StaticMutexAutoLock lock(sMutex);
-
-    sFactoryShutdown = true;
-
-    if (!sBackgroundThread) {
+    if (!sFactory) {
       return;
     }
 
-    // Guaranteed to succeed because we should be shutdown before the
-    // background thread is destroyed.
-    nsCOMPtr<nsIRunnable> runnable = new ShutdownAllRunnable();
-    nsresult rv = sBackgroundThread->Dispatch(runnable,
-                                              nsIThread::DISPATCH_NORMAL);
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv));
+    MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
+
+    {
+      // Note that we are synchronously calling shutdown code here.  If any
+      // of the shutdown code synchronously decides to delete the Factory
+      // we need to delay that delete until the end of this method.
+      AutoRestore<bool> restore(sFactory->mInSyncShutdown);
+      sFactory->mInSyncShutdown = true;
+
+      ManagerList::ForwardIterator iter(sFactory->mManagerList);
+      while (iter.HasMore()) {
+        RefPtr<Manager> manager = iter.GetNext();
+        manager->Shutdown();
+      }
+    }
+
+    MaybeDestroyInstance();
   }
 
   static bool
-  IsShutdownAllCompleteOnMainThread()
+  IsShutdownAllComplete()
   {
-    MOZ_ASSERT(NS_IsMainThread());
-    StaticMutexAutoLock lock(sMutex);
-    // Infer whether we have shutdown using the sBackgroundThread value.  We
-    // guarantee this is nullptr when sFactory is destroyed.
-    return sFactoryShutdown && !sBackgroundThread;
+    mozilla::ipc::AssertIsOnBackgroundThread();
+    return !sFactory;
   }
 
 private:
   Factory()
     : mInSyncShutdown(false)
   {
     MOZ_COUNT_CTOR(cache::Manager::Factory);
   }
@@ -317,22 +323,16 @@ private:
       // we don't need to lock it here.  Just protect sFactoryShutdown and
       // sBackgroundThread.
       {
         StaticMutexAutoLock lock(sMutex);
 
         if (sFactoryShutdown) {
           return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
         }
-
-        // Cannot use ClearOnShutdown() because we're on the background thread.
-        // This is automatically cleared when Factory::Remove() calls
-        // MaybeDestroyInstance().
-        MOZ_ASSERT(!sBackgroundThread);
-        sBackgroundThread = NS_GetCurrentThread();
       }
 
       // We cannot use ClearOnShutdown() here because we're not on the main
       // thread.  Instead, we delete sFactory in Factory::Remove() after the
       // last manager is removed.  ShutdownObserver ensures this happens
       // before shutdown.
       sFactory = new Factory();
     }
@@ -354,144 +354,31 @@ private:
     // If the factory is is still in use then we cannot delete yet.  This
     // could be due to managers still existing or because we are in the
     // middle of shutting down.  We need to be careful not to delete ourself
     // synchronously during shutdown.
     if (!sFactory->mManagerList.IsEmpty() || sFactory->mInSyncShutdown) {
       return;
     }
 
-    // Be clear about what we are locking.  sFactory is bg thread only, so
-    // we don't need to lock it here.  Just protect sBackgroundThread.
-    {
-      StaticMutexAutoLock lock(sMutex);
-      MOZ_ASSERT(sBackgroundThread);
-      sBackgroundThread = nullptr;
-    }
-
     sFactory = nullptr;
   }
 
-  static void
-  AbortOnBackgroundThread(const nsACString& aOrigin)
-  {
-    mozilla::ipc::AssertIsOnBackgroundThread();
-
-    // The factory was destroyed between when abort started on main thread and
-    // when we could start abort on the worker thread.  Just declare abort
-    // complete.
-    if (!sFactory) {
-#ifdef DEBUG
-      StaticMutexAutoLock lock(sMutex);
-      MOZ_ASSERT(!sBackgroundThread);
-#endif
-      return;
-    }
-
-    MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
-
-    {
-      ManagerList::ForwardIterator iter(sFactory->mManagerList);
-      while (iter.HasMore()) {
-        RefPtr<Manager> manager = iter.GetNext();
-        if (aOrigin.IsVoid() ||
-            manager->mManagerId->QuotaOrigin() == aOrigin) {
-          manager->Abort();
-        }
-      }
-    }
-  }
-
-  static void
-  ShutdownAllOnBackgroundThread()
-  {
-    mozilla::ipc::AssertIsOnBackgroundThread();
-
-    // The factory shutdown between when shutdown started on main thread and
-    // when we could start shutdown on the worker thread.  Just declare
-    // shutdown complete.  The sFactoryShutdown flag prevents the factory
-    // from racing to restart here.
-    if (!sFactory) {
-#ifdef DEBUG
-      StaticMutexAutoLock lock(sMutex);
-      MOZ_ASSERT(!sBackgroundThread);
-#endif
-      return;
-    }
-
-    MOZ_ASSERT(!sFactory->mManagerList.IsEmpty());
-
-    {
-      // Note that we are synchronously calling shutdown code here.  If any
-      // of the shutdown code synchronously decides to delete the Factory
-      // we need to delay that delete until the end of this method.
-      AutoRestore<bool> restore(sFactory->mInSyncShutdown);
-      sFactory->mInSyncShutdown = true;
-
-      ManagerList::ForwardIterator iter(sFactory->mManagerList);
-      while (iter.HasMore()) {
-        RefPtr<Manager> manager = iter.GetNext();
-        manager->Shutdown();
-      }
-    }
-
-    MaybeDestroyInstance();
-  }
-
-  class AbortRunnable final : public nsRunnable
-  {
-  public:
-    explicit AbortRunnable(const nsACString& aOrigin)
-      : mOrigin(aOrigin)
-    { }
-
-    NS_IMETHOD
-    Run() override
-    {
-      mozilla::ipc::AssertIsOnBackgroundThread();
-      AbortOnBackgroundThread(mOrigin);
-      return NS_OK;
-    }
-  private:
-    ~AbortRunnable() { }
-
-    const nsCString mOrigin;
-  };
-
-  class ShutdownAllRunnable final : public nsRunnable
-  {
-  public:
-    NS_IMETHOD
-    Run() override
-    {
-      mozilla::ipc::AssertIsOnBackgroundThread();
-      ShutdownAllOnBackgroundThread();
-      return NS_OK;
-    }
-  private:
-    ~ShutdownAllRunnable() { }
-  };
-
   // Singleton created on demand and deleted when last Manager is cleared
   // in Remove().
   // PBackground thread only.
   static StaticAutoPtr<Factory> sFactory;
 
-  // protects following static attributes
+  // protects following static attribute
   static StaticMutex sMutex;
 
   // Indicate if shutdown has occurred to block re-creation of sFactory.
   // Must hold sMutex to access.
   static bool sFactoryShutdown;
 
-  // Background thread owning all Manager objects.  Only set while sFactory is
-  // set.
-  // Must hold sMutex to access.
-  static StaticRefPtr<nsIThread> sBackgroundThread;
-
   // Weak references as we don't want to keep Manager objects alive forever.
   // When a Manager is destroyed it calls Factory::Remove() to clear itself.
   // PBackground thread only.
   typedef nsTObserverArray<Manager*> ManagerList;
   ManagerList mManagerList;
 
   // This flag is set when we are looping through the list and calling
   // Shutdown() on each Manager.  We need to be careful not to synchronously
@@ -503,19 +390,16 @@ private:
 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
 
 // static
 StaticMutex Manager::Factory::sMutex;
 
 // static
 bool Manager::Factory::sFactoryShutdown = false;
 
-// static
-StaticRefPtr<nsIThread> Manager::Factory::sBackgroundThread;
-
 // ----------------------------------------------------------------------------
 
 // Abstract class to help implement the various Actions.  The vast majority
 // of Actions are synchronous and need to report back to a Listener on the
 // Manager.
 class Manager::BaseAction : public SyncDBAction
 {
 protected:
@@ -1526,37 +1410,37 @@ already_AddRefed<Manager>
 Manager::Get(ManagerId* aManagerId)
 {
   mozilla::ipc::AssertIsOnBackgroundThread();
   return Factory::Get(aManagerId);
 }
 
 // static
 void
-Manager::ShutdownAllOnMainThread()
+Manager::ShutdownAll()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  mozilla::ipc::AssertIsOnBackgroundThread();
 
-  Factory::StartShutdownAllOnMainThread();
+  Factory::ShutdownAll();
 
-  while (!Factory::IsShutdownAllCompleteOnMainThread()) {
+  while (!Factory::IsShutdownAllComplete()) {
     if (!NS_ProcessNextEvent()) {
       NS_WARNING("Something bad happened!");
       break;
     }
   }
 }
 
 // static
 void
-Manager::AbortOnMainThread(const nsACString& aOrigin)
+Manager::Abort(const nsACString& aOrigin)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  mozilla::ipc::AssertIsOnBackgroundThread();
 
-  Factory::StartAbortOnMainThread(aOrigin);
+  Factory::Abort(aOrigin);
 }
 
 void
 Manager::RemoveListener(Listener* aListener)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   // There may not be a listener here in the case where an actor is killed
   // before it can perform any actual async requests on Manager.
--- a/dom/cache/Manager.h
+++ b/dom/cache/Manager.h
@@ -126,21 +126,21 @@ public:
   {
     Open,
     Closing
   };
 
   static nsresult GetOrCreate(ManagerId* aManagerId, Manager** aManagerOut);
   static already_AddRefed<Manager> Get(ManagerId* aManagerId);
 
-  // Synchronously shutdown from main thread.  This spins the event loop.
-  static void ShutdownAllOnMainThread();
+  // Synchronously shutdown.  This spins the event loop.
+  static void ShutdownAll();
 
   // Cancel actions for given origin or all actions if passed string is null.
-  static void AbortOnMainThread(const nsACString& aOrigin);
+  static void Abort(const nsACString& aOrigin);
 
   // Must be called by Listener objects before they are destroyed.
   void RemoveListener(Listener* aListener);
 
   // Must be called by Context objects before they are destroyed.
   void RemoveContext(Context* aContext);
 
   // Marks the Manager "invalid".  Once the Context completes no new operations
--- a/dom/cache/QuotaClient.cpp
+++ b/dom/cache/QuotaClient.cpp
@@ -5,29 +5,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/QuotaClient.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/cache/Manager.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
+#include "mozilla/ipc/BackgroundParent.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
 #include "nsThreadUtils.h"
 
 namespace {
 
 using mozilla::DebugOnly;
 using mozilla::dom::ContentParentId;
 using mozilla::dom::cache::Manager;
 using mozilla::dom::quota::Client;
 using mozilla::dom::quota::PersistenceType;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::UsageInfo;
+using mozilla::ipc::AssertIsOnBackgroundThread;
 
 static nsresult
 GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo)
 {
   nsCOMPtr<nsISimpleEnumerator> entries;
   nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
@@ -170,19 +172,19 @@ public:
   {
     // Nothing to do here as the Context handles cleaning everything up
     // automatically.
   }
 
   virtual void
   AbortOperations(const nsACString& aOrigin) override
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
 
-    Manager::AbortOnMainThread(aOrigin);
+    Manager::Abort(aOrigin);
   }
 
   virtual void
   AbortOperationsForProcess(ContentParentId aContentParentId) override
   {
     // The Cache and Context can be shared by multiple client processes.  They
     // are not exclusively owned by a single process.
     //
@@ -190,44 +192,50 @@ public:
     // when a particular process goes away.  We definitely don't want this
     // since we are shared.  Also, the Cache actor code already properly
     // handles asynchronous actor destruction when the child process dies.
     //
     // Therefore, do nothing here.
   }
 
   virtual void
-  PerformIdleMaintenance() override
+  StartIdleMaintenance() override
+  { }
+
+  virtual void
+  StopIdleMaintenance() override
   { }
 
   virtual void
   ShutdownWorkThreads() override
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
 
     // spins the event loop and synchronously shuts down all Managers
-    Manager::ShutdownAllOnMainThread();
+    Manager::ShutdownAll();
   }
 
 private:
   ~CacheQuotaClient()
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
   }
 
   NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, override)
 };
 
 } // namespace
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 already_AddRefed<quota::Client> CreateQuotaClient()
 {
+  AssertIsOnBackgroundThread();
+
   RefPtr<CacheQuotaClient> ref = new CacheQuotaClient();
   return ref.forget();
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -157,19 +157,19 @@ TypeUtils::ToCacheRequest(CacheRequest& 
   bool schemeValid;
   ProcessURL(url, &schemeValid, &aOut.urlWithoutQuery(), &aOut.urlQuery(), aRv);
   if (aRv.Failed()) {
     return;
   }
 
   if (!schemeValid) {
     if (aSchemeAction == TypeErrorOnInvalidScheme) {
-      NS_NAMED_LITERAL_STRING(label, "Request");
       NS_ConvertUTF8toUTF16 urlUTF16(url);
-      aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(&label, &urlUTF16);
+      aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
+                                                 urlUTF16);
       return;
     }
   }
 
   aIn->GetReferrer(aOut.referrer());
 
   RefPtr<InternalHeaders> headers = aIn->Headers();
   MOZ_ASSERT(headers);
--- a/dom/cache/test/mochitest/driver.js
+++ b/dom/cache/test/mochitest/driver.js
@@ -30,17 +30,21 @@ function runTests(testFile, order) {
         resolve();
       });
     });
   }
 
   // adapted from dom/indexedDB/test/helpers.js
   function clearStorage() {
     return new Promise(function(resolve, reject) {
-      SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
+      var qms = SpecialPowers.Services.qms;
+      var principal = SpecialPowers.wrap(document).nodePrincipal;
+      var request = qms.clearStoragesForPrincipal(principal);
+      var cb = SpecialPowers.wrapCallback(resolve);
+      request.callback = cb;
     });
   }
 
   function loadScript(script) {
     return new Promise(function(resolve, reject) {
       var s = document.createElement("script");
       s.src = script;
       s.onerror = reject;
--- a/dom/cache/test/mochitest/test_cache_orphaned_body.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html
@@ -19,29 +19,41 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function clearStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var principal = SpecialPowers.wrap(document).nodePrincipal;
+    var request = qms.clearStoragesForPrincipal(principal);
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 function storageUsage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var principal = SpecialPowers.wrap(document).nodePrincipal;
+    var cb = SpecialPowers.wrapCallback(function(request) {
+      resolve(request.usage, request.fileUsage);
+    });
+    qms.getUsageForPrincipal(principal, cb);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var request = qms.reset();
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
     SpecialPowers.exactGC(window, resolve);
   });
 }
--- a/dom/cache/test/mochitest/test_cache_orphaned_cache.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html
@@ -19,29 +19,41 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function clearStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var principal = SpecialPowers.wrap(document).nodePrincipal;
+    var request = qms.clearStoragesForPrincipal(principal);
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 function storageUsage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var principal = SpecialPowers.wrap(document).nodePrincipal;
+    var cb = SpecialPowers.wrapCallback(function(request) {
+      resolve(request.usage, request.fileUsage);
+    });
+    qms.getUsageForPrincipal(principal, cb);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var request = qms.reset();
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
     SpecialPowers.exactGC(window, resolve);
   });
 }
--- a/dom/cache/test/mochitest/test_cache_restart.html
+++ b/dom/cache/test/mochitest/test_cache_restart.html
@@ -18,17 +18,20 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var request = qms.reset();
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/cache/test/mochitest/test_cache_shrink.html
+++ b/dom/cache/test/mochitest/test_cache_shrink.html
@@ -19,29 +19,41 @@ function setupTestIframe() {
       resolve();
     };
     document.body.appendChild(iframe);
   });
 }
 
 function clearStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var principal = SpecialPowers.wrap(document).nodePrincipal;
+    var request = qms.clearStoragesForPrincipal(principal);
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 function storageUsage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var principal = SpecialPowers.wrap(document).nodePrincipal;
+    var cb = SpecialPowers.wrapCallback(function(request) {
+      resolve(request.usage, request.fileUsage);
+    });
+    qms.getUsageForPrincipal(principal, cb);
   });
 }
 
 function resetStorage() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.resetStorageForDoc(SpecialPowers.wrap(document), resolve);
+    var qms = SpecialPowers.Services.qms;
+    var request = qms.reset();
+    var cb = SpecialPowers.wrapCallback(resolve);
+    request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
     SpecialPowers.exactGC(window, resolve);
   });
 }
--- a/dom/cache/test/xpcshell/make_profile.js
+++ b/dom/cache/test/xpcshell/make_profile.js
@@ -87,32 +87,21 @@ function resetQuotaManager() {
 
     var prefService = Cc['@mozilla.org/preferences-service;1']
                       .getService(Ci.nsIPrefService);
 
     // enable quota manager testing mode
     var pref = 'dom.quotaManager.testing';
     prefService.getBranch(null).setBoolPref(pref, true);
 
-    qm.reset();
+    var request = qm.reset();
+    request.callback = resolve;
 
     // disable quota manager testing mode
     //prefService.getBranch(null).setBoolPref(pref, false);
-
-    var uri = Cc['@mozilla.org/network/io-service;1']
-              .getService(Ci.nsIIOService)
-              .newURI('http://example.com', null, null);
-    var principal = Cc['@mozilla.org/scriptsecuritymanager;1']
-                    .getService(Ci.nsIScriptSecurityManager)
-                    .getSystemPrincipal();
-
-    // use getUsageForPrincipal() to get a callback when the reset() is done
-    qm.getUsageForPrincipal(principal, function(principal, usage, fileUsage) {
-      resolve(usage);
-    });
   });
 }
 
 function run_test() {
   do_test_pending();
   do_get_profile();
 
   var directoryService = Cc['@mozilla.org/file/directory_service;1']
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -1271,18 +1271,16 @@ nsDOMCameraControl::OnGetCameraComplete(
 // Camera Control event handlers--must only be called from the Main Thread!
 void
 nsDOMCameraControl::OnHardwareStateChange(CameraControlListener::HardwareState aState,
                                           nsresult aReason)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   MOZ_ASSERT(NS_IsMainThread());
 
-  ErrorResult ignored;
-
   switch (aState) {
     case CameraControlListener::kHardwareOpen:
       DOM_CAMERA_LOGI("DOM OnHardwareStateChange: open\n");
       MOZ_ASSERT(aReason == NS_OK);
       if (!mSetInitialConfig) {
         // The hardware is open, so we can return a camera to JS, even if
         // the preview hasn't started yet.
         OnGetCameraComplete();
@@ -1400,17 +1398,16 @@ nsDOMCameraControl::OnPoster(BlobImpl* a
 void
 nsDOMCameraControl::OnRecorderStateChange(CameraControlListener::RecorderState aState,
                                           int32_t aArg, int32_t aTrackNum)
 {
   // For now, we do nothing with 'aStatus' and 'aTrackNum'.
   DOM_CAMERA_LOGT("%s:%d : this=%p, state=%u\n", __func__, __LINE__, this, aState);
   MOZ_ASSERT(NS_IsMainThread());
 
-  ErrorResult ignored;
   nsString state;
 
   switch (aState) {
     case CameraControlListener::kRecorderStarted:
       {
         RefPtr<Promise> promise = mStartRecordingPromise.forget();
         if (promise) {
           promise->MaybeResolve(JS::UndefinedHandleValue);
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3962,16 +3962,17 @@ gfxFontGroup *CanvasRenderingContext2D::
   // use lazy initilization for the font group since it's rather expensive
   if (!CurrentState().fontGroup) {
     ErrorResult err;
     NS_NAMED_LITERAL_STRING(kDefaultFontStyle, "10px sans-serif");
     static float kDefaultFontSize = 10.0;
     nsCOMPtr<nsIPresShell> presShell = GetPresShell();
     bool fontUpdated = SetFontInternal(kDefaultFontStyle, err);
     if (err.Failed() || !fontUpdated) {
+      err.SuppressException();
       gfxFontStyle style;
       style.size = kDefaultFontSize;
       gfxTextPerfMetrics* tp = nullptr;
       if (presShell && !presShell->IsDestroying()) {
         tp = presShell->GetPresContext()->GetTextPerfMetrics();
       }
       int32_t perDevPixel, perCSSPixel;
       GetAppUnitsValues(&perDevPixel, &perCSSPixel);
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -1,10 +1,10 @@
 # WebGL Reftests!
-default-preferences pref(webgl.force-enabled,true)
+default-preferences pref(webgl.force-enabled,true) pref(media.useAudioChannelAPI,true) pref(dom.audiochannel.mutedByDefault,false)
 
 # Check that disabling works:
                            == webgl-disable-test.html?nogl wrapper.html?green.png
 pref(webgl.disabled,true)  == webgl-disable-test.html      wrapper.html?green.png
 
 # Basic WebGL tests:
 # Do we get pixels to the screen at all?
 # Neither of these should ever break.
deleted file mode 100644
--- a/dom/devicestorage/ipc/ipc.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-"runtests":{
-},
-"excludetests":{
-    "dom/devicestorage/test/test_dirs.html":"excluded",
-    "dom/devicestorage/test/test_storageAreaListener.html":"excluded"
-   }
-}
deleted file mode 100644
--- a/dom/devicestorage/ipc/mochitest.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[DEFAULT]
-skip-if = toolkit == 'android' || e10s #bug 781789 & bug 782275
-support-files = 
-  ../test/devicestorage_common.js
-  ipc.json
-
-[test_ipc.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' # b2g(nested ipc not working) b2g-debug(nested ipc not working) b2g-desktop(nested ipc not working)
deleted file mode 100644
--- a/dom/devicestorage/ipc/test_ipc.html
+++ /dev/null
@@ -1,173 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test for OOP DeviceStorage</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-  <body>
-
-  <script type="application/javascript;version=1.7">
-    "use strict";
-
-    SimpleTest.waitForExplicitFinish();
-    SimpleTest.requestFlakyTimeout("untriaged");
-
-    // The crash observer registration functions are stubbed out here to
-    // prevent the iframe test runner from breaking later crash-related tests.
-    function iframeScriptFirst() {
-      SpecialPowers.prototype.registerProcessCrashObservers = () => {};
-      SpecialPowers.prototype.unregisterProcessCrashObservers = () => {};
-
-      content.wrappedJSObject.RunSet.reloadAndRunAll({
-          preventDefault: function() { },
-          __exposedProps__: { preventDefault: 'r' }
-      });
-    }
-
-    function iframeScriptSecond() {
-      let TestRunner = content.wrappedJSObject.TestRunner;
-
-      let oldComplete = TestRunner.onComplete;
-
-      TestRunner.onComplete = function() {
-        TestRunner.onComplete = oldComplete;
-
-        sendAsyncMessage("test:DeviceStorage:ipcTestComplete", {
-          result: JSON.stringify(TestRunner._failedTests)
-        });
-
-        if (oldComplete) {
-          oldComplete();
-        }
-      };
-
-      TestRunner.structuredLogger._dumpMessage = function(msg) {
-        sendAsyncMessage("test:DeviceStorage:ipcTestMessage", { msg: msg });
-      }
-    }
-
-    let VALID_ACTIONS = ['suite_start', 'suite_end', 'test_start', 'test_end', 'test_status', 'process_output', 'log'];
-    function validStructuredMessage(message) {
-      return message.action !== undefined && VALID_ACTIONS.indexOf(message.action) >= 0;
-    }
-    function onTestMessage(data) {
-      let message = SpecialPowers.wrap(data).data.msg;
-
-      if (validStructuredMessage(message)) {
-        switch (message.action) {
-          case "test_status":
-          case "test_end":
-            let test_tokens = message.test.split("/");
-            let test_name = test_tokens[test_tokens.length - 1];
-            if (message.subtest) {
-                test_name += " | " + message.subtest;
-            }
-            ok(message.expected === undefined, test_name, message.message);
-            break;
-          case "log":
-            info(message.message);
-            break;
-          default:
-            // nothing
-        }
-      }
-    }
-
-    function onTestComplete() {
-      let comp = SpecialPowers.wrap(SpecialPowers.Components);
-      let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-      let spObserver = comp.classes["@mozilla.org/special-powers-observer;1"]
-                            .getService(comp.interfaces.nsIMessageListener);
-
-      mm.removeMessageListener("SPPrefService", spObserver);
-      mm.removeMessageListener("SPProcessCrashService", spObserver);
-      mm.removeMessageListener("SPPingService", spObserver);
-      mm.removeMessageListener("SpecialPowers.Quit", spObserver);
-      mm.removeMessageListener("SPPermissionManager", spObserver);
-
-      mm.removeMessageListener("test:DeviceStorage:ipcTestMessage", onTestMessage);
-      mm.removeMessageListener("test:DeviceStorage:ipcTestComplete", onTestComplete);
-
-      SimpleTest.executeSoon(function () { SimpleTest.finish(); });
-    }
-
-    function runTests() {
-      let iframe = document.createElement("iframe");
-      SpecialPowers.wrap(iframe).mozbrowser = true;
-      iframe.id = "iframe";
-      iframe.style.width = "100%";
-      iframe.style.height = "1000px";
-
-      function iframeLoadSecond() {
-        ok(true, "Got second iframe load event.");
-        iframe.removeEventListener("mozbrowserloadend", iframeLoadSecond);
-        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-        mm.loadFrameScript("data:,(" + iframeScriptSecond.toString() + ")();",
-                           false);
-      }
-
-      function iframeLoadFirst() {
-        ok(true, "Got first iframe load event.");
-        iframe.removeEventListener("mozbrowserloadend", iframeLoadFirst);
-        iframe.addEventListener("mozbrowserloadend", iframeLoadSecond);
-
-        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
-
-        let comp = SpecialPowers.wrap(SpecialPowers.Components);
-
-        let spObserver =
-          comp.classes["@mozilla.org/special-powers-observer;1"]
-              .getService(comp.interfaces.nsIMessageListener);
-
-        mm.addMessageListener("SPPrefService", spObserver);
-        mm.addMessageListener("SPProcessCrashService", spObserver);
-        mm.addMessageListener("SPPingService", spObserver);
-        mm.addMessageListener("SpecialPowers.Quit", spObserver);
-        mm.addMessageListener("SPPermissionManager", spObserver);
-
-        mm.addMessageListener("test:DeviceStorage:ipcTestMessage", onTestMessage);
-        mm.addMessageListener("test:DeviceStorage:ipcTestComplete", onTestComplete);
-
-        let specialPowersBase = "chrome://specialpowers/content/";
-        mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
-        mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
-        mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
-
-        mm.loadFrameScript("data:,(" + iframeScriptFirst.toString() + ")();", false);
-      }
-
-      iframe.addEventListener("mozbrowserloadend", iframeLoadFirst);
-
-      // Strip this filename and one directory level and then add "/test".
-      let href =  window.location.href;
-      href = href.substring(0, href.lastIndexOf('/'));
-      href = href.substring(0, href.lastIndexOf('/'));
-      let manifest = "tests/dom/devicestorage/ipc/ipc.json";
-      iframe.src = href + "/test?consoleLevel=INFO&testManifest=" + manifest;
-      document.body.appendChild(iframe);
-    }
-
-    addEventListener("load", function() {
-
-      SpecialPowers.addPermission("browser", true, document);
-      SpecialPowers.pushPrefEnv({
-        "set": [
-
-          ["device.storage.enabled", true],
-          ["device.storage.testing", true],
-          ["device.storage.prompt.testing", true],
-
-          // TODO: remove this as part of bug 820712
-          ["network.disable.ipc.security", true],
-
-          ["dom.ipc.browser_frames.oop_by_default", true],
-          ["dom.mozBrowserFramesEnabled", true],
-          ["browser.pagethumbnails.capturing_disabled", true]
-        ]
-      }, runTests);
-    });
-
-  </script>
-</body>
-</html>
--- a/dom/devicestorage/moz.build
+++ b/dom/devicestorage/moz.build
@@ -36,15 +36,14 @@ include('/ipc/chromium/chromium-config.m
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/ipc',
 ]
 
 MOCHITEST_MANIFESTS += [
-    'ipc/mochitest.ini',
     'test/mochitest.ini',
 ]
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/dom/devicestorage/test/devicestorage_common.js
+++ b/dom/devicestorage/test/devicestorage_common.js
@@ -1,57 +1,39 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 var oldVal = false;
-  
+
 Object.defineProperty(Array.prototype, "remove", {
   enumerable: false,
   configurable: false,
   writable: false,
   value: function(from, to) {
     // Array Remove - By John Resig (MIT Licensed)
     var rest = this.slice((to || from) + 1 || this.length);
     this.length = from < 0 ? this.length + from : from;
     return this.push.apply(this, rest);
   }
 });
 
-function devicestorage_setup() {
-
-  // ensure that the directory we are writing into is empty
-  try {
-    const Cc = SpecialPowers.Cc;
-    const Ci = SpecialPowers.Ci;
-    var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
-    var f = directoryService.get("TmpD", Ci.nsIFile);
-    f.appendRelativePath("device-storage-testing");
-    f.remove(true);
-  } catch(e) {}
-
+function devicestorage_setup(callback) {
   SimpleTest.waitForExplicitFinish();
-  if (SpecialPowers.isMainProcess()) {
-    try {
-      oldVal = SpecialPowers.getBoolPref("device.storage.enabled");
-    } catch(e) {}
-    SpecialPowers.setBoolPref("device.storage.enabled", true);
-    SpecialPowers.setBoolPref("device.storage.testing", true);
-    SpecialPowers.setBoolPref("device.storage.prompt.testing", true);
-  }
-}
+
+  let script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('remove_testing_directory.js'));
 
-function devicestorage_cleanup() {
-  if (SpecialPowers.isMainProcess()) {
-    SpecialPowers.setBoolPref("device.storage.enabled", oldVal);
-    SpecialPowers.setBoolPref("device.storage.testing", false);
-    SpecialPowers.setBoolPref("device.storage.prompt.testing", false);
-  }
-  SimpleTest.finish();
+  script.addMessageListener('directory-removed', function listener () {
+    script.removeMessageListener('directory-removed', listener);
+    var prefs = [["device.storage.enabled", true],
+                 ["device.storage.testing", true],
+                 ["device.storage.prompt.testing", true]];
+    SpecialPowers.pushPrefEnv({"set": prefs}, callback);
+  });
 }
 
 function getRandomBuffer() {
   var size = 1024;
   var buffer = new ArrayBuffer(size);
   var view = new Uint8Array(buffer);
   for (var i = 0; i < size; i++) {
     view[i] = parseInt(Math.random() * 255);
@@ -70,17 +52,17 @@ function randomFilename(l) {
     var r = Math.floor(set.length * Math.random());
     result += set.substring(r, r + 1);
   }
   return result;
 }
 
 function reportErrorAndQuit(e) {
   ok(false, "handleError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function createTestFiles(storage, paths) {
   function createTestFile(path) {
     return new Promise(function(resolve, reject) {
       function addNamed() {
         var req = storage.addNamed(createRandomBlob("image/png"), path);
 
--- a/dom/devicestorage/test/mochitest.ini
+++ b/dom/devicestorage/test/mochitest.ini
@@ -1,19 +1,21 @@
 [DEFAULT]
-skip-if = e10s || (toolkit == 'android' && processor == 'x86') # e10s: bug 1222522. Android: bug 781789 & bug 782275
+skip-if = (toolkit == 'android' && processor == 'x86') # Android: bug 781789 & bug 782275
 support-files = devicestorage_common.js
+                remove_testing_directory.js
 
 [test_823965.html]
 # [test_add.html]
 # man, our mime database sucks hard.  followup bug # 788273
 [test_addCorrectType.html]
 [test_available.html]
 [test_basic.html]
 [test_dirs.html]
+skip-if = e10s # Bug 1063569.
 # [test_diskSpace.html]
 # Possible race between the time we write a file, and the
 # time it takes to be reflected by statfs().  Bug # 791287
 [test_dotdot.html]
 [test_enumerate.html]
 [test_enumerateMultipleContinue.html]
 [test_enumerateOptions.html]
 [test_freeSpace.html]
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/remove_testing_directory.js
@@ -0,0 +1,11 @@
+// ensure that the directory we are writing into is empty
+try {
+  var Cc = Components.classes;
+  var Ci = Components.interfaces;
+  var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+  var f = directoryService.get("TmpD", Ci.nsIFile);
+  f.appendRelativePath("device-storage-testing");
+  f.remove(true);
+} catch(e) {}
+
+sendAsyncMessage('directory-removed', {});
--- a/dom/devicestorage/test/test_823965.html
+++ b/dom/devicestorage/test/test_823965.html
@@ -17,17 +17,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=823965">Mozilla Bug 823965</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 var gFileName = "devicestorage/" + randomFilename(12) + "/hi.png";
 var gData = "My name is Doug Turner (?!?).  My IRC nick is DougT.  I like Maple cookies."
 var gDataBlob = new Blob([gData], {type: 'image/png'});
 
 function getSuccess(e) {
   var storage = navigator.getDeviceStorage("pictures");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
@@ -38,30 +38,30 @@ function getSuccess(e) {
   ok(e.target.result.lastModifiedDate, "File should have a last modified date");
 
   var mreq = storage.enumerate();
   mreq.onsuccess = function() {
     var storage2 = navigator.getDeviceStorage('music');
     var dreq = storage2.delete(mreq.result.name);
     dreq.onerror = function () {
       ok(true, "The bug has been fixed");
-      devicestorage_cleanup();
+      SimpleTest.finish();
     };
     dreq.onsuccess = function () {
       ok(false, "The bug has been fixed");
-      devicestorage_cleanup();
+      SimpleTest.finish();
     };
   };
 
   mreq.onerror = getError;
 }
 
 function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
 
   var filename = e.target.result;
   if (filename[0] == "/") {
     // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
@@ -80,27 +80,28 @@ function addSuccess(e) {
   request.onsuccess = getSuccess;
   request.onerror = getError;
 
   ok(true, "addSuccess was called");
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(storage, "Should have gotten a storage");
 
 request = storage.addNamed(gDataBlob, gFileName);
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_add.html
+++ b/dom/devicestorage/test/test_add.html
@@ -13,57 +13,58 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=786922">Mozilla Bug 786922</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function add(storage, mime) {
   dump("adding: " + mime + "\n");
   return navigator.getDeviceStorage(storage).add(createRandomBlob(mime));
 }
 
 var tests = [
   function () { return add("pictures", "image/png")},
   function () { return add("videos",   "video/webm")},
   function () { return add("music",    "audio/wav")},
   function () { return add("sdcard",   "maple/cookies")},
 ];
 
 function fail(e) {
   ok(false, "onerror was called");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function next(e) {
 
   if (e != undefined)
     ok(true, "addError was called");
-  
+
   var f = tests.pop();
 
   if (f == undefined) {
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
 
   request = f();
   request.onsuccess = next;
   request.onerror = fail;
 }
 
 next();
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_addCorrectType.html
+++ b/dom/devicestorage/test/test_addCorrectType.html
@@ -13,23 +13,23 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=786922">Mozilla Bug 786922</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function addNamed(storage, mime, fileExtension) {
   dump("adding: " + mime + " " + fileExtension + "\n");
   return navigator.getDeviceStorage(storage).addNamed(createRandomBlob(mime), randomFilename(40) + "." + fileExtension);
 }
 
 // These tests must all fail
 var tests = [
@@ -40,35 +40,36 @@ var tests = [
   function () { return addNamed("videos",   "kyle/smash", ".ogv")},
   function () { return addNamed("videos",   "video/ogv",  ".poo")},
 ];
 
 function fail(e) {
   ok(false, "addSuccess was called");
   ok(e.target.error.name == "TypeMismatchError", "Error must be TypeMismatchError");
 
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function next(e) {
 
   if (e != undefined)
     ok(true, "addError was called");
-  
+
   var f = tests.pop();
 
   if (f == undefined) {
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
 
   request = f();
   request.onsuccess = fail;
   request.onerror = next;
 }
 
 next();
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_available.html
+++ b/dom/devicestorage/test/test_available.html
@@ -17,32 +17,34 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=834595">Mozilla Bug 834595</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function availableSuccess(e) {
   isnot(e.target.result, null, "result should not be null");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function availableError(e) {
   ok(false, "availableError was called");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 var storage = navigator.getDeviceStorage("pictures");
 
 request = storage.available();
 ok(request, "Should have a non-null request");
 
 request.onsuccess = availableSuccess;
 request.onerror = availableError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/devicestorage/test/test_basic.html
+++ b/dom/devicestorage/test/test_basic.html
@@ -12,53 +12,53 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function() {
 
 var gFileName = "devicestorage/" + randomFilename(12) + "/hi.png";
 var gData = "My name is Doug Turner.  My IRC nick is DougT.  I like Maple cookies."
 var gDataBlob = new Blob([gData], {type: 'image/png'});
 var gFileReader = new FileReader();
 
 function getAfterDeleteSuccess(e) {
   ok(false, "file was deleted not successfully");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function getAfterDeleteError(e) {
   ok(true, "file was deleted successfully");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function deleteSuccess(e) {
 
   ok(e.target.result == gFileName, "File name should match");
   dump(e.target.result + "\n")
 
   var storage = navigator.getDeviceStorage("pictures");
   request = storage.get(e.target.result);
   request.onsuccess = getAfterDeleteSuccess;
   request.onerror = getAfterDeleteError;
 
 }
 
 function deleteError(e) {
   ok(false, "deleteError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function getSuccess(e) {
   var storage = navigator.getDeviceStorage("pictures");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   ok(e.target.result.name == gFileName, "File name should match");
   ok(e.target.result.size > 0, "File size be greater than zero");
@@ -83,17 +83,17 @@ function readerCallback(e) {
 
   is(ab.byteLength, gData.length, "wrong arraybuffer byteLength");
   var u8v = new Uint8Array(ab);
   is(String.fromCharCode.apply(String, u8v), gData, "wrong values");
 }
 
 function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
 
   var filename = e.target.result;
   if (filename[0] == "/") {
     // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
@@ -114,27 +114,28 @@ function addSuccess(e) {
   request.onsuccess = getSuccess;
   request.onerror = getError;
 
   ok(true, "addSuccess was called");
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(storage, "Should have gotten a storage");
 
 request = storage.addNamed(gDataBlob, gFileName);
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_diskSpace.html
+++ b/dom/devicestorage/test/test_diskSpace.html
@@ -12,39 +12,38 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
-
+devicestorage_setup(function () {
 
 var freeBytes = -1;
 var stats = 0;
 
 function stat(s, file_list_length) {
   if (freeBytes == -1) {
     freeBytes = s.target.result.freeBytes;
   }
 
   ok(freeBytes == s.target.result.freeBytes, "Free bytes should be the same");
   ok(file_list_length * 1024 == s.target.result.totalBytes, "space taken up by files should match")
 
   stats = stats + 1;
 
   if (stats == 2) {
-    devicestorage_cleanup();
+    SimpleTest.finish();
   }
 }
 
 function addSuccess(e) {
   added = added - 1;
 
   if (added == 0) {
     request = pictures.stat();
@@ -55,17 +54,17 @@ function addSuccess(e) {
 
     request = music.stat();
     request.onsuccess = function(s) {stat(s, music_files.length)};
   }
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(true, "hi");
 
 var pictures = navigator.getDeviceStorage("pictures");
 var picture_files = [ "a.png", "b.png", "c.png", "d.png", "e.png" ];
 
 var videos = navigator.getDeviceStorage("videos");
@@ -89,13 +88,14 @@ for (var i=0; i < video_files.length; i+
 }
 
 for (var i=0; i < music_files.length; i++) {
  request = music.addNamed(createRandomBlob('audio/mp3'), music_files[i]);
  request.onsuccess = addSuccess;
  request.onerror = addError;
 }
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_dotdot.html
+++ b/dom/devicestorage/test/test_dotdot.html
@@ -12,22 +12,22 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function testingStorage() {
   return navigator.getDeviceStorage("pictures");
 }
 
 var tests = [
   function () { return testingStorage().addNamed(createRandomBlob('image/png'), gFileName); },
   function () { return testingStorage().delete(gFileName); },
@@ -35,39 +35,38 @@ var tests = [
   function () { var r = testingStorage().enumerate("../"); return r; }
 ];
 
 var gFileName = "../owned.png";
 
 function fail(e) {
   ok(false, "addSuccess was called");
   dump(request);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function next(e) {
 
   if (e != undefined) {
-    ok(true, "addError was called");  
+    ok(true, "addError was called");
     ok(e.target.error.name == "SecurityError", "Error must be SecurityError");
   }
 
   var f = tests.pop();
 
   if (f == undefined) {
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
 
   request = f();
   request.onsuccess = fail;
   request.onerror = next;
 }
 
 next();
 
-
+});
 
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_enumerate.html
+++ b/dom/devicestorage/test/test_enumerate.html
@@ -12,32 +12,32 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function enumerateSuccess(e) {
 
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     dump("We still have length = " + files.length + "\n");
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
-  
+
   var filename = e.target.result.name;
   if (filename[0] == "/") {
     // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
@@ -56,32 +56,32 @@ function enumerateSuccess(e) {
   var cleanup = storage.delete(e.target.result.name);
   cleanup.onsuccess = function(e) {}  // todo - can i remove this?
 
   e.target.continue();
 }
 
 function handleError(e) {
   ok(false, "handleError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
   addedSoFar = addedSoFar + 1;
   if (addedSoFar == files.length) {
 
     var cursor = storage.enumerate(prefix);
     cursor.onsuccess = enumerateSuccess;
     cursor.onerror = handleError;
   }
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 var prefix = "devicestorage/" + randomFilename(12) + ".png"
 
 var files = [ "a.PNG", "b.pnG", "c.png", "d/a.png", "d/b.png", "d/c.png", "d/d.png", "The/quick/brown/fox/jumps/over/the/lazy/dog.png"]
 var addedSoFar = 0;
@@ -91,13 +91,14 @@ for (var i=0; i<files.length; i++) {
 
  request = storage.addNamed(createRandomBlob('image/png'), prefix + '/' + files[i]);
 
  ok(request, "Should have a non-null request");
  request.onsuccess = addSuccess;
  request.onerror = addError;
 }
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_enumerateMultipleContinue.html
+++ b/dom/devicestorage/test/test_enumerateMultipleContinue.html
@@ -12,39 +12,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function enumerateSuccess(e) {
 }
 
 function enumerateFailure(e) {
 }
 
 var cursor = navigator.getDeviceStorage("pictures").enumerate();
 cursor.onsuccess = enumerateSuccess;
 cursor.onerror = enumerateFailure;
 
 try {
  cursor.continue();
 }
 catch (e) {
   ok(true, "Calling continue before enumerateSuccess fires should throw");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
+});
 
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_enumerateNoParam.html
+++ b/dom/devicestorage/test/test_enumerateNoParam.html
@@ -30,17 +30,17 @@ Array.prototype.remove = function(from, 
 };
 
 devicestorage_setup();
 
 function enumerateSuccess(e) {
 
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
   
   var filename = e.target.result.name;
   if (filename[0] == "/") {
     // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
@@ -62,32 +62,32 @@ function enumerateSuccess(e) {
   var cleanup = storage.delete(e.target.result.name);
   cleanup.onsuccess = function(e) {}  // todo - can i remove this?
 
   e.target.continue();
 }
 
 function handleError(e) {
   ok(false, "handleError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
   addedSoFar = addedSoFar + 1;
   if (addedSoFar == files.length) {
 
     var cursor = storage.enumerate();
     cursor.onsuccess = enumerateSuccess;
     cursor.onerror = handleError;
   }
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 var prefix = "devicestorage/" + randomFilename(12)
 
 var files = [ "a.png", "b.png", "c.png" ]
 var addedSoFar = 0;
--- a/dom/devicestorage/test/test_enumerateOptions.html
+++ b/dom/devicestorage/test/test_enumerateOptions.html
@@ -13,22 +13,22 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup()
+devicestorage_setup(function () {
 
 storage = navigator.getDeviceStorage("pictures");
 
 
 throws = false;
 try {
 var cursor = storage.enumerate();
 } catch(e) {throws = true}
@@ -65,17 +65,16 @@ var cursor = storage.enumerate({}, "stri
 ok(throws, "enumerate object then a string");
 
 throws = false;
 try {
 var cursor = storage.enumerate({"path": "a", "since": new Date(0) });
 } catch(e) {throws = true}
 ok(!throws, "enumerate object parameter with path");
 
-
+SimpleTest.finish()
 
+});
 
-devicestorage_cleanup()
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_freeSpace.html
+++ b/dom/devicestorage/test/test_freeSpace.html
@@ -17,45 +17,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function freeSpaceSuccess(e) {
   ok(e.target.result  > 0, "free bytes should exist and be greater than zero");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function freeSpaceError(e) {
   ok(false, "freeSpaceError was called");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 var storage = navigator.getDeviceStorage("pictures");
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
   request = storage.freeSpace();
   ok(request, "Should have a non-null request");
 
   request.onsuccess = freeSpaceSuccess;
   request.onerror = freeSpaceError;
 }
 
 var prefix = "devicestorage/" + randomFilename(12);
 request = storage.addNamed(createRandomBlob('image/png'), prefix + "/a/b.png");
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_appendFile.html
+++ b/dom/devicestorage/test/test_fs_appendFile.html
@@ -21,38 +21,38 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 var file   = new Blob(["This is a text file."], {type: "text/plain"});
 var appendFile   = new Blob([" Another text file."], {type: "text/plain"});
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function deleteSuccess(e) {
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function deleteError(e) {
   ok(false, "deleteError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function appendSuccess(e) {
   ok(true, "appendSuccess was called.");
 
   request = gStorage.delete(e.target.result)
   request.onsuccess = deleteSuccess;
   request.onerror = deleteError;
 }
 
 function appendError(e) {
   ok(false, "appendError was called.");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
   ok(true, "addSuccess was called");
 
   request = gStorage.appendNamed(appendFile, e.target.result);
   ok(request, "Should have a non-null request");
 
@@ -78,13 +78,14 @@ function runtest() {
   addFile();
 }
 
 var gStorage = navigator.getDeviceStorage("sdcard");
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 ok(gStorage, "Should get storage from sdcard");
 runtest();
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_basic.html
+++ b/dom/devicestorage/test/test_fs_basic.html
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 var gFileName = randomFilename(12);
 
 // The root directory object.
 var gRoot;
 
 function getRootSuccess(r) {
   ok(r && r.name === storage.storageName, "Failed to get the root directory.");
@@ -41,30 +41,32 @@ function createDirectorySuccess(d) {
   ok(d.name === gFileName, "Failed to create directory: name mismatch.");
 
   // Get the new created directory from the root.
   gRoot.get(gFileName).then(getSuccess, cbError);
 }
 
 function getSuccess(d) {
   ok(d.name === gFileName, "Should get directory - " + gFileName + ".");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function cbError(e) {
   ok(false,  "Should not arrive here! Error: " + e.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(storage, "Should have gotten a storage");
 
 var promise = storage.getRoot();
 ok(promise, "Should have a non-null promise");
 
 promise.then(getRootSuccess, cbError);
+
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_createDirectory.html
+++ b/dom/devicestorage/test/test_fs_createDirectory.html
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 // The root directory object.
 var gRoot;
 var gTestCount = 0;
 var gPath = '';
 var gName = '';
 
 function testCreateDirectory(rootDir, path) {
@@ -54,52 +54,54 @@ function getSuccess(d) {
       gName = randomFilename(12);
       gPath = gPath + '/' + gName;
       testCreateDirectory(d, gName);
       break;
     case 2:
       // Create directory with an existing path.
       gRoot.createDirectory(gPath).then(function(what) {
         ok(false, "Should not overwrite an existing directory.");
-        devicestorage_cleanup();
+        SimpleTest.finish();
       }, function(e) {
         ok(true, "Creating directory should fail if it already exists.");
 
         // Create a directory whose intermediate directory doesn't exit.
         gName = randomFilename(12);
         gPath = 'sub1/sub2/' + gName;
         testCreateDirectory(gRoot, gPath);
       });
       break;
     default:
       // Create the parent directory.
       d.createDirectory('..').then(function(what) {
         ok(false, "Should not overwrite an existing directory.");
-        devicestorage_cleanup();
+        SimpleTest.finish();
       }, function(e) {
         ok(true, "Accessing parent directory with '..' is not allowed.");
-        devicestorage_cleanup();
+        SimpleTest.finish();
       });
       break;
   }
   gTestCount++;
 }
 
 function cbError(e) {
   ok(false, e.name + " error should not arrive here!");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage.");
 
 var storage = navigator.getDeviceStorage("pictures");
 ok(storage, "Should have gotten a storage.");
 
 var promise = storage.getRoot();
 ok(promise, "Should have a non-null promise for getRoot.");
 
 gName = storage.storageName;
 promise.then(getSuccess, cbError);
+
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_createFile.html
+++ b/dom/devicestorage/test/test_fs_createFile.html
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.7">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 let gTestCount = 0;
 let gFileReader = new FileReader();
 let gRoot;
 
 function str2array(str) {
   let strlen = str.length;
   let buf = new ArrayBuffer(strlen);
@@ -79,17 +79,17 @@ let gTestCases = [
     data: null,
     shouldPass: false,
     mode: "fail"
   }
 ];
 
 function next() {
   if (gTestCount >= gTestCases.length) {
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
   let c = gTestCases[gTestCount++];
   gRoot.createFile("text.png", {
     data: c.data,
     ifExists: c.mode
   }).then(function(file) {
     is(c.shouldPass, true, "[" + gTestCount + "] Success callback was called for createFile.");
@@ -117,16 +117,17 @@ ok(storage, "Should have gotten a storag
 
 // Get the root directory
 storage.getRoot().then(function(dir) {
   ok(dir, "Should have gotten the root directory.");
   gRoot = dir;
   next();
 }, function(e) {
   ok(false, e.name + " error should not arrive here!");
-  devicestorage_cleanup();
+  SimpleTest.finish();
+});
+
 });
 
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_get.html
+++ b/dom/devicestorage/test/test_fs_get.html
@@ -16,17 +16,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=910412">Mozilla Bug 910412</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
+
 SimpleTest.requestCompleteLog();
 
 // The root directory object.
 var gRoot = null;
 var gSub1 = null;
 var gSub2 = null;
 var gTestCount = 0;
 var gPath = "/";
@@ -72,17 +73,17 @@ function getSuccess(r) {
       break;
     case 5:
       // Get sub1 from sub1/sub2 with "..".
       gPath = "sub1/sub2/..";
       testGetFailure(gSub2, "..");
       break;
     default:
       ok(false, "Should not arrive at getSuccess!");
-      devicestorage_cleanup();
+      SimpleTest.finish();
       break;
   }
   gTestCount++;
 }
 
 function getFailure(e) {
   ok(true, "[" + gTestCount +"] Should not get the file - " + gPath + ". Error: " + e.name);
   switch (gTestCount) {
@@ -132,34 +133,34 @@ function getFailure(e) {
       testGetFailure(gSub2, "");
       break;
     case 16:
       // Test special path "//".
       gPath = "sub1//sub2";
       testGetFailure(gRoot, "sub1//sub2");
       break;
     case 17:
-      devicestorage_cleanup();
+      SimpleTest.finish();
       break;
     default:
       ok(false, "Should not arrive here!");
-      devicestorage_cleanup();
+      SimpleTest.finish();
       break;
   }
   gTestCount++;
 }
 
 function cbError(e) {
   ok(false, "Should not arrive at cbError! Error: " + e.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function cbSuccess(e) {
   ok(false, "Should not arrive at cbSuccess!");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage.");
 
 var gStorage = navigator.getDeviceStorage("pictures");
 ok(gStorage, "Should have gotten a storage.");
 
 function createTestFile(path, callback) {
@@ -168,17 +169,17 @@ function createTestFile(path, callback) 
 
     req.onsuccess = function() {
       ok(true, path + " was created.");
       callback();
     };
 
     req.onerror = function(e) {
       ok(false, "Failed to create " + path + ": " + e.target.error.name);
-      devicestorage_cleanup();
+      SimpleTest.finish();
     };
   }
 
   // Bug 980136. Check if the file exists before we create.
   var req = gStorage.get(path);
 
   req.onsuccess = function() {
     ok(true, path + " exists. Do not need to create.");
@@ -192,13 +193,14 @@ function createTestFile(path, callback) 
 }
 
 createTestFile("sub1/sub2/test.png", function() {
   var promise = gStorage.getRoot();
   ok(promise, "Should have a non-null promise for getRoot.");
   promise.then(getSuccess, cbError);
 });
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_getFilesAndDirectories.html
+++ b/dom/devicestorage/test/test_fs_getFilesAndDirectories.html
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=XXX">Mozilla Bug XXX</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 SimpleTest.requestCompleteLog();
 
 // The root directory object.
 var gRoot = null;
 
 function checkContents1(contents) {
   var expected = {
     "sub2": "/sub",
@@ -57,22 +57,22 @@ function checkContents1(contents) {
     ok(false, "Expected '" + missing.name + "' in /sub");
   }
 
   sub2.getFilesAndDirectories().then(checkContents2, handleError);
 }
 
 function checkContents2(contents) {
   is(contents[0].name, "c.png", "'sub2' should contain 'c.png'");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function handleError(e) {
   ok(false, "Should not arrive at handleError! Error: " + e.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 var gStorage = navigator.getDeviceStorage("pictures");
 
 ok(gStorage, "Should have gotten a storage.");
 
 function runTests() {
   gStorage.getRoot().then(function(rootDir) {
@@ -82,16 +82,17 @@ function runTests() {
     return subDir.getFilesAndDirectories();
   }).then(checkContents1).catch(handleError);
 }
 
 createTestFiles(gStorage, ["sub/a.png", "sub/b.png", "sub/sub2/c.png", "sub/sub3/d.png"]).then(function() {
   runTests();
 }, function() {
   ok(false, "Failed to created test files.");
-  devicestorage_cleanup();
+  SimpleTest.finish();
+});
+
 });
 
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_fs_remove.html
+++ b/dom/devicestorage/test/test_fs_remove.html
@@ -16,17 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=934368">Mozilla Bug 934368</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.7">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 let gStorage = null;
 let gTestCount = 0;
 let gFileMap = {};
 let gRemoveDeep = true;
 
 let gTestCases = [
   // Remove a non-existent file should return false.
@@ -88,17 +88,17 @@ let gTestCases = [
   }
 ];
 
 function runNextTests() {
   gTestCount = 0;
   function runTests() {
     function cbError(e) {
       ok(false, "Should not arrive at cbError! Error: " + e.name);
-      devicestorage_cleanup();
+      SimpleTest.finish();
     }
 
     function cbSuccess(r) {
       ok(r, "Should get the file - " + this);
       gFileMap[this] = r;
     }
 
     // Get directory and file objects.
@@ -111,25 +111,25 @@ function runNextTests() {
       ["sub1", "sub1/sub2", "sub1/sub2/a.png", "sub/b.png"].forEach(function(path) {
         arr.push(root.get(path).then(cbSuccess.bind(path), cbError));
       });
 
       Promise.all(arr).then(function() {
         testNextRemove();
       }, function() {
         ok(false, "Failed to get test files.");
-        devicestorage_cleanup();
+        SimpleTest.finish();
       });
     }, cbError);
   };
   createTestFiles(gStorage, ["sub1/sub2/a.png", "sub/b.png"]).then(function() {
     runTests();
   }, function() {
     ok(false, "Failed to created test files.");
-    devicestorage_cleanup();
+    SimpleTest.finish();
   });
 }
 
 function testNextRemove() {
   if (gTestCount < gTestCases.length) {
     let data = gTestCases[gTestCount++];
     let dir = gFileMap[data.dir];
     let path = data.path || gFileMap[data.target];
@@ -151,25 +151,26 @@ function testNextRemove() {
 
   if (gRemoveDeep) {
     // Test "remove" after "removeDeep".
     gRemoveDeep = false;
     runNextTests();
     return;
   }
 
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage.");
 
 gStorage = navigator.getDeviceStorage("pictures");
 ok(gStorage, "Should have gotten a storage.");
 
 // Test "removeDeep" first.
 gRemoveDeep = true;
 runNextTests();
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_lastModificationFilter.html
+++ b/dom/devicestorage/test/test_lastModificationFilter.html
@@ -12,23 +12,24 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 SimpleTest.requestFlakyTimeout("untriaged");
-devicestorage_setup();
+devicestorage_setup(function () {
+
 // We put the old files in 2 levels deep. When you add a file to a directory
 // it will modify the parents last modification time, but not the parents
 // parents. So we want to make sure that even though x's timestamp is earlier
 // than the since parameter, we still pick up the later files.
 var oldFiles = ["x/y/aa.png", "x/y/ab.png", "x/y/ac.png"];
 var timeFile = "t.png";
 var newFiles = ["x/y/ad.png", "x/y/ae.png", "x/y/af.png", // new files in old dir
                 "z/bd.png", "z/be.png", "z/bf.png"];      // new files in new dir
@@ -39,17 +40,17 @@ var i;
 var timestamp;
 var lastFileAddedTimestamp;
 
 function verifyAndDelete(prefix, files, e) {
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
     dump("We still have length = " + files.length + "\n");
     dump(files + "\n");
-    devicestorage_cleanup();
+    SimpleTest.finish();
     return;
   }
 
   var filename = e.target.result.name;
   if (filename[0] == "/") {
     // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
     filename = filename.substring(1); // Remove leading slash
@@ -81,22 +82,22 @@ function addFile(filename, callback) {
     // that we just added
     var getReq = storage.get(prefix + '/' + filename);
     getReq.onsuccess = function(e) {
       lastFileAddedTimestamp = e.target.result.lastModifiedDate;
       callback();
     }
     getReq.onerror = function(e) {
       ok(false, "getError was called : " + e.target.error.name);
-      devicestorage_cleanup();
+      SimpleTest.finish();
     };
   }
   addReq.onerror = function(e) {
     ok(false, "addError was called : " + e.target.error.name);
-    devicestorage_cleanup();
+    SimpleTest.finish();
   }
 }
 
 function addFileArray(fileArray, callback) {
   var i = 0;
   function addNextFile() {
     i = i + 1;
     if (i == fileArray.length) {
@@ -110,31 +111,31 @@ function addFileArray(fileArray, callbac
 
 function delFile(filename, callback) {
   var req = storage.delete(prefix + '/' + filename);
   req.onsuccess = function(e) {
     callback();
   };
   req.onerror = function(e) {
     ok(false, "delError was called : " + e.target.error.name);
-    devicestorage_cleanup();
+    SimpleTest.finish();
   };
 }
 
 function afterNewFiles() {
   var cursor = storage.enumerate(prefix, {"since": timestamp});
   cursor.onsuccess = function(e) {
     verifyAndDelete(prefix, newFiles, e);
     if (e.target.result) {
       e.target.continue();
     }
   };
   cursor.onerror = function (e) {
     ok(false, "handleError was called : " + e.target.error.name);
-    devicestorage_cleanup();
+    SimpleTest.finish();
   };
 }
 
 function waitForTimestampChange() {
   // We've added a new file. See if the timestamp differs from
   // oldFileAddedTimestamp, and if it's the same wait for a bit
   // and try again.
   if (lastFileAddedTimestamp.valueOf() === timestamp.valueOf()) {
@@ -162,13 +163,14 @@ function afterOldFiles() {
 }
 
 function addOldFiles() {
   addFileArray(oldFiles, afterOldFiles);
 }
 
 addOldFiles();
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_overrideDir.html
+++ b/dom/devicestorage/test/test_overrideDir.html
@@ -12,36 +12,36 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 var gFileName = "devicestorage/" + randomFilename(12) + "/hi.png";
 var gData = "My name is Doug Turner.  My IRC nick is DougT.  I like Maple cookies."
 var gDataBlob = new Blob([gData], {type: 'image/png'});
 var gFileReader = new FileReader();
 
 function getAfterDeleteSuccess(e) {
   ok(false, "file was deleted not successfully");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function getAfterDeleteError(e) {
   ok(true, "file was deleted successfully");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function deleteSuccess(e) {
 
   ok(e.target.result == gFileName, "File name should match");
   dump(e.target.result + "\n")
 
   // File was deleted using the sdcard stoage area. It should be gone
@@ -49,17 +49,17 @@ function deleteSuccess(e) {
   var storage = navigator.getDeviceStorage("pictures");
   request = storage.get(e.target.result);
   request.onsuccess = getAfterDeleteSuccess;
   request.onerror = getAfterDeleteError;
 }
 
 function deleteError(e) {
   ok(false, "deleteError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function getSuccess(e) {
   // We wrote the file out using pictures type. Since we've over-ridden the
   // root directory, we should be able to read it back using the sdcard
   // storage area.
   var storage = navigator.getDeviceStorage("sdcard");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
@@ -87,17 +87,17 @@ function readerCallback(e) {
 
   is(ab.byteLength, gData.length, "wrong arraybuffer byteLength");
   var u8v = new Uint8Array(ab);
   is(String.fromCharCode.apply(String, u8v), gData, "wrong values");
 }
 
 function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
 
   var filename = e.target.result;
   if (filename[0] == "/") {
     // We got /storageName/prefix/filename
     // Remove the storageName (this shows up on FirefoxOS)
@@ -118,17 +118,17 @@ function addSuccess(e) {
   request.onsuccess = getSuccess;
   request.onerror = getError;
 
   ok(true, "addSuccess was called");
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function startTest() {
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   var storage = navigator.getDeviceStorage("pictures");
   ok(storage, "Should have gotten a storage");
 
@@ -151,13 +151,14 @@ try {
   } catch (e) {}
   SpecialPowers.pushPrefEnv({'set': [["device.storage.overrideRootDir", f.path],
                                      ["device.storage.testing", false]]},
     function() {
       startTest();
     });
 } catch(e) {}
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_overwrite.html
+++ b/dom/devicestorage/test/test_overwrite.html
@@ -13,38 +13,38 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 var filename = "devicestorage/aaaa.png"
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 
 function deleteSuccess(e) {
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function deleteError(e) {
   ok(false, "deleteError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addOverwritingSuccess(e) {
   ok(false, "addOverwritingSuccess was called.");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addOverwritingError(e) {
   ok(true, "Adding to the same location should fail");
   ok(e.target.error.name == "NoModificationAllowedError", "Error must be NoModificationAllowedError");
 
   var storage = navigator.getDeviceStorage("pictures");
   request = storage.delete(filename)
@@ -80,13 +80,14 @@ function runtest() {
   ok(request, "Should have a non-null request");
 
   request.onsuccess = addSuccess;
   request.onerror = addError;
 }
 
 runtest();
 
+});
+
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_sanity.html
+++ b/dom/devicestorage/test/test_sanity.html
@@ -13,22 +13,22 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="text/javascript" src="devicestorage_common.js"></script>
 
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup()
+devicestorage_setup(function () {
 
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 var storage;
 
 var throws = false;
 try {
  storage = navigator.getDeviceStorage();
@@ -45,15 +45,16 @@ storage = navigator.getDeviceStorage("mu
 ok(storage, "music - Should have getDeviceStorage");
 
 storage = navigator.getDeviceStorage("videos");
 ok(storage, "videos - Should have getDeviceStorage");
 
 var cursor = storage.enumerate();
 ok(cursor, "Should have a non-null cursor");
 
-devicestorage_cleanup();
+SimpleTest.finish();
+
+});
 
 </script>
 </pre>
 </body>
 </html>
-
--- a/dom/devicestorage/test/test_storageAreaListener.html
+++ b/dom/devicestorage/test/test_storageAreaListener.html
@@ -18,17 +18,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1126694">Mozilla Bug 1126684</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-	devicestorage_setup()
+	devicestorage_setup(function () {
 
 	var XPCOMUtils = SpecialPowers.Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
 	var Ci = SpecialPowers.Ci;
 
 	var volumeService = SpecialPowers.Cc["@mozilla.org/telephony/volume-service;1"].getService(Ci.nsIVolumeService);
 
 	var volName = "dummy-volume";
 	var mountPoint = "/data/local/tmp/dummy";
@@ -44,21 +44,23 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 		if (e.operation == "added") {
 			storage = navigator.getDeviceStorageByNameAndType(e.storageName, "sdcard");
 			ok (storage, "got storage");
 			volumeService.removeFakeVolume(volName);
 		}
 		else if (e.operation == "removed") {
 			ok (true, "got removal event");
-			devicestorage_cleanup();
+			SimpleTest.finish();
 		}
 	});
 
 	storage = navigator.getDeviceStorageByNameAndType(volName, "sdcard");
 	ok(!storage, "storage area doesn't exist");
 
 	volumeService.createFakeVolume(volName, mountPoint);
 
+        });
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/devicestorage/test/test_usedSpace.html
+++ b/dom/devicestorage/test/test_usedSpace.html
@@ -17,45 +17,47 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 function usedSpaceSuccess(e) {
   ok(e.target.result > 0, "total bytes should exist and be greater than zero");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function usedSpaceError(e) {
   ok(false, "usedSpaceError was called");
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 var storage = navigator.getDeviceStorage("pictures");
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function addSuccess(e) {
   request = storage.usedSpace();
   ok(request, "Should have a non-null request");
 
   request.onsuccess = usedSpaceSuccess;
   request.onerror = usedSpaceError;
 }
 
 var prefix = "devicestorage/" + randomFilename(12);
 request = storage.addNamed(createRandomBlob('image/png'), prefix + "/a/b.png");
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/devicestorage/test/test_watch.html
+++ b/dom/devicestorage/test/test_watch.html
@@ -17,26 +17,26 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 var gFileName = randomFilename(20) + ".png"
 
 function addSuccess(e) {
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function onChange(e) {
 
   dump("we saw: " + e.path + " " + e.reason + "\n");
 
   var filename = e.path;
   if (filename[0] == "/") {
@@ -46,17 +46,17 @@ function onChange(e) {
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename == gFileName) {
     ok(true, "we saw the file get created");
     storage.removeEventListener("change", onChange);
-    devicestorage_cleanup();
+    SimpleTest.finish();
   }
   else {
     // we may see other file changes during the test, and
     // that is completely ok
   }
 }
 
 var storage = navigator.getDeviceStorage("pictures");
@@ -64,12 +64,14 @@ ok(storage, "Should have storage");
 storage.addEventListener("change", onChange);
 
 request = storage.addNamed(createRandomBlob('image/png'), gFileName);
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/devicestorage/test/test_watchOther.html
+++ b/dom/devicestorage/test/test_watchOther.html
@@ -17,26 +17,26 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=717103">Mozilla Bug 717103</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-devicestorage_setup();
+devicestorage_setup(function () {
 
 var gFileName = randomFilename(20) + ".png"
 
 function addSuccess(e) {
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
-  devicestorage_cleanup();
+  SimpleTest.finish();
 }
 
 function onChange(e) {
 
   dump("we saw: " + e.path + " " + e.reason + "\n");
 
   var filename = e.path;
   if (filename[0] == "/") {
@@ -46,17 +46,17 @@ function onChange(e) {
     var slashIndex = filename.indexOf("/");
     if (slashIndex >= 0) {
       filename = filename.substring(slashIndex + 1); // Remove storageName
     }
   }
   if (filename == gFileName) {
     ok(true, "we saw the file get created");
     storage.removeEventListener("change", onChange);
-    devicestorage_cleanup();
+    SimpleTest.finish();
   }
   else {
     // we may see other file changes during the test, and
     // that is completely ok
   }
 }
 
 function onChangeFail(e) {
@@ -73,12 +73,14 @@ ok(storageOther, "Should have storage");
 storageOther.addEventListener("change", onChangeFail);
 
 request = storage.addNamed(createRandomBlob('image/png'), gFileName);
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
+});
+
 </script>
 </pre>
 </body>
 </html>
--- a/dom/encoding/TextDecoder.cpp
+++ b/dom/encoding/TextDecoder.cpp
@@ -21,17 +21,17 @@ TextDecoder::Init(const nsAString& aLabe
 {
   nsAutoCString encoding;
   // Let encoding be the result of getting an encoding from label.
   // If encoding is failure or replacement, throw a RangeError
   // (https://encoding.spec.whatwg.org/#dom-textdecoder).
   if (!EncodingUtils::FindEncodingForLabelNoReplacement(aLabel, encoding)) {
     nsAutoString label(aLabel);
     EncodingUtils::TrimSpaceCharacters(label);
-    aRv.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(&label);
+    aRv.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(label);
     return;
   }
   InitWithEncoding(encoding, aFatal);
 }
 
 void
 TextDecoder::InitWithEncoding(const nsACString& aEncoding, const bool aFatal)
 {
--- a/dom/encoding/TextEncoder.cpp
+++ b/dom/encoding/TextEncoder.cpp
@@ -16,17 +16,17 @@ TextEncoder::Init(const nsAString& aEnco
 {
   nsAutoString label(aEncoding);
   EncodingUtils::TrimSpaceCharacters(label);
 
   // Let encoding be the result of getting an encoding from label.
   // If encoding is failure, or is none of utf-8, utf-16, and utf-16be,
   // throw a RangeError (https://encoding.spec.whatwg.org/#dom-textencoder).
   if (!EncodingUtils::FindEncodingForLabel(label, mEncoding)) {
-    aRv.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(&label);
+    aRv.ThrowRangeError<MSG_ENCODING_NOT_SUPPORTED>(label);
     return;
   }
 
   if (!mEncoding.EqualsLiteral("UTF-8") &&
       !mEncoding.EqualsLiteral("UTF-16LE") &&
       !mEncoding.EqualsLiteral("UTF-16BE")) {
     aRv.ThrowRangeError<MSG_DOM_ENCODING_NOT_UTF>();
     return;
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -1002,32 +1002,30 @@ FetchBody<Derived>::ContinueConsumeBody(
   ErrorResult error;
 
   switch (mConsumeType) {
     case CONSUME_ARRAYBUFFER: {
       JS::Rooted<JSObject*> arrayBuffer(cx);
       FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
                                     error);
 
-      error.WouldReportJSException();
       if (!error.Failed()) {
         JS::Rooted<JS::Value> val(cx);
         val.setObjectOrNull(arrayBuffer);
 
         localPromise->MaybeResolve(cx, val);
         // ArrayBuffer takes over ownership.
         autoFree.Reset();
       }
       break;
     }
     case CONSUME_BLOB: {
       RefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
         DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
         aResultLength, aResult, error);
-      error.WouldReportJSException();
       if (!error.Failed()) {
         localPromise->MaybeResolve(blob);
         // File takes over ownership.
         autoFree.Reset();
       }
       break;
     }
     case CONSUME_FORMDATA: {
@@ -1061,23 +1059,17 @@ FetchBody<Derived>::ContinueConsumeBody(
       break;
     }
     default:
       NS_NOTREACHED("Unexpected consume body type");
   }
 
   error.WouldReportJSException();
   if (error.Failed()) {
-    if (error.IsJSException()) {
-      JS::Rooted<JS::Value> exn(cx);
-      error.StealJSException(cx, &exn);
-      localPromise->MaybeReject(cx, exn);
-    } else {
-      localPromise->MaybeReject(error);
-    }
+    localPromise->MaybeReject(error);
   }
 }
 
 template <class Derived>
 already_AddRefed<Promise>
 FetchBody<Derived>::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
 {
   mConsumeType = aType;
--- a/dom/fetch/InternalHeaders.cpp
+++ b/dom/fetch/InternalHeaders.cpp
@@ -175,30 +175,30 @@ InternalHeaders::IsSimpleHeader(const ns
 }
 
 //static
 bool
 InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv)
 {
   if (!NS_IsValidHTTPToken(aName)) {
     NS_ConvertUTF8toUTF16 label(aName);
-    aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(&label);
+    aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(label);
     return true;
   }
 
   return false;
 }
 
 // static
 bool
 InternalHeaders::IsInvalidValue(const nsACString& aValue, ErrorResult& aRv)
 {
   if (!NS_IsReasonableHTTPHeaderValue(aValue)) {
     NS_ConvertUTF8toUTF16 label(aValue);
-    aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(&label);
+    aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(label);
     return true;
   }
   return false;
 }
 
 bool
 InternalHeaders::IsImmutable(ErrorResult& aRv) const
 {
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -97,26 +97,26 @@ GetRequestURLFromDocument(nsIDocument* a
 {
   MOZ_ASSERT(aDocument);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIURI> baseURI = aDocument->GetBaseURI();
   nsCOMPtr<nsIURI> resolvedURI;
   aRv = NS_NewURI(getter_AddRefs(resolvedURI), aInput, nullptr, baseURI);
   if (NS_WARN_IF(aRv.Failed())) {
-    aRv.ThrowTypeError<MSG_INVALID_URL>(&aInput);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
     return;
   }
 
   // This fails with URIs with weird protocols, even when they are valid,
   // so we ignore the failure
   nsAutoCString credentials;
   Unused << resolvedURI->GetUserPass(credentials);
   if (!credentials.IsEmpty()) {
-    aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(&aInput);
+    aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
     return;
   }
 
   nsCOMPtr<nsIURI> resolvedURIClone;
   // We use CloneIgnoringRef to strip away the fragment even if the original URI
   // is immutable.
   aRv = resolvedURI->CloneIgnoringRef(getter_AddRefs(resolvedURIClone));
   if (NS_WARN_IF(aRv.Failed())) {
@@ -136,26 +136,26 @@ void
 GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL,
                         ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIURI> uri;
   aRv = NS_NewURI(getter_AddRefs(uri), aInput, nullptr, nullptr);
   if (NS_WARN_IF(aRv.Failed())) {
-    aRv.ThrowTypeError<MSG_INVALID_URL>(&aInput);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
     return;
   }
 
   // This fails with URIs with weird protocols, even when they are valid,
   // so we ignore the failure
   nsAutoCString credentials;
   Unused << uri->GetUserPass(credentials);
   if (!credentials.IsEmpty()) {
-    aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(&aInput);
+    aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
     return;
   }
 
   nsCOMPtr<nsIURI> uriClone;
   // We use CloneIgnoringRef to strip away the fragment even if the original URI
   // is immutable.
   aRv = uri->CloneIgnoringRef(getter_AddRefs(uriClone));
   if (NS_WARN_IF(aRv.Failed())) {
@@ -178,34 +178,34 @@ GetRequestURLFromWorker(const GlobalObje
   workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref);
   RefPtr<workers::URL> url =
     workers::URL::Constructor(aGlobal, aInput, baseURL, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
-    aRv.ThrowTypeError<MSG_INVALID_URL>(&aInput);
+    aRv.ThrowTypeError<MSG_INVALID_URL>(aInput);
     return;
   }
 
   nsString username;
   url->GetUsername(username, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   nsString password;
   url->GetPassword(password, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (!username.IsEmpty() || !password.IsEmpty()) {
-    aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(&aInput);
+    aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput);
     return;
   }
 
   url->SetHash(EmptyString(), aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
@@ -311,17 +311,17 @@ Request::Constructor(const GlobalObject&
 
     // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP
     // token, since HTTP states that Method may be any of the defined values or
     // a token (extension method).
     nsAutoCString outMethod;
     nsresult rv = FetchUtil::GetValidRequestMethod(method, outMethod);
     if (NS_FAILED(rv)) {
       NS_ConvertUTF8toUTF16 label(method);
-      aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(&label);
+      aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label);
       return nullptr;
     }
 
     // Step 14.2
     request->ClearCreatedByFetchEvent();
     request->SetMethod(outMethod);
   }
 
@@ -345,17 +345,17 @@ Request::Constructor(const GlobalObject&
   requestHeaders->SetGuard(HeadersGuardEnum::Request, aRv);
   MOZ_ASSERT(!aRv.Failed());
 
   if (request->Mode() == RequestMode::No_cors) {
     if (!request->HasSimpleMethod()) {
       nsAutoCString method;
       request->GetMethod(method);
       NS_ConvertUTF8toUTF16 label(method);
-      aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(&label);
+      aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label);
       return nullptr;
     }
 
     requestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv);
     if (aRv.Failed()) {
       return nullptr;
     }
   }
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2404,16 +2404,17 @@ HTMLMediaElement::WakeLockCreate()
 }
 
 void
 HTMLMediaElement::WakeLockRelease()
 {
   if (mWakeLock) {
     ErrorResult rv;
     mWakeLock->Unlock(rv);
+    rv.SuppressException();
     mWakeLock = nullptr;
   }
 }
 
 bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
                                       nsIAtom* aAttribute,
                                       const nsAString& aValue,
                                       nsAttrValue& aResult)
@@ -4771,16 +4772,21 @@ HTMLMediaElement::IsPlayingThroughTheAud
     return true;
   }
 
   // If we are seeking, we consider it as playing
   if (mPlayingThroughTheAudioChannelBeforeSeek) {
     return true;
   }
 
+  // If we are playing an external stream.
+  if (mSrcAttrStream) {
+    return true;
+  }
+
   return false;
 }
 
 void
 HTMLMediaElement::UpdateAudioChannelPlayingState()
 {
   bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
 
--- a/dom/html/HTMLVideoElement.cpp
+++ b/dom/html/HTMLVideoElement.cpp
@@ -262,16 +262,17 @@ HTMLVideoElement::WakeLockRelease()
 void
 HTMLVideoElement::UpdateScreenWakeLock()
 {
   bool hidden = OwnerDoc()->Hidden();
 
   if (mScreenWakeLock && (mPaused || hidden || !mUseScreenWakeLock)) {
     ErrorResult rv;
     mScreenWakeLock->Unlock(rv);
+    rv.SuppressException();
     mScreenWakeLock = nullptr;
     return;
   }
 
   if (!mScreenWakeLock && !mPaused && !hidden &&
       mUseScreenWakeLock && HasVideo()) {
     RefPtr<power::PowerManagerService> pmService =
       power::PowerManagerService::GetInstance();
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -3289,25 +3289,57 @@ nsGenericHTMLElement::NewURIFromString(c
     // and waiting for the subsequent load to fail.
     NS_RELEASE(*aURI);
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   return NS_OK;
 }
 
+static bool
+IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell)
+{
+  nsTArray<Element*> elementsToCheck;
+  for (Element* e = aElement; e; e = e->GetParentElement()) {
+    if (e->GetPrimaryFrame()) {
+      // e definitely isn't display:none and doesn't have a display:none
+      // ancestor.
+      break;
+    }
+    elementsToCheck.AppendElement(e);
+  }
+
+  if (elementsToCheck.IsEmpty()) {
+    return false;
+  }
+
+  nsStyleSet* styleSet = aPresShell->StyleSet();
+  RefPtr<nsStyleContext> sc;
+  for (int32_t i = elementsToCheck.Length() - 1; i >= 0; --i) {
+    if (sc) {
+      sc = styleSet->ResolveStyleFor(elementsToCheck[i], sc);
+    } else {
+      sc = nsComputedDOMStyle::GetStyleContextForElementNoFlush(elementsToCheck[i],
+                                                                nullptr, aPresShell);
+    }
+    if (sc->StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_NONE) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void
 nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue,
                                    mozilla::ErrorResult& aError)
 {
   if (!GetPrimaryFrame(Flush_Layout)) {
-    RefPtr<nsStyleContext> sc =
-      nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr, nullptr);
-    if (!sc || sc->StyleDisplay()->mDisplay == NS_STYLE_DISPLAY_NONE ||
-        !IsInComposedDoc()) {
+    nsIPresShell* presShell = nsComputedDOMStyle::GetPresShellForContent(this);
+    if (!presShell || IsOrHasAncestorWithDisplayNone(this, presShell)) {
       GetTextContentInternal(aValue, aError);
       return;
     }
   }
 
   nsRange::GetInnerTextNoFlush(aValue, aError, this, 0, this, GetChildCount());
 }
 
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -955,20 +955,26 @@ public:
   ShouldExposeNameAsHTMLDocumentProperty(Element* aElement)
   {
     return aElement->IsHTMLElement() &&
            CanHaveName(aElement->NodeInfo()->NameAtom());
   }
   static inline bool
   ShouldExposeIdAsHTMLDocumentProperty(Element* aElement)
   {
-    return aElement->IsAnyOfHTMLElements(nsGkAtoms::img,
-                                         nsGkAtoms::applet,
-                                         nsGkAtoms::embed,
-                                         nsGkAtoms::object);
+    if (aElement->IsAnyOfHTMLElements(nsGkAtoms::applet,
+                                      nsGkAtoms::embed,
+                                      nsGkAtoms::object)) {
+      return true;
+    }
+
+    // Per spec, <img> is exposed by id only if it also has a nonempty
+    // name (which doesn't have to match the id or anything).
+    // HasName() is true precisely when name is nonempty.
+    return aElement->IsHTMLElement(nsGkAtoms::img) && aElement->HasName();
   }
 
   static bool
   IsScrollGrabAllowed(JSContext*, JSObject*);
 
 protected:
   /**
    * Add/remove this element to the documents name cache
--- a/dom/imptests/failures/html/html/dom/documents/dta/test_nameditem-06.html.json
+++ b/dom/imptests/failures/html/html/dom/documents/dta/test_nameditem-06.html.json
@@ -1,5 +1,3 @@
 {
-  "If there are two imgs, a collection should be returned. (name)": true,
-  "If there is one img, it should not be returned (id)": true,
-  "If there are two imgs, nothing should be returned. (id)": true
+  "If there are two imgs, a collection should be returned. (name)": true
 }
--- a/dom/imptests/html/html/dom/documents/dta/test_nameditem-06.html
+++ b/dom/imptests/html/html/dom/documents/dta/test_nameditem-06.html
@@ -58,19 +58,17 @@ test(function() {
 
 test(function() {
   var img1 = document.getElementsByTagName("img")[4];
   assert_equals(img1.id, "test4");
   var img2 = document.getElementsByTagName("img")[5];
   assert_equals(img2.id, "test4");
 
   assert_false("test4" in document, '"test4" in document should be false');
-  var collection = document.test4;
-  assert_class_string(collection, "HTMLCollection", "collection should be an HTMLCollection");
-  assert_array_equals(collection, [img1, img2]);
+  assert_equals(document.test4, undefined);
 }, "If there are two imgs, nothing should be returned. (id)");
 
 test(function() {
   var img1 = document.getElementsByTagName("img")[6];
   assert_equals(img1.name, "test5");
   var img2 = document.getElementsByTagName("img")[7];
   assert_equals(img2.id, "test5");
 
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -1092,17 +1092,16 @@ PermissionRequestChildProcessActor::Recv
 }
 
 /*******************************************************************************
  * BackgroundRequestChildBase
  ******************************************************************************/
 
 BackgroundRequestChildBase::BackgroundRequestChildBase(IDBRequest* aRequest)
   : mRequest(aRequest)
-  , mActorDestroyed(false)
 {
   MOZ_ASSERT(aRequest);
   aRequest->AssertIsOnOwningThread();
 
   MOZ_COUNT_CTOR(indexedDB::BackgroundRequestChildBase);
 }
 
 BackgroundRequestChildBase::~BackgroundRequestChildBase()
@@ -1118,25 +1117,16 @@ void
 BackgroundRequestChildBase::AssertIsOnOwningThread() const
 {
   MOZ_ASSERT(mRequest);
   mRequest->AssertIsOnOwningThread();
 }
 
 #endif // DEBUG
 
-void
-BackgroundRequestChildBase::NoteActorDestroyed()
-{
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(!mActorDestroyed);
-
-  mActorDestroyed = true;
-}
-
 /*******************************************************************************
  * BackgroundFactoryChild
  ******************************************************************************/
 
 BackgroundFactoryChild::BackgroundFactoryChild(IDBFactory* aFactory)
   : mFactory(aFactory)
 #ifdef DEBUG
   , mOwningThread(NS_GetCurrentThread())
@@ -1271,18 +1261,17 @@ BackgroundFactoryRequestChild::~Backgrou
   MOZ_COUNT_DTOR(indexedDB::BackgroundFactoryRequestChild);
 }
 
 IDBOpenDBRequest*
 BackgroundFactoryRequestChild::GetOpenDBRequest() const
 {
   AssertIsOnOwningThread();
 
-  IDBRequest* baseRequest = BackgroundRequestChildBase::GetDOMObject();
-  return static_cast<IDBOpenDBRequest*>(baseRequest);
+  return static_cast<IDBOpenDBRequest*>(mRequest.get());
 }
 
 bool
 BackgroundFactoryRequestChild::HandleResponse(nsresult aResponse)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NS_FAILED(aResponse));
   MOZ_ASSERT(NS_ERROR_GET_MODULE(aResponse) == NS_ERROR_MODULE_DOM_INDEXEDDB);
@@ -1353,18 +1342,16 @@ BackgroundFactoryRequestChild::HandleRes
 
 void
 BackgroundFactoryRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
   MaybeCollectGarbageOnIPCMessage();
 
-  NoteActorDestroyed();
-
   if (aWhy != Deletion) {
     IDBOpenDBRequest* openRequest = GetOpenDBRequest();
     if (openRequest) {
       openRequest->NoteComplete();
     }
   }
 }
 
@@ -2442,18 +2429,16 @@ BackgroundRequestChild::HandleResponse(u
 
 void
 BackgroundRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
   MaybeCollectGarbageOnIPCMessage();
 
-  NoteActorDestroyed();
-
   if (mTransaction) {
     mTransaction->AssertIsOnOwningThread();
 
     mTransaction->OnRequestFinished(/* aActorDestroyedNormally */
                                     aWhy == Deletion);
 #ifdef DEBUG
     mTransaction = nullptr;
 #endif
@@ -3056,11 +3041,72 @@ BackgroundCursorChild::CachedResponse::C
 }
 
 BackgroundCursorChild::CachedResponse::CachedResponse(CachedResponse&& aOther)
   : mKey(Move(aOther.mKey))
 {
   mCloneInfo = Move(aOther.mCloneInfo);
 }
 
+/*******************************************************************************
+ * BackgroundUtilsChild
+ ******************************************************************************/
+
+BackgroundUtilsChild::BackgroundUtilsChild(IndexedDatabaseManager* aManager)
+  : mManager(aManager)
+#ifdef DEBUG
+  , mOwningThread(NS_GetCurrentThread())
+#endif
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aManager);
+
+  MOZ_COUNT_CTOR(indexedDB::BackgroundUtilsChild);
+}
+
+BackgroundUtilsChild::~BackgroundUtilsChild()
+{
+  MOZ_COUNT_DTOR(indexedDB::BackgroundUtilsChild);
+}
+
+#ifdef DEBUG
+
+void
+BackgroundUtilsChild::AssertIsOnOwningThread() const
+{
+  MOZ_ASSERT(mOwningThread);
+
+  bool current;
+  MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
+  MOZ_ASSERT(current);
+}
+
+#endif // DEBUG
+
+void
+BackgroundUtilsChild::SendDeleteMeInternal()
+{
+  AssertIsOnOwningThread();
+
+  if (mManager) {
+    mManager->ClearBackgroundActor();
+    mManager = nullptr;
+
+    MOZ_ALWAYS_TRUE(PBackgroundIndexedDBUtilsChild::SendDeleteMe());
+  }
+}
+
+void
+BackgroundUtilsChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnOwningThread();
+
+  if (mManager) {
+    mManager->ClearBackgroundActor();
+#ifdef DEBUG
+    mManager = nullptr;
+#endif
+  }
+}
+
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
--- a/dom/indexedDB/ActorsChild.h
+++ b/dom/indexedDB/ActorsChild.h
@@ -15,16 +15,17 @@
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionChild.h"
+#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsChild.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
 class nsIEventTarget;
 struct nsID;
 struct PRThread;
 
@@ -39,16 +40,17 @@ namespace dom {
 namespace indexedDB {
 
 class IDBCursor;
 class IDBDatabase;
 class IDBFactory;
 class IDBMutableFile;
 class IDBOpenDBRequest;
 class IDBRequest;
+class IndexedDatabaseManager;
 class Key;
 class PermissionRequestChild;
 class PermissionRequestParent;
 class SerializedStructuredCloneReadInfo;
 
 class ThreadLocal
 {
   friend class nsAutoPtr<ThreadLocal>;
@@ -213,50 +215,30 @@ private:
 
 class BackgroundDatabaseChild;
 
 class BackgroundRequestChildBase
 {
 protected:
   RefPtr<IDBRequest> mRequest;
 
-private:
-  bool mActorDestroyed;
-
 public:
   void
   AssertIsOnOwningThread() const
 #ifdef DEBUG
   ;
 #else
   { }
 #endif
 
-  IDBRequest*
-  GetDOMObject() const
-  {
-    AssertIsOnOwningThread();
-    return mRequest;
-  }
-
-  bool
-  IsActorDestroyed() const
-  {
-    AssertIsOnOwningThread();
-    return mActorDestroyed;
-  }
-
 protected:
   explicit BackgroundRequestChildBase(IDBRequest* aRequest);
 
   virtual
   ~BackgroundRequestChildBase();
-
-  void
-  NoteActorDestroyed();
 };
 
 class BackgroundFactoryRequestChild final
   : public BackgroundRequestChildBase
   , public PBackgroundIDBFactoryRequestChild
 {
   typedef mozilla::dom::quota::PersistenceType PersistenceType;
 
@@ -824,13 +806,52 @@ private:
   // Force callers to use SendContinueInternal.
   bool
   SendContinue(const CursorRequestParams& aParams, const Key& aKey) = delete;
 
   bool
   SendDeleteMe() = delete;
 };
 
+class BackgroundUtilsChild final
+  : public PBackgroundIndexedDBUtilsChild
+{
+  friend class mozilla::ipc::BackgroundChildImpl;
+  friend class IndexedDatabaseManager;
+
+  IndexedDatabaseManager* mManager;
+
+#ifdef DEBUG
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+#endif
+
+public:
+  void
+  AssertIsOnOwningThread() const
+#ifdef DEBUG
+  ;
+#else
+  { }
+#endif
+
+private:
+  // Only created by IndexedDatabaseManager.
+  explicit BackgroundUtilsChild(IndexedDatabaseManager* aManager);
+
+  // Only destroyed by mozilla::ipc::BackgroundChildImpl.
+  ~BackgroundUtilsChild();
+
+  void
+  SendDeleteMeInternal();
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  bool
+  SendDeleteMe() = delete;
+};
+
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_indexeddb_actorschild_h__
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -17,17 +17,16 @@
 #include "js/StructuredClone.h"
 #include "js/Value.h"
 #include "jsapi.h"
 #include "KeyPath.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/AppProcessChecker.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/Endian.h"
-#include "mozilla/Hal.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/storage.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
@@ -39,16 +38,17 @@
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
+#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
 #include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestParent.h"
 #include "mozilla/dom/ipc/BlobParent.h"
 #include "mozilla/dom/quota/Client.h"
 #include "mozilla/dom/quota/FileStreams.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
@@ -63,22 +63,19 @@
 #include "nsEscape.h"
 #include "nsHashKeys.h"
 #include "nsNetUtil.h"
 #include "nsISimpleEnumerator.h"
 #include "nsIAppsService.h"
 #include "nsIEventTarget.h"
 #include "nsIFile.h"
 #include "nsIFileURL.h"
-#include "nsIIdleService.h"
 #include "nsIInputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsInterfaceHashtable.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
 #include "nsIOutputStream.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsISupportsPriority.h"
 #include "nsIThread.h"
 #include "nsITimer.h"
@@ -237,37 +234,27 @@ const char kPrefIndexedDBEnabled[] = "do
 const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
 
 #define IDB_PREFIX "indexedDB"
 
 #define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
 #define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
 #define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"
 
-const char kIdleServiceContractId[] = "@mozilla.org/widget/idleservice;1";
-
 #ifdef DEBUG
 
 const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGThreadSleepMS = 0;
 
 const int32_t kDEBUGTransactionThreadPriority =
   nsISupportsPriority::PRIORITY_NORMAL;
 const uint32_t kDEBUGTransactionThreadSleepMS = 0;
 
 #endif
 
-const bool kRunningXPCShellTests =
-#ifdef ENABLE_TESTS
-  !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")
-#else
-  false
-#endif
-  ;
-
 struct FreeDeleter
 {
   void
   operator()(void* aPtr) const
   {
     free(aPtr);
   }
 };
@@ -6139,37 +6126,16 @@ private:
   MaybeWaitForFileHandles();
 
   void
   CallCallback();
 
   NS_DECL_NSIRUNNABLE
 };
 
-class UnlockDirectoryRunnable final
-  : public nsRunnable
-{
-  RefPtr<DirectoryLock> mDirectoryLock;
-
-public:
-  explicit
-  UnlockDirectoryRunnable(already_AddRefed<DirectoryLock> aDirectoryLock)
-    : mDirectoryLock(Move(aDirectoryLock))
-  { }
-
-private:
-  ~UnlockDirectoryRunnable()
-  {
-    MOZ_ASSERT(!mDirectoryLock);
-  }
-
-  NS_IMETHOD
-  Run() override;
-};
-
 class Database final
   : public PBackgroundIDBDatabaseParent
 {
   friend class VersionChangeTransaction;
 
   class StartTransactionOp;
 
 private:
@@ -7129,40 +7095,47 @@ class FactoryOp
 public:
   struct MaybeBlockedDatabaseInfo;
 
 protected:
   enum class State
   {
     // Just created on the PBackground thread, dispatched to the main thread.
     // Next step is either SendingResults if permission is denied,
-    // PermissionChallenge if the permission is unknown, or DirectoryOpenPending
+    // PermissionChallenge if the permission is unknown, or FinishOpen
     // if permission is granted.
     Initial,
 
     // Sending a permission challenge message to the child on the PBackground
     // thread. Next step is PermissionRetry.
     PermissionChallenge,
 
     // Retrying permission check after a challenge on the main thread. Next step
-    // is either SendingResults if permission is denied or DirectoryOpenPending
+    // is either SendingResults if permission is denied or FinishOpen
     // if permission is granted.
     PermissionRetry,
 
-    // Waiting for directory open allowed on the main thread. The next step is
-    // either SendingResults if directory lock failed to acquire, or
-    // DirectoryWorkOpen if directory lock is acquired.
+    // Opening directory or initializing quota manager on the PBackground
+    // thread. Next step is either DirectoryOpenPending if quota manager is
+    // already initialized or QuotaManagerPending if quota manager needs to be
+    // initialized.
+    FinishOpen,
+
+    // Waiting for quota manager initialization to complete on the PBackground
+    // thread. Next step is either SendingResults if initialization failed or
+    // DirectoryOpenPending if initialization succeeded.
+    QuotaManagerPending,
+
+    // Waiting for directory open allowed on the PBackground thread. The next
+    // step is either SendingResults if directory lock failed to acquire, or
+    // DatabaseOpenPending if directory lock is acquired.
     DirectoryOpenPending,
 
-    // Checking if database open needs to wait on the PBackground thread. The
-    // next step is DatabaseOpenPending.
-    DirectoryWorkOpen,
-
-    // Waiting for database open allowed on the main thread. The next step is
-    // DatabaseWorkOpen.
+    // Waiting for database open allowed on the PBackground thread. The next
+    // step is DatabaseWorkOpen.
     DatabaseOpenPending,
 
     // Waiting to do/doing work on the QuotaManager IO thread. Its next step is
     // either BeginVersionChange if the requested version doesn't match the
     // existing database version or SendingResults if the versions match.
     DatabaseWorkOpen,
 
     // Starting a version change transaction or deleting a database on the
@@ -7321,19 +7294,25 @@ private:
   nsresult
   CheckPermission(ContentParent* aContentParent,
                   PermissionRequestBase::PermissionValue* aPermission);
 
   static bool
   CheckAtLeastOneAppHasPermission(ContentParent* aContentParent,
                                   const nsACString& aPermissionString);
 
-  nsresult
+  void
   FinishOpen();
 
+  nsresult
+  QuotaManagerOpen();
+
+  void
+  OpenDirectory();
+
   // Test whether this FactoryOp needs to wait for the given op.
   bool
   MustWaitFor(const FactoryOp& aExistingOp);
 };
 
 struct FactoryOp::MaybeBlockedDatabaseInfo final
 {
   RefPtr<Database> mDatabase;
@@ -7579,21 +7558,16 @@ private:
   {
     MOZ_ASSERT(aDeleteDatabaseOp);
     MOZ_ASSERT(!aDeleteDatabaseOp->mDatabaseDirectoryPath.IsEmpty());
   }
 
   ~VersionChangeOp()
   { }
 
-  // XXX This should be much simpler when the QuotaManager lives on the
-  //     PBackground thread.
-  nsresult
-  RunOnMainThread();
-
   nsresult
   RunOnIOThread();
 
   void
   RunOnOwningThread();
 
   nsresult
   DeleteFile(nsIFile* aDirectory,
@@ -7625,16 +7599,26 @@ protected:
     SendingResults,
 
     // All done.
     Completed
   };
 
   State mState;
 
+public:
+  void
+  RunImmediately()
+  {
+    MOZ_ASSERT(mState == State::Initial);
+
+    Unused << this->Run();
+  }
+
+protected:
   DatabaseOp(Database* aDatabase);
 
   virtual
   ~DatabaseOp()
   {
     MOZ_ASSERT_IF(OperationMayProceed(),
                   mState == State::Initial || mState == State::Completed);
   }
@@ -8500,16 +8484,96 @@ private:
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 
   virtual nsresult
   SendSuccessResult() override;
 };
 
+class Utils final
+  : public PBackgroundIndexedDBUtilsParent
+{
+#ifdef DEBUG
+  bool mActorDestroyed;
+#endif
+
+public:
+  Utils();
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Utils)
+
+private:
+  // Reference counted.
+  ~Utils();
+
+  // IPDL methods are only called by IPDL.
+  virtual void
+  ActorDestroy(ActorDestroyReason aWhy) override;
+
+  virtual bool
+  RecvDeleteMe() override;
+
+  virtual bool
+  RecvGetFileReferences(const PersistenceType& aPersistenceType,
+                        const nsCString& aOrigin,
+                        const nsString& aDatabaseName,
+                        const int64_t& aFileId,
+                        int32_t* aRefCnt,
+                        int32_t* aDBRefCnt,
+                        int32_t* aSliceRefCnt,
+                        bool* aResult) override;
+};
+
+class GetFileReferencesHelper final
+  : public nsRunnable
+{
+  PersistenceType mPersistenceType;
+  nsCString mOrigin;
+  nsString mDatabaseName;
+  int64_t mFileId;
+
+  mozilla::Mutex mMutex;
+  mozilla::CondVar mCondVar;
+  int32_t mMemRefCnt;
+  int32_t mDBRefCnt;
+  int32_t mSliceRefCnt;
+  bool mResult;
+  bool mWaiting;
+
+public:
+  GetFileReferencesHelper(PersistenceType aPersistenceType,
+                          const nsACString& aOrigin,
+                          const nsAString& aDatabaseName,
+                          int64_t aFileId)
+    : mPersistenceType(aPersistenceType)
+    , mOrigin(aOrigin)
+    , mDatabaseName(aDatabaseName)
+    , mFileId(aFileId)
+    , mMutex("GetFileReferencesHelper::mMutex")
+    , mCondVar(mMutex, "GetFileReferencesHelper::mCondVar")
+    , mMemRefCnt(-1)
+    , mDBRefCnt(-1)
+    , mSliceRefCnt(-1)
+    , mResult(false)
+    , mWaiting(true)
+  { }
+
+  nsresult
+  DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
+                                  int32_t* aDBRefCnt,
+                                  int32_t* aSliceRefCnt,
+                                  bool* aResult);
+
+private:
+  ~GetFileReferencesHelper() {}
+
+  NS_DECL_NSIRUNNABLE
+};
+
 class PermissionRequestHelper final
   : public PermissionRequestBase
   , public PIndexedDBPermissionRequestParent
 {
   bool mActorDestroyed;
 
 public:
   PermissionRequestHelper(Element* aOwnerElement,
@@ -8684,22 +8748,17 @@ private:
     return mFileInfo->Id();
   }
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(BlobImplStoredFile, BLOB_IMPL_STORED_FILE_IID)
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
-  , public nsIObserver
-{
-  // The number of seconds we will wait after receiving the idle-daily
-  // notification before beginning maintenance.
-  static const uint32_t kIdleObserverTimeSec = 1;
-
+{
   // The minimum amount of time that has passed since the last vacuum before we
   // will attempt to analyze the database for fragmentation.
   static const PRTime kMinVacuumAge =
     PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24 * 7;
 
   // If the percent of database pages that are not in contiguous order is higher
   // than this percentage we will attempt a vacuum.
   static const int32_t kPercentUnorderedThreshold = 30;
@@ -8711,99 +8770,92 @@ class QuotaClient final
   // The number of freelist pages beyond which we will favor an incremental
   // vacuum over a full vacuum.
   static const int32_t kMaxFreelistThreshold = 5;
 
   // If the percent of unused file bytes in the database exceeds this percentage
   // then we will attempt a full vacuum.
   static const int32_t kPercentUnusedThreshold = 20;
 
-  class AbortOperationsRunnable;
   class AutoProgressHandler;
   class GetDirectoryLockListener;
   struct MaintenanceInfoBase;
   struct MultipleMaintenanceInfo;
-  class ShutdownWorkThreadsRunnable;
   struct SingleMaintenanceInfo;
 
   typedef nsClassHashtable<nsCStringHashKey, MultipleMaintenanceInfo>
           MaintenanceInfoHashtable;
 
   enum class MaintenanceAction
   {
     Nothing = 0,
     IncrementalVacuum,
     FullVacuum
   };
 
   static QuotaClient* sInstance;
 
   nsCOMPtr<nsIEventTarget> mBackgroundThread;
-  RefPtr<ShutdownWorkThreadsRunnable> mShutdownRunnable;
   RefPtr<nsThreadPool> mMaintenanceThreadPool;
   PRTime mMaintenanceStartTime;
   Atomic<uint32_t> mMaintenanceRunId;
   UniquePtr<MaintenanceInfoHashtable> mMaintenanceInfoHashtable;
   bool mShutdownRequested;
-  bool mIdleObserverRegistered;
 
 public:
   QuotaClient();
 
   static QuotaClient*
   GetInstance()
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
 
     return sInstance;
   }
 
   static bool
-  IsShuttingDownOnMainThread()
-  {
-    MOZ_ASSERT(NS_IsMainThread());
+  IsShuttingDownOnBackgroundThread()
+  {
+    AssertIsOnBackgroundThread();
 
     if (sInstance) {
       return sInstance->IsShuttingDown();
     }
 
     return QuotaManager::IsShuttingDown();
   }
 
   static bool
-  IsShuttingDownOnNonMainThread()
-  {
-    MOZ_ASSERT(!NS_IsMainThread());
+  IsShuttingDownOnNonBackgroundThread()
+  {
+    MOZ_ASSERT(!IsOnBackgroundThread());
 
     return QuotaManager::IsShuttingDown();
   }
 
   bool
   IsShuttingDown() const
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
 
     return mShutdownRequested;
   }
 
   bool
   IdleMaintenanceMustEnd(uint32_t aRunId) const
   {
     if (mMaintenanceRunId != aRunId) {
       MOZ_ASSERT(mMaintenanceRunId > aRunId);
       return true;
     }
 
     return false;
   }
 
-  void
-  NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
-
-  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override)
 
   virtual mozilla::dom::quota::Client::Type
   GetType() override;
 
   virtual nsresult
   InitOrigin(PersistenceType aPersistenceType,
              const nsACString& aGroup,
              const nsACString& aOrigin,
@@ -8825,17 +8877,20 @@ public:
 
   virtual void
   AbortOperations(const nsACString& aOrigin) override;
 
   virtual void
   AbortOperationsForProcess(ContentParentId aContentParentId) override;
 
   virtual void
-  PerformIdleMaintenance() override;
+  StartIdleMaintenance() override;
+
+  virtual void
+  StopIdleMaintenance() override;
 
   virtual void
   ShutdownWorkThreads() override;
 
 private:
   ~QuotaClient();
 
   nsresult
@@ -8844,23 +8899,20 @@ private:
                nsIFile** aDirectory);
 
   nsresult
   GetUsageForDirectoryInternal(nsIFile* aDirectory,
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 
   void
-  RemoveIdleObserver();
-
-  void
-  StartIdleMaintenance();
-
-  void
-  StopIdleMaintenance();
+  CreateManager();
+
+  void
+  StartIdleMaintenanceInternal();
 
   // Runs on mMaintenanceThreadPool. Once it finds databases it will queue a
   // runnable that calls GetDirectoryLockForIdleMaintenance.
   void
   FindDatabasesForIdleMaintenance(uint32_t aRunId);
 
   // Runs on the main thread. Once QuotaManager has given a lock it will call
   // ScheduleIdleMaintenance.
@@ -8910,18 +8962,16 @@ private:
              nsIFile* aDatabaseFile);
 
   // Runs on the main thread. Checks to see if all database maintenance has
   // finished and then releases the directory lock.
   void
   MaybeReleaseDirectoryLockForIdleMaintenance(
                                      const nsACString& aKey,
                                      const nsAString& aDatabasePath);
-
-  NS_DECL_NSIOBSERVER
 };
 
 class MOZ_STACK_CLASS QuotaClient::AutoProgressHandler final
   : public mozIStorageProgressHandler
 {
   QuotaClient* mQuotaClient;
   mozIStorageConnection* mConnection;
   uint32_t mRunId;
@@ -9079,110 +9129,42 @@ struct QuotaClient::MultipleMaintenanceI
       MOZ_ASSERT(!databasePath.IsEmpty());
     }
 #endif
   }
 
   MultipleMaintenanceInfo(const MultipleMaintenanceInfo& aOther) = delete;
 };
 
-class QuotaClient::ShutdownWorkThreadsRunnable final
-  : public nsRunnable
-{
-  RefPtr<QuotaClient> mQuotaClient;
-
-public:
-  explicit ShutdownWorkThreadsRunnable(QuotaClient* aQuotaClient)
-    : mQuotaClient(aQuotaClient)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(aQuotaClient);
-    MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
-    MOZ_ASSERT(aQuotaClient->mShutdownRequested);
-  }
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-private:
-  ~ShutdownWorkThreadsRunnable()
-  {
-    MOZ_ASSERT(!mQuotaClient);
-  }
-
-  NS_DECL_NSIRUNNABLE
-};
-
-class QuotaClient::AbortOperationsRunnable final
-  : public nsRunnable
-{
-  const ContentParentId mContentParentId;
-  const nsCString mOrigin;
-
-  nsTArray<RefPtr<Database>> mDatabases;
-
-public:
-  explicit AbortOperationsRunnable(const nsACString& aOrigin)
-    : mOrigin(aOrigin)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(!mOrigin.IsEmpty());
-  }
-
-  explicit AbortOperationsRunnable(ContentParentId aContentParentId)
-    : mContentParentId(aContentParentId)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mOrigin.IsEmpty());
-  }
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-private:
-  ~AbortOperationsRunnable()
-  { }
-
-  static PLDHashOperator
-  MatchOrigin(const nsACString& aKey,
-              DatabaseActorInfo* aValue,
-              void* aClosure);
-
-  static PLDHashOperator
-  MatchContentParentId(const nsACString& aKey,
-                       DatabaseActorInfo* aValue,
-                       void* aClosure);
-
-  NS_DECL_NSIRUNNABLE
-};
-
 class QuotaClient::GetDirectoryLockListener final
   : public OpenDirectoryListener
 {
   RefPtr<QuotaClient> mQuotaClient;
   const uint32_t mRunId;
   const nsCString mKey;
 
 public:
   GetDirectoryLockListener(QuotaClient* aQuotaClient,
                            uint32_t aRunId,
                            const nsACString& aKey)
     : mQuotaClient(aQuotaClient)
     , mRunId(aRunId)
     , mKey(aKey)
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
     MOZ_ASSERT(aQuotaClient);
     MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
   }
 
   NS_INLINE_DECL_REFCOUNTING(QuotaClient::GetDirectoryLockListener, override)
 
 private:
   ~GetDirectoryLockListener()
   {
-    MOZ_ASSERT(NS_IsMainThread());
+    AssertIsOnBackgroundThread();
   }
 
   // OpenDirectoryListener overrides.
   virtual void
   DirectoryLockAcquired(DirectoryLock* aLock) override;
 
   virtual void
   DirectoryLockFailed() override;
@@ -9526,17 +9508,17 @@ TelemetryIdForFile(nsIFile* aFile)
  * Exported functions
  ******************************************************************************/
 
 PBackgroundIDBFactoryParent*
 AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo)
 {
   AssertIsOnBackgroundThread();
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread())) {
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
 
   if (NS_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) ||
       NS_WARN_IF(!aLoggingInfo.nextVersionChangeTransactionSerialNumber()) ||
       NS_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) {
     ASSERT_UNLESS_FUZZING();
     return nullptr;
@@ -9549,31 +9531,51 @@ AllocPBackgroundIDBFactoryParent(const L
 }
 
 bool
 RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor,
                                      const LoggingInfo& /* aLoggingInfo */)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 
   return true;
 }
 
 bool
 DeallocPBackgroundIDBFactoryParent(PBackgroundIDBFactoryParent* aActor)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   RefPtr<Factory> actor = dont_AddRef(static_cast<Factory*>(aActor));
   return true;
 }
 
+PBackgroundIndexedDBUtilsParent*
+AllocPBackgroundIndexedDBUtilsParent()
+{
+  AssertIsOnBackgroundThread();
+
+  RefPtr<Utils> actor = new Utils();
+
+  return actor.forget().take();
+}
+
+bool
+DeallocPBackgroundIndexedDBUtilsParent(PBackgroundIndexedDBUtilsParent* aActor)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aActor);
+
+  RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
+  return true;
+}
+
 PIndexedDBPermissionRequestParent*
 AllocPIndexedDBPermissionRequestParent(Element* aOwnerElement,
                                        nsIPrincipal* aPrincipal)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RefPtr<PermissionRequestHelper> actor =
     new PermissionRequestHelper(aOwnerElement, aPrincipal);
@@ -9613,25 +9615,37 @@ DeallocPIndexedDBPermissionRequestParent
   RefPtr<PermissionRequestHelper> actor =
     dont_AddRef(static_cast<PermissionRequestHelper*>(aActor));
   return true;
 }
 
 already_AddRefed<mozilla::dom::quota::Client>
 CreateQuotaClient()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
 
   RefPtr<QuotaClient> client = new QuotaClient();
   return client.forget();
 }
 
 FileHandleThreadPool*
 GetFileHandleThreadPool()
 {
+  AssertIsOnBackgroundThread();
+
+  if (!gFileHandleThreadPool) {
+    RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+      FileHandleThreadPool::Create();
+    if (NS_WARN_IF(!fileHandleThreadPool)) {
+      return nullptr;
+    }
+
+    gFileHandleThreadPool = fileHandleThreadPool;
+  }
+
   return gFileHandleThreadPool;
 }
 
 /*******************************************************************************
  * DatabaseConnection implementation
  ******************************************************************************/
 
 DatabaseConnection::DatabaseConnection(
@@ -12637,30 +12651,30 @@ DatabaseLoggingInfo::~DatabaseLoggingInf
 
 uint64_t Factory::sFactoryInstanceCount = 0;
 
 Factory::Factory(already_AddRefed<DatabaseLoggingInfo> aLoggingInfo)
   : mLoggingInfo(Move(aLoggingInfo))
   , mActorDestroyed(false)
 {
   AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 }
 
 Factory::~Factory()
 {
   MOZ_ASSERT(mActorDestroyed);
 }
 
 // static
 already_AddRefed<Factory>
 Factory::Create(const LoggingInfo& aLoggingInfo)
 {
   AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 
   // If this is the first instance then we need to do some initialization.
   if (!sFactoryInstanceCount) {
     MOZ_ASSERT(!gFactoryOps);
     gFactoryOps = new FactoryOpArray();
 
     MOZ_ASSERT(!gLiveDatabaseHashtable);
     gLiveDatabaseHashtable = new DatabaseActorHashtable();
@@ -12789,17 +12803,17 @@ Factory::RecvIncrementLoggingRequestSeri
 
 PBackgroundIDBFactoryRequestParent*
 Factory::AllocPBackgroundIDBFactoryRequestParent(
                                             const FactoryRequestParams& aParams)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread())) {
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
     return nullptr;
   }
 
   const CommonFactoryRequestParams* commonParams;
 
   switch (aParams.type()) {
     case FactoryRequestParams::TOpenDatabaseRequestParams: {
       const OpenDatabaseRequestParams& params =
@@ -12860,17 +12874,17 @@ Factory::AllocPBackgroundIDBFactoryReque
 bool
 Factory::RecvPBackgroundIDBFactoryRequestConstructor(
                                      PBackgroundIDBFactoryRequestParent* aActor,
                                      const FactoryRequestParams& aParams)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 
   auto* op = static_cast<FactoryOp*>(aActor);
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(op)));
   return true;
 }
 
 bool
@@ -12998,27 +13012,16 @@ WaitForTransactionsHelper::Run()
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-UnlockDirectoryRunnable::Run()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mDirectoryLock);
-
-  mDirectoryLock = nullptr;
-
-  return NS_OK;
-}
-
 /*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database(Factory* aFactory,
                    const PrincipalInfo& aPrincipalInfo,
                    const OptionalContentId& aOptionalContentParentId,
                    const nsACString& aGroup,
@@ -13330,22 +13333,17 @@ Database::MaybeCloseConnection()
 void
 Database::ConnectionClosedCallback()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(!mTransactions.Count());
   MOZ_ASSERT(!mActiveMutableFileCount);
 
-  if (mDirectoryLock) {
-    RefPtr<UnlockDirectoryRunnable> runnable =
-      new UnlockDirectoryRunnable(mDirectoryLock.forget());
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
-  }
+  mDirectoryLock = nullptr;
 
   CleanupMetadata();
 }
 
 void
 Database::CleanupMetadata()
 {
   AssertIsOnBackgroundThread();
@@ -13499,17 +13497,18 @@ Database::RecvPBackgroundIDBDatabaseRequ
                                     const DatabaseRequestParams& aParams)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
   MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
 
   auto* op = static_cast<DatabaseOp*>(aActor);
 
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(op)));
+  op->RunImmediately();
+
   return true;
 }
 
 bool
 Database::DeallocPBackgroundIDBDatabaseRequestParent(
                                     PBackgroundIDBDatabaseRequestParent* aActor)
 {
   AssertIsOnBackgroundThread();
@@ -16282,17 +16281,17 @@ FileManager::GetUsage(nsIFile* aDirector
     }
 
     int64_t fileSize;
     rv = file->GetFileSize(&fileSize);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    quota::IncrementUsage(&usage, uint64_t(fileSize));
+    UsageInfo::IncrementUsage(&usage, uint64_t(fileSize));
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   *aUsage = usage;
   return NS_OK;
@@ -16311,58 +16310,44 @@ NS_IMPL_ISUPPORTS_INHERITED(BlobImplStor
  ******************************************************************************/
 
 QuotaClient* QuotaClient::sInstance = nullptr;
 
 QuotaClient::QuotaClient()
   : mMaintenanceStartTime(0)
   , mMaintenanceRunId(0)
   , mShutdownRequested(false)
-  , mIdleObserverRegistered(false)
-{
-  MOZ_ASSERT(NS_IsMainThread());
+{
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
   MOZ_ASSERT(!gTelemetryIdMutex);
 
   // Always create this so that later access to gTelemetryIdHashtable can be
   // properly synchronized.
   gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex");
 
   sInstance = this;
 }
 
 QuotaClient::~QuotaClient()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
   MOZ_ASSERT(gTelemetryIdMutex);
   MOZ_ASSERT(!mMaintenanceThreadPool);
   MOZ_ASSERT_IF(mMaintenanceInfoHashtable, !mMaintenanceInfoHashtable->Count());
-  MOZ_ASSERT(!mIdleObserverRegistered);
 
   // No one else should be able to touch gTelemetryIdHashtable now that the
   // QuotaClient has gone away.
   gTelemetryIdHashtable = nullptr;
   gTelemetryIdMutex = nullptr;
 
   sInstance = nullptr;
 }
 
-void
-QuotaClient::NoteBackgroundThread(nsIEventTarget* aBackgroundThread)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aBackgroundThread);
-  MOZ_ASSERT(!mShutdownRequested);
-
-  mBackgroundThread = aBackgroundThread;
-}
-
-NS_IMPL_ISUPPORTS(QuotaClient, nsIObserver)
-
 mozilla::dom::quota::Client::Type
 QuotaClient::GetType()
 {
   return QuotaClient::IDB;
 }
 
 struct FileManagerInitInfo
 {
@@ -16703,128 +16688,122 @@ QuotaClient::ReleaseIOThreadObjects()
   if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
     mgr->InvalidateAllFileManagers();
   }
 }
 
 void
 QuotaClient::AbortOperations(const nsACString& aOrigin)
 {
-  if (mBackgroundThread) {
-    RefPtr<AbortOperationsRunnable> runnable =
-      new AbortOperationsRunnable(aOrigin);
-
-    if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
-      // This can happen if the thread has shut down already.
-      return;
-    }
-  }
+  AssertIsOnBackgroundThread();
+
+  if (!gLiveDatabaseHashtable) {
+    return;
+  }
+
+  nsTArray<RefPtr<Database>> databases;
+
+  for (auto iter = gLiveDatabaseHashtable->ConstIter();
+       !iter.Done(); iter.Next()) {
+    for (Database* database : iter.Data()->mLiveDatabases) {
+      if (aOrigin.IsVoid() || database->Origin() == aOrigin) {
+        databases.AppendElement(database);
+      }
+    }
+  }
+
+  for (Database* database : databases) {
+    database->Invalidate();
+  }
+
+  databases.Clear();
 }
 
 void
 QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId)
 {
-  if (mBackgroundThread) {
-    RefPtr<AbortOperationsRunnable> runnable =
-      new AbortOperationsRunnable(aContentParentId);
-
-    if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
-      // This can happen if the thread has shut down already.
-      return;
-    }
-  }
-}
-
-void
-QuotaClient::PerformIdleMaintenance()
-{
-  using namespace mozilla::hal;
-
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
+
+  if (!gLiveDatabaseHashtable) {
+    return;
+  }
+
+  nsTArray<RefPtr<Database>> databases;
+
+  for (auto iter = gLiveDatabaseHashtable->ConstIter();
+       !iter.Done(); iter.Next()) {
+    for (Database* database : iter.Data()->mLiveDatabases) {
+      if (database->IsOwnedByProcess(aContentParentId)) {
+        databases.AppendElement(database);
+      }
+    }
+  }
+
+  for (Database* database : databases) {
+    database->Invalidate();
+  }
+
+  databases.Clear();
+}
+
+void
+QuotaClient::StartIdleMaintenance()
+{
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mShutdownRequested);
 
-  // If we're running on battery power then skip all idle maintenance since we
-  // would otherwise be doing lots of disk I/O.
-  BatteryInformation batteryInfo;
-
-#ifdef MOZ_WIDGET_ANDROID
-  // Android XPCShell doesn't load the AndroidBridge that is needed to make
-  // GetCurrentBatteryInformation work...
-  if (!kRunningXPCShellTests)
-#endif
-  {
-    GetCurrentBatteryInformation(&batteryInfo);
-  }
-
-  // If we're running XPCShell because we always want to be able to test this
-  // code so pretend that we're always charging.
-  if (kRunningXPCShellTests) {
-    batteryInfo.level() = 100;
-    batteryInfo.charging() = true;
-  }
-
-  if (NS_WARN_IF(!batteryInfo.charging())) {
-    return;
-  }
-
-  // Make sure that the IndexedDatabaseManager is running so that we can check
-  // for low disk space mode.
-  IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
-  if (NS_WARN_IF(!mgr)) {
-    return;
-  }
-
-  if (kRunningXPCShellTests) {
-    // We don't want user activity to impact this code if we're running tests.
-    Unused << Observe(nullptr, OBSERVER_TOPIC_IDLE, nullptr);
-  } else if (!mIdleObserverRegistered) {
-    nsCOMPtr<nsIIdleService> idleService =
-      do_GetService(kIdleServiceContractId);
-    MOZ_ASSERT(idleService);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      idleService->AddIdleObserver(this, kIdleObserverTimeSec)));
-
-    mIdleObserverRegistered = true;
-  }
+  mBackgroundThread = do_GetCurrentThread();
+
+  if (!IndexedDatabaseManager::Get()) {
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethod(this, &QuotaClient::CreateManager);
+    MOZ_ASSERT(runnable);
+
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+    return;
+  }
+
+  StartIdleMaintenanceInternal();
+}
+
+void
+QuotaClient::StopIdleMaintenance()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mShutdownRequested);
+
+  mMaintenanceRunId++;
 }
 
 void
 QuotaClient::ShutdownWorkThreads()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mShutdownRunnable);
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mShutdownRequested);
 
-  StopIdleMaintenance();
-
   mShutdownRequested = true;
 
-  if (mBackgroundThread) {
-    RefPtr<ShutdownWorkThreadsRunnable> runnable =
-      new ShutdownWorkThreadsRunnable(this);
-
-    if (NS_SUCCEEDED(mBackgroundThread->Dispatch(runnable,
-                                                 NS_DISPATCH_NORMAL))) {
-      mShutdownRunnable = Move(runnable);
-    }
-  }
-
   if (mMaintenanceThreadPool) {
     mMaintenanceThreadPool->Shutdown();
     mMaintenanceThreadPool = nullptr;
   }
 
-  if (mShutdownRunnable) {
-    nsIThread* currentThread = NS_GetCurrentThread();
-    MOZ_ASSERT(currentThread);
-
-    while (mShutdownRunnable) {
-      MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
-    }
+  RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+  if (connectionPool) {
+    connectionPool->Shutdown();
+
+    gConnectionPool = nullptr;
+  }
+
+  RefPtr<FileHandleThreadPool> fileHandleThreadPool =
+    gFileHandleThreadPool.get();
+  if (fileHandleThreadPool) {
+    fileHandleThreadPool->Shutdown();
+
+    gFileHandleThreadPool = nullptr;
   }
 }
 
 nsresult
 QuotaClient::GetDirectory(PersistenceType aPersistenceType,
                           const nsACString& aOrigin, nsIFile** aDirectory)
 {
   QuotaManager* quotaManager = QuotaManager::Get();
@@ -16948,36 +16927,39 @@ QuotaClient::GetUsageForDirectoryInterna
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
 void
-QuotaClient::RemoveIdleObserver()
+QuotaClient::CreateManager()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mIdleObserverRegistered) {
-    nsCOMPtr<nsIIdleService> idleService =
-      do_GetService(kIdleServiceContractId);
-    MOZ_ASSERT(idleService);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-      idleService->RemoveIdleObserver(this, kIdleObserverTimeSec)));
-
-    mIdleObserverRegistered = false;
-  }
-}
-
-void
-QuotaClient::StartIdleMaintenance()
-{
-  MOZ_ASSERT(NS_IsMainThread());
+  // Make sure that the IndexedDatabaseManager is running so that we can check
+  // for low disk space mode.
+  IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
+  if (NS_WARN_IF(!mgr)) {
+    return;
+  }
+
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &QuotaClient::StartIdleMaintenanceInternal);
+  MOZ_ASSERT(runnable);
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mBackgroundThread->Dispatch(runnable,
+                                                           NS_DISPATCH_NORMAL)));
+}
+
+void
+QuotaClient::StartIdleMaintenanceInternal()
+{
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mShutdownRequested);
 
   if (!mMaintenanceThreadPool) {
     RefPtr<nsThreadPool> threadPool = new nsThreadPool();
 
     // PR_GetNumberOfProcessors() can return -1 on error, so make sure we
     // don't set some huge number here. We add 2 in case some threads block on
     // the disk I/O.
@@ -17016,27 +16998,16 @@ QuotaClient::StartIdleMaintenance()
       mMaintenanceRunId);
   MOZ_ASSERT(runnable);
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
     mMaintenanceThreadPool->Dispatch(runnable, NS_DISPATCH_NORMAL)));
 }
 
 void
-QuotaClient::StopIdleMaintenance()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mShutdownRequested);
-
-  RemoveIdleObserver();
-
-  mMaintenanceRunId++;
-}
-
-void
 QuotaClient::FindDatabasesForIdleMaintenance(uint32_t aRunId)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(mMaintenanceThreadPool);
 
   // The storage directory is structured like this:
   //
@@ -17242,28 +17213,29 @@ QuotaClient::FindDatabasesForIdleMainten
             aRunId,
             MultipleMaintenanceInfo(group,
                                     origin,
                                     persistenceType,
                                     isApp,
                                     Move(databasePaths)));
         MOZ_ASSERT(runnable);
 
-        MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+        MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+          mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
       }
     }
   }
 }
 
 void
 QuotaClient::GetDirectoryLockForIdleMaintenance(
                                      uint32_t aRunId,
                                      MultipleMaintenanceInfo&& aMaintenanceInfo)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
 
   if (IdleMaintenanceMustEnd(aRunId)) {
     return;
   }
 
   MOZ_ASSERT(mMaintenanceInfoHashtable);
 
   nsAutoCString key;
@@ -17279,31 +17251,32 @@ QuotaClient::GetDirectoryLockForIdleMain
   mMaintenanceInfoHashtable->Put(key, maintenanceInfo);
 
   RefPtr<GetDirectoryLockListener> listener =
     new GetDirectoryLockListener(this, aRunId, key);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
+  // FIXME: This will update origin access time!
   quotaManager->OpenDirectory(maintenanceInfo->mPersistenceType,
                               maintenanceInfo->mGroup,
                               maintenanceInfo->mOrigin,
                               maintenanceInfo->mIsApp,
                               Client::IDB,
                               /* aExclusive */ false,
                               listener);
 }
 
 void
 QuotaClient::ScheduleIdleMaintenance(uint32_t aRunId,
                                      const nsACString& aKey,
                                      const MultipleMaintenanceInfo& aMaintenanceInfo)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(!aKey.IsEmpty());
 
   MOZ_ASSERT(mMaintenanceThreadPool);
 
   for (const nsString& databasePath : aMaintenanceInfo.mDatabasePaths) {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethodWithArgs<uint32_t,
                                    nsCString,
@@ -17342,17 +17315,18 @@ QuotaClient::PerformIdleMaintenanceOnDat
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethodWithArgs<nsCString, nsString>(
       this,
       &QuotaClient::MaybeReleaseDirectoryLockForIdleMaintenance,
       aKey,
       aMaintenanceInfo.mDatabasePath);
   MOZ_ASSERT(runnable);
 
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
 }
 
 void
 QuotaClient::PerformIdleMaintenanceOnDatabaseInternal(
                                   uint32_t aRunId,
                                   const SingleMaintenanceInfo& aMaintenanceInfo)
 {
   MOZ_ASSERT(!NS_IsMainThread());
@@ -17597,17 +17571,17 @@ QuotaClient::DetermineMaintenanceAction(
   // track the values needed for the heuristics below.
   if (schemaVersion < MakeSchemaVersion(18, 0)) {
     *aMaintenanceAction = MaintenanceAction::Nothing;
     return NS_OK;
   }
 
   bool lowDiskSpace = IndexedDatabaseManager::InLowDiskSpaceMode();
 
-  if (kRunningXPCShellTests) {
+  if (QuotaManager::kRunningXPCShellTests) {
     // If we're running XPCShell then we want to test both the low disk space
     // and normal disk space code paths so pick semi-randomly based on the
     // current time.
     lowDiskSpace = ((PR_Now() / PR_USEC_PER_MSEC) % 2) == 0;
   }
 
   // If we're low on disk space then the best we can hope for is that an
   // incremental vacuum might free some space. That is a journaled operation so
@@ -17858,17 +17832,17 @@ QuotaClient::FullVacuum(mozIStorageConne
   }
 }
 
 void
 QuotaClient::MaybeReleaseDirectoryLockForIdleMaintenance(
                                                  const nsACString& aKey,
                                                  const nsAString& aDatabasePath)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(!aKey.IsEmpty());
   MOZ_ASSERT(!aDatabasePath.IsEmpty());
   MOZ_ASSERT(mMaintenanceInfoHashtable);
 
   MultipleMaintenanceInfo* maintenanceInfo;
   MOZ_ALWAYS_TRUE(mMaintenanceInfoHashtable->Get(aKey, &maintenanceInfo));
   MOZ_ASSERT(maintenanceInfo);
 
@@ -17878,37 +17852,16 @@ QuotaClient::MaybeReleaseDirectoryLockFo
     // That's it!
     maintenanceInfo->mDirectoryLock = nullptr;
 
     // This will delete |maintenanceInfo|.
     mMaintenanceInfoHashtable->Remove(aKey);
   }
 }
 
-NS_IMETHODIMP
-QuotaClient::Observe(nsISupports* aSubject,
-                     const char* aTopic,
-                     const char16_t* aData)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (!strcmp(aTopic, OBSERVER_TOPIC_IDLE)) {
-    StartIdleMaintenance();
-    return NS_OK;
-  }
-
-  if (!strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) {
-    StopIdleMaintenance();
-    return NS_OK;
-  }
-
-  MOZ_ASSERT_UNREACHABLE("Should never get here!");
-  return NS_OK;
-}
-
 nsresult
 QuotaClient::
 AutoProgressHandler::Register(mozIStorageConnection* aConnection)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(aConnection);
 
@@ -17977,133 +17930,21 @@ AutoProgressHandler::OnProgress(mozIStor
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(mConnection == aConnection);
   MOZ_ASSERT(_retval);
 
   *_retval = mQuotaClient->IdleMaintenanceMustEnd(mRunId);
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownWorkThreadsRunnable,
-                             nsRunnable)
-
-NS_IMETHODIMP
-QuotaClient::
-ShutdownWorkThreadsRunnable::Run()
-{
-  if (NS_IsMainThread()) {
-    MOZ_ASSERT(QuotaClient::GetInstance() == mQuotaClient);
-    MOZ_ASSERT(mQuotaClient->mShutdownRunnable == this);
-
-    mQuotaClient->mShutdownRunnable = nullptr;
-    mQuotaClient = nullptr;
-
-    return NS_OK;
-  }
-
-  AssertIsOnBackgroundThread();
-
-  RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
-  if (connectionPool) {
-    connectionPool->Shutdown();
-
-    gConnectionPool = nullptr;
-  }
-
-  RefPtr<FileHandleThreadPool> fileHandleThreadPool =
-    gFileHandleThreadPool.get();
-  if (fileHandleThreadPool) {
-    fileHandleThreadPool->Shutdown();
-
-    gFileHandleThreadPool = nullptr;
-  }
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
-
-  return NS_OK;
-}
-
-// static
-PLDHashOperator
-QuotaClient::
-AbortOperationsRunnable::MatchOrigin(const nsACString& aKey,
-                                     DatabaseActorInfo* aValue,
-                                     void* aClosure)
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!aKey.IsEmpty());
-  MOZ_ASSERT(aValue);
-  MOZ_ASSERT(aClosure);
-
-  auto* closure = static_cast<AbortOperationsRunnable*>(aClosure);
-
-  for (Database* database : aValue->mLiveDatabases) {
-    if (closure->mOrigin.IsVoid() || closure->mOrigin == database->Origin()) {
-      closure->mDatabases.AppendElement(database);
-    }
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-// static
-PLDHashOperator
-QuotaClient::
-AbortOperationsRunnable::MatchContentParentId(const nsACString& aKey,
-                                              DatabaseActorInfo* aValue,
-                                              void* aClosure)
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(!aKey.IsEmpty());
-  MOZ_ASSERT(aValue);
-  MOZ_ASSERT(aClosure);
-
-  auto* closure = static_cast<AbortOperationsRunnable*>(aClosure);
-
-  for (Database* database : aValue->mLiveDatabases) {
-    if (database->IsOwnedByProcess(closure->mContentParentId)) {
-      closure->mDatabases.AppendElement(database);
-    }
-  }
-
-  return PL_DHASH_NEXT;
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::AbortOperationsRunnable, nsRunnable)
-
-NS_IMETHODIMP
-QuotaClient::
-AbortOperationsRunnable::Run()
-{
-  AssertIsOnBackgroundThread();
-
-  if (!gLiveDatabaseHashtable) {
-    return NS_OK;
-  }
-
-  if (mOrigin.IsEmpty()) {
-    gLiveDatabaseHashtable->EnumerateRead(MatchContentParentId, this);
-  } else {
-    gLiveDatabaseHashtable->EnumerateRead(MatchOrigin, this);
-  }
-
-  for (Database* database : mDatabases) {
-    database->Invalidate();
-  }
-
-  mDatabases.Clear();
-
-  return NS_OK;
-}
-
 void
 QuotaClient::
 GetDirectoryLockListener::DirectoryLockAcquired(DirectoryLock* aLock)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
 
   MultipleMaintenanceInfo* maintenanceInfo;
   MOZ_ALWAYS_TRUE(
     mQuotaClient->mMaintenanceInfoHashtable->Get(mKey, &maintenanceInfo));
   MOZ_ASSERT(maintenanceInfo);
   MOZ_ASSERT(!maintenanceInfo->mDirectoryLock);
 
   if (mQuotaClient->IdleMaintenanceMustEnd(mRunId)) {
@@ -18119,17 +17960,17 @@ GetDirectoryLockListener::DirectoryLockA
 
   mQuotaClient->ScheduleIdleMaintenance(mRunId, mKey, *maintenanceInfo);
 }
 
 void
 QuotaClient::
 GetDirectoryLockListener::DirectoryLockFailed()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
 
   DebugOnly<MultipleMaintenanceInfo*> maintenanceInfo;
   MOZ_ASSERT(
     mQuotaClient->mMaintenanceInfoHashtable->Get(mKey, &maintenanceInfo));
   MOZ_ASSERT(maintenanceInfo);
   MOZ_ASSERT(!maintenanceInfo->mDirectoryLock);
 
   mQuotaClient->mMaintenanceInfoHashtable->Remove(mKey);
@@ -19325,30 +19166,30 @@ FactoryOp::FactoryOp(Factory* aFactory,
   , mEnforcingQuota(true)
   , mDeleting(aDeleting)
   , mBlockedDatabaseOpen(false)
   , mChromeWriteAccessAllowed(false)
   , mFileHandleDisabled(false)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFactory);
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
 }
 
 nsresult
 FactoryOp::Open()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == State::Initial);
 
   // Swap this to the stack now to ensure that we release it on this thread.
   RefPtr<ContentParent> contentParent;
   mContentParent.swap(contentParent);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   PermissionRequestBase::PermissionValue permission;
   nsresult rv = CheckPermission(contentParent, &permission);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -19360,40 +19201,31 @@ FactoryOp::Open()
              permission == PermissionRequestBase::kPermissionPrompt);
 
   if (permission == PermissionRequestBase::kPermissionDenied) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   {
     // These services have to be started on the main thread currently.
-    if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) {
+
+    IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
+    if (NS_WARN_IF(!mgr)) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
 
+    mgr->NoteBackgroundThread(mOwningThread);
+
     nsCOMPtr<mozIStorageService> ss;
     if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
       IDB_REPORT_INTERNAL_ERR();
       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
     }
-
-    if (NS_WARN_IF(!QuotaManager::GetOrCreate())) {
-      IDB_REPORT_INTERNAL_ERR();
-      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-    }
-  }
-
-  QuotaClient* quotaClient = QuotaClient::GetInstance();
-  if (NS_WARN_IF(!quotaClient)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  quotaClient->NoteBackgroundThread(mOwningThread);
+  }
 
   const DatabaseMetadata& metadata = mCommonParams.metadata();
 
   QuotaManager::GetStorageId(metadata.persistenceType(),
                              mOrigin,
                              Client::IDB,
                              mDatabaseId);
 
@@ -19404,20 +19236,19 @@ FactoryOp::Open()
     mState = State::PermissionChallenge;
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
                                                          NS_DISPATCH_NORMAL)));
     return NS_OK;
   }
 
   MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
 
-  rv = FinishOpen();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+  mState = State::FinishOpen;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
 
   return NS_OK;
 }
 
 nsresult
 FactoryOp::ChallengePermission()
 {
   AssertIsOnOwningThread();
@@ -19440,17 +19271,17 @@ FactoryOp::RetryCheckPermission()
   MOZ_ASSERT(mState == State::PermissionRetry);
   MOZ_ASSERT(mCommonParams.principalInfo().type() ==
                PrincipalInfo::TContentPrincipalInfo);
 
   // Swap this to the stack now to ensure that we release it on this thread.
   RefPtr<ContentParent> contentParent;
   mContentParent.swap(contentParent);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   PermissionRequestBase::PermissionValue permission;
   nsresult rv = CheckPermission(contentParent, &permission);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -19463,29 +19294,28 @@ FactoryOp::RetryCheckPermission()
 
   if (permission == PermissionRequestBase::kPermissionDenied ||
       permission == PermissionRequestBase::kPermissionPrompt) {
     return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
   }
 
   MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
 
-  rv = FinishOpen();
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
+  mState = State::FinishOpen;
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
 
   return NS_OK;
 }
 
 nsresult
 FactoryOp::DirectoryOpen()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mState == State::DirectoryWorkOpen);
+  MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(mDirectoryLock);
 
   // gFactoryOps could be null here if the child process crashed or something
   // and that cleaned up the last Factory actor.
   if (!gFactoryOps) {
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
@@ -19505,29 +19335,32 @@ FactoryOp::DirectoryOpen()
   // Adding this to the factory ops list will block any additional ops from
   // proceeding until this one is done.
   gFactoryOps->AppendElement(this);
 
   mBlockedDatabaseOpen = true;
 
   mState = State::DatabaseOpenPending;
   if (!delayed) {
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+    nsresult rv = DatabaseOpen();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 FactoryOp::SendToIOThread()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DatabaseOpenPending);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
@@ -19570,17 +19403,17 @@ FactoryOp::FinishSendResults()
   MOZ_ASSERT(mFactory);
 
   // Make sure to release the factory on this thread.
   RefPtr<Factory> factory;
   mFactory.swap(factory);
 
   if (mBlockedDatabaseOpen) {
     if (mDelayedOp) {
-      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mDelayedOp)));
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToCurrentThread(mDelayedOp)));
       mDelayedOp = nullptr;
     }
 
     MOZ_ASSERT(gFactoryOps);
     gFactoryOps->RemoveElement(this);
   }
 
   mState = State::Completed;
@@ -19859,44 +19692,68 @@ FactoryOp::CheckAtLeastOneAppHasPermissi
   }
 
   return false;
 #else
   return true;
 #endif // MOZ_CHILD_PERMISSIONS
 }
 
-nsresult
+void
 FactoryOp::FinishOpen()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State::Initial || mState == State::PermissionRetry);
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::FinishOpen);
+  MOZ_ASSERT(!mContentParent);
+
+  if (QuotaManager::Get()) {
+    OpenDirectory();
+
+    return;
+  }
+
+  mState = State::QuotaManagerPending;
+  QuotaManager::GetOrCreate(this);
+}
+
+nsresult
+FactoryOp::QuotaManagerOpen()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::QuotaManagerPending);
+
+  if (NS_WARN_IF(!QuotaManager::Get())) {
+    return NS_ERROR_FAILURE;
+  }
+
+  OpenDirectory();
+
+  return NS_OK;
+}
+
+void
+FactoryOp::OpenDirectory()
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mState == State::FinishOpen ||
+             mState == State::QuotaManagerPending);
   MOZ_ASSERT(!mOrigin.IsEmpty());
-  MOZ_ASSERT(!mDatabaseId.IsEmpty());
   MOZ_ASSERT(!mDirectoryLock);
-  MOZ_ASSERT(!mContentParent);
-  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnMainThread());
-
-  QuotaManager* quotaManager = QuotaManager::GetOrCreate();
-  if (NS_WARN_IF(!quotaManager)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
+  MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
+  MOZ_ASSERT(QuotaManager::Get());
 
   mState = State::DirectoryOpenPending;
 
-  quotaManager->OpenDirectory(mCommonParams.metadata().persistenceType(),
-                              mGroup,
-                              mOrigin,
-                              mIsApp,
-                              Client::IDB,
-                              /* aExclusive */ false,
-                              this);
-
-  return NS_OK;
+  QuotaManager::Get()->OpenDirectory(mCommonParams.metadata().persistenceType(),
+                                     mGroup,
+                                     mOrigin,
+                                     mIsApp,
+                                     Client::IDB,
+                                     /* aExclusive */ false,
+                                     this);
 }
 
 bool
 FactoryOp::MustWaitFor(const FactoryOp& aExistingOp)
 {
   AssertIsOnOwningThread();
 
   // Things for the same persistence type, the same origin and the same
@@ -19953,18 +19810,22 @@ FactoryOp::Run()
     case State::PermissionChallenge:
       rv = ChallengePermission();
       break;
 
     case State::PermissionRetry:
       rv = RetryCheckPermission();
       break;
 
-    case State::DirectoryWorkOpen:
-      rv = DirectoryOpen();
+    case State::FinishOpen:
+      FinishOpen();
+      return NS_OK;
+
+    case State::QuotaManagerPending:
+      rv = QuotaManagerOpen();
       break;
 
     case State::DatabaseOpenPending:
       rv = DatabaseOpen();
       break;
 
     case State::DatabaseWorkOpen:
       rv = DoDatabaseWork();
@@ -20004,42 +19865,49 @@ FactoryOp::Run()
   }
 
   return NS_OK;
 }
 
 void
 FactoryOp::DirectoryLockAcquired(DirectoryLock* aLock)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   mDirectoryLock = aLock;
 
-  mState = State::DirectoryWorkOpen;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
-                                                       NS_DISPATCH_NORMAL)));
+  nsresult rv = DirectoryOpen();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    if (NS_SUCCEEDED(mResultCode)) {
+      mResultCode = rv;
+    }
+
+    mState = State::SendingResults;
+    SendResults();
+
+    return;
+  }
 }
 
 void
 FactoryOp::DirectoryLockFailed()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   if (NS_SUCCEEDED(mResultCode)) {
     IDB_REPORT_INTERNAL_ERR();
     mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   mState = State::SendingResults;
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
-                                                       NS_DISPATCH_NORMAL)));
+  SendResults();
 }
 
 void
 FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnBackgroundThread();
 
   NoteActorDestroyed();
@@ -20088,17 +19956,17 @@ OpenDatabaseOp::ActorDestroy(ActorDestro
   if (mVersionChangeOp) {
     mVersionChangeOp->NoteActorDestroyed();
   }
 }
 
 nsresult
 OpenDatabaseOp::DatabaseOpen()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DatabaseOpenPending);
 
   nsresult rv = SendToIOThread();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
@@ -20109,17 +19977,17 @@ OpenDatabaseOp::DoDatabaseWork()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == State::DatabaseWorkOpen);
 
   PROFILER_LABEL("IndexedDB",
                  "OpenDatabaseOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   const nsString& databaseName = mCommonParams.metadata().name();
   PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
 
@@ -20721,17 +20589,17 @@ OpenDatabaseOp::BeginVersionChange()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::BeginVersionChange);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
   MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion);
   MOZ_ASSERT(!mDatabase);
   MOZ_ASSERT(!mVersionChangeTransaction);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   EnsureDatabaseActor();
 
   if (mDatabase->IsInvalidated()) {
@@ -20851,17 +20719,17 @@ OpenDatabaseOp::DispatchToWorkThread()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
   MOZ_ASSERT(mVersionChangeTransaction);
   MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
                IDBTransaction::VERSION_CHANGE);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       IsActorDestroyed() ||
       mDatabase->IsInvalidated()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   mState = State::DatabaseWorkVersionChange;
 
@@ -20904,17 +20772,17 @@ OpenDatabaseOp::SendUpgradeNeeded()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DatabaseWorkVersionChange);
   MOZ_ASSERT(mVersionChangeTransaction);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
   MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
   MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   RefPtr<VersionChangeTransaction> transaction;
   mVersionChangeTransaction.swap(transaction);
 
@@ -21032,20 +20900,17 @@ OpenDatabaseOp::SendResults()
 
 void
 OpenDatabaseOp::ConnectionClosedCallback()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(NS_FAILED(mResultCode));
   MOZ_ASSERT(mDirectoryLock);
 
-  RefPtr<UnlockDirectoryRunnable> runnable =
-    new UnlockDirectoryRunnable(mDirectoryLock.forget());
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+  mDirectoryLock = nullptr;
 }
 
 void
 OpenDatabaseOp::EnsureDatabaseActor()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::BeginVersionChange ||
              mState == State::DatabaseWorkVersionChange ||
@@ -21347,17 +21212,17 @@ nsresult
 OpenDatabaseOp::
 VersionChangeOp::DoDatabaseWork(DatabaseConnection* aConnection)
 {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(mOpenDatabaseOp);
   MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   PROFILER_LABEL("IndexedDB",
                  "OpenDatabaseOp::VersionChangeOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
@@ -21524,17 +21389,17 @@ DeleteDatabaseOp::LoadPreviousVersion(ns
   }
 
   mPreviousVersion = uint64_t(version);
 }
 
 nsresult
 DeleteDatabaseOp::DatabaseOpen()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::DatabaseOpenPending);
 
   // Swap this to the stack now to ensure that we release it on this thread.
   RefPtr<ContentParent> contentParent;
   mContentParent.swap(contentParent);
 
   nsresult rv = SendToIOThread();
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -21549,17 +21414,17 @@ DeleteDatabaseOp::DoDatabaseWork()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == State::DatabaseWorkOpen);
 
   PROFILER_LABEL("IndexedDB",
                  "DeleteDatabaseOp::DoDatabaseWork",
                  js::ProfileEntry::Category::STORAGE);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   const nsString& databaseName = mCommonParams.metadata().name();
   PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
 
@@ -21626,17 +21491,17 @@ DeleteDatabaseOp::DoDatabaseWork()
 
 nsresult
 DeleteDatabaseOp::BeginVersionChange()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::BeginVersionChange);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   DatabaseActorInfo* info;
   if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
     MOZ_ASSERT(!info->mWaitingFactoryOp);
@@ -21665,27 +21530,35 @@ DeleteDatabaseOp::BeginVersionChange()
 
 nsresult
 DeleteDatabaseOp::DispatchToWorkThread()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   mState = State::DatabaseWorkVersionChange;
 
   RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
 
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(versionChangeOp)));
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsresult rv =
+    quotaManager->IOThread()->Dispatch(versionChangeOp, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
 
   return NS_OK;
 }
 
 void
 DeleteDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
 {
   AssertIsOnOwningThread();
@@ -21749,54 +21622,23 @@ DeleteDatabaseOp::SendResults()
     } else {
       response = ClampResultCode(mResultCode);
     }
 
     Unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
-  if (mDirectoryLock) {
-    RefPtr<UnlockDirectoryRunnable> runnable =
-      new UnlockDirectoryRunnable(mDirectoryLock.forget());
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
-  }
+  mDirectoryLock = nullptr;
 
   FinishSendResults();
 }
 
 nsresult
 DeleteDatabaseOp::
-VersionChangeOp::RunOnMainThread()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
-
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnMainThread()) ||
-      !OperationMayProceed()) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  MOZ_ASSERT(quotaManager);
-
-  nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  return NS_OK;
-}
-
-
-nsresult
-DeleteDatabaseOp::
 VersionChangeOp::DeleteFile(nsIFile* aDirectory,
                             const nsAString& aFilename,
                             QuotaManager* aQuotaManager)
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(aDirectory);
   MOZ_ASSERT(!aFilename.IsEmpty());
   MOZ_ASSERT_IF(aQuotaManager, mDeleteDatabaseOp->mEnforcingQuota);
@@ -21863,17 +21705,17 @@ VersionChangeOp::RunOnIOThread()
 {
   AssertIsOnIOThread();
   MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
 
   PROFILER_LABEL("IndexedDB",
                  "DeleteDatabaseOp::VersionChangeOp::RunOnIOThread",
                  js::ProfileEntry::Category::STORAGE);
 
-  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
       !OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   const PersistenceType& persistenceType =
     mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
 
@@ -22087,19 +21929,17 @@ VersionChangeOp::RunOnOwningThread()
 }
 
 nsresult
 DeleteDatabaseOp::
 VersionChangeOp::Run()
 {
   nsresult rv;
 
-  if (NS_IsMainThread()) {
-    rv = RunOnMainThread();
-  } else if (!IsOnBackgroundThread()) {
+  if (IsOnIOThread()) {
     rv = RunOnIOThread();
   } else {
     RunOnOwningThread();
     rv = NS_OK;
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (NS_SUCCEEDED(mResultCode)) {
@@ -22604,17 +22444,17 @@ DatabaseOp::DatabaseOp(Database* aDataba
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aDatabase);
 }
 
 nsresult
 DatabaseOp::SendToIOThread()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::Initial);
 
   if (!OperationMayProceed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   QuotaManager* quotaManager = QuotaManager::Get();
@@ -27171,16 +27011,182 @@ ContinueOp::SendSuccessResult()
   }
 
   mCursor->SendResponseInternal(mResponse, mFiles);
 
   mResponseSent = true;
   return NS_OK;
 }
 
+Utils::Utils()
+#ifdef DEBUG
+  : mActorDestroyed(false)
+#endif
+{
+  AssertIsOnBackgroundThread();
+}
+
+Utils::~Utils()
+{
+  MOZ_ASSERT(mActorDestroyed);
+}
+
+void
+Utils::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+#ifdef DEBUG
+  mActorDestroyed = true;
+#endif
+}
+
+bool
+Utils::RecvDeleteMe()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mActorDestroyed);
+
+  return PBackgroundIndexedDBUtilsParent::Send__delete__(this);
+}
+
+bool
+Utils::RecvGetFileReferences(const PersistenceType& aPersistenceType,
+                             const nsCString& aOrigin,
+                             const nsString& aDatabaseName,
+                             const int64_t& aFileId,
+                             int32_t* aRefCnt,
+                             int32_t* aDBRefCnt,
+                             int32_t* aSliceRefCnt,
+                             bool* aResult)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aRefCnt);
+  MOZ_ASSERT(aDBRefCnt);
+  MOZ_ASSERT(aSliceRefCnt);
+  MOZ_ASSERT(aResult);
+  MOZ_ASSERT(!mActorDestroyed);
+
+  if (NS_WARN_IF(!IndexedDatabaseManager::Get() ||
+                 !QuotaManager::Get())) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (NS_WARN_IF(aPersistenceType != quota::PERSISTENCE_TYPE_PERSISTENT &&
+                 aPersistenceType != quota::PERSISTENCE_TYPE_TEMPORARY &&
+                 aPersistenceType != quota::PERSISTENCE_TYPE_DEFAULT)) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (NS_WARN_IF(aOrigin.IsEmpty())) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (NS_WARN_IF(aDatabaseName.IsEmpty())) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (NS_WARN_IF(aFileId == 0)) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  RefPtr<GetFileReferencesHelper> helper =
+    new GetFileReferencesHelper(aPersistenceType, aOrigin, aDatabaseName,
+                                aFileId);
+
+  nsresult rv =
+    helper->DispatchAndReturnFileReferences(aRefCnt, aDBRefCnt,
+                                            aSliceRefCnt, aResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+nsresult
+GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
+                                                         int32_t* aDBRefCnt,
+                                                         int32_t* aSliceRefCnt,
+                                                         bool* aResult)
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aMemRefCnt);
+  MOZ_ASSERT(aDBRefCnt);
+  MOZ_ASSERT(aSliceRefCnt);
+  MOZ_ASSERT(aResult);
+
+  QuotaManager* quotaManager = QuotaManager::Get();
+  MOZ_ASSERT(quotaManager);
+
+  nsresult rv =
+    quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mozilla::MutexAutoLock autolock(mMutex);
+  while (mWaiting) {
+    mCondVar.Wait();
+  }
+
+  *aMemRefCnt = mMemRefCnt;
+  *aDBRefCnt = mDBRefCnt;
+  *aSliceRefCnt = mSliceRefCnt;
+  *aResult = mResult;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GetFileReferencesHelper::Run()
+{
+  AssertIsOnIOThread();
+
+  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
+  MOZ_ASSERT(mgr);
+
+  RefPtr<FileManager> fileManager =
+    mgr->GetFileManager(mPersistenceType, mOrigin, mDatabaseName);
+
+  if (fileManager) {
+    RefPtr<FileInfo> fileInfo = fileManager->GetFileInfo(mFileId);
+
+    if (fileInfo) {
+      fileInfo->GetReferences(&mMemRefCnt, &mDBRefCnt, &mSliceRefCnt);
+
+      if (mMemRefCnt != -1) {
+        // We added an extra temp ref, so account for that accordingly.
+        mMemRefCnt--;
+      }
+
+      mResult = true;
+    }
+  }
+
+  mozilla::MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mWaiting);
+
+  mWaiting = false;
+  mCondVar.Notify();
+
+  return NS_OK;
+}
+
 void
 PermissionRequestHelper::OnPromptComplete(PermissionValue aPermissionValue)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mActorDestroyed) {
     Unused <<
       PIndexedDBPermissionRequestParent::Send__delete__(this, aPermissionValue);
--- a/dom/indexedDB/ActorsParent.h
+++ b/dom/indexedDB/ActorsParent.h
@@ -21,28 +21,35 @@ namespace quota {
 class Client;
 
 } // namespace quota
 
 namespace indexedDB {
 
 class LoggingInfo;
 class PBackgroundIDBFactoryParent;
+class PBackgroundIndexedDBUtilsParent;
 class PIndexedDBPermissionRequestParent;
 
 PBackgroundIDBFactoryParent*
 AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo);
 
 bool
 RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor,
                                      const LoggingInfo& aLoggingInfo);
 
 bool
 DeallocPBackgroundIDBFactoryParent(PBackgroundIDBFactoryParent* aActor);
 
+PBackgroundIndexedDBUtilsParent*
+AllocPBackgroundIndexedDBUtilsParent();
+
+bool
+DeallocPBackgroundIndexedDBUtilsParent(PBackgroundIndexedDBUtilsParent* aActor);
+
 PIndexedDBPermissionRequestParent*
 AllocPIndexedDBPermissionRequestParent(Element* aOwnerElement,
                                        nsIPrincipal* aPrincipal);
 
 bool
 RecvPIndexedDBPermissionRequestConstructor(
                                      PIndexedDBPermissionRequestParent* aActor);
 
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -468,26 +468,16 @@ IDBFactory::IsChrome() const
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mPrincipalInfo);
 
   return mPrincipalInfo->type() == PrincipalInfo::TSystemPrincipalInfo;
 }
 
 void
-IDBFactory::SetBackgroundActor(BackgroundFactoryChild* aBackgroundActor)
-{
-  AssertIsOnOwningThread();
-  MOZ_ASSERT(aBackgroundActor);
-  MOZ_ASSERT(!mBackgroundActor);
-
-  mBackgroundActor = aBackgroundActor;
-}
-
-void
 IDBFactory::IncrementParentLoggingRequestSerialNumber()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mBackgroundActor);
 
   mBackgroundActor->SendIncrementLoggingRequestSerialNumber();
 }
 
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -117,19 +117,16 @@ public:
   OwningThread() const;
 #else
   void
   AssertIsOnOwningThread() const
   { }
 #endif
 
   void
-  SetBackgroundActor(BackgroundFactoryChild* aBackgroundActor);
-
-  void
   ClearBackgroundActor()
   {
     AssertIsOnOwningThread();
 
     mBackgroundActor = nullptr;
   }
 
   void
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -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/. */
 
 #include "IndexedDatabaseManager.h"
 
 #include "nsIConsoleService.h"
 #include "nsIDiskSpaceWatcher.h"
 #include "nsIDOMWindow.h"
+#include "nsIEventTarget.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsIScriptError.h"
 #include "nsIScriptGlobalObject.h"
 
 #include "jsapi.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/CondVar.h"
@@ -21,16 +22,19 @@
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/ErrorEventBinding.h"
 #include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/ipc/PBackgroundChild.h"
 #include "nsContentUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
 
 #include "FileInfo.h"
 #include "FileManager.h"
 #include "IDBEvents.h"
@@ -67,16 +71,17 @@
 #define LOW_DISK_SPACE_DATA_FREE "free"
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 using namespace mozilla::dom::quota;
 using namespace mozilla::dom::workers;
+using namespace mozilla::ipc;
 
 class FileManagerInfo
 {
 public:
   already_AddRefed<FileManager>
   GetFileManager(PersistenceType aPersistenceType,
                  const nsAString& aName) const;
 
@@ -173,30 +178,36 @@ class DeleteFilesRunnable final
     // Notifying the QuotaManager that it can proceed to the next operation on
     // the main thread. Next step is State_Completed.
     State_UnblockingOpen,
 
     // All done.
     State_Completed
   };
 
+  nsCOMPtr<nsIEventTarget> mBackgroundThread;
+
   RefPtr<FileManager> mFileManager;
   nsTArray<int64_t> mFileIds;
 
   RefPtr<DirectoryLock> mDirectoryLock;
 
   nsCOMPtr<nsIFile> mDirectory;
   nsCOMPtr<nsIFile> mJournalDirectory;
 
   State mState;
 
 public:
-  DeleteFilesRunnable(FileManager* aFileManager,
+  DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
+                      FileManager* aFileManager,
                       nsTArray<int64_t>& aFileIds);
 
+  void
+  Dispatch();
+
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
   virtual void
   DirectoryLockAcquired(DirectoryLock* aLock) override;
 
   virtual void
   DirectoryLockFailed() override;
@@ -215,82 +226,42 @@ private:
 
   void
   Finish();
 
   void
   UnblockOpen();
 };
 
-class GetFileReferencesHelper final : public nsIRunnable
-{
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-  NS_DECL_NSIRUNNABLE
-
-  GetFileReferencesHelper(PersistenceType aPersistenceType,
-                          const nsACString& aOrigin,
-                          const nsAString& aDatabaseName,
-                          int64_t aFileId)
-  : mPersistenceType(aPersistenceType),
-    mOrigin(aOrigin),
-    mDatabaseName(aDatabaseName),
-    mFileId(aFileId),
-    mMutex(IndexedDatabaseManager::FileMutex()),
-    mCondVar(mMutex, "GetFileReferencesHelper::mCondVar"),
-    mMemRefCnt(-1),
-    mDBRefCnt(-1),
-    mSliceRefCnt(-1),
-    mResult(false),
-    mWaiting(true)
-  { }
-
-  nsresult
-  DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
-                                  int32_t* aDBRefCnt,
-                                  int32_t* aSliceRefCnt,
-                                  bool* aResult);
-
-private:
-  ~GetFileReferencesHelper() {}
-
-  PersistenceType mPersistenceType;
-  nsCString mOrigin;
-  nsString mDatabaseName;
-  int64_t mFileId;
-
-  mozilla::Mutex& mMutex;
-  mozilla::CondVar mCondVar;
-  int32_t mMemRefCnt;
-  int32_t mDBRefCnt;
-  int32_t mSliceRefCnt;
-  bool mResult;
-  bool mWaiting;
-};
-
 void
 AtomicBoolPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aClosure);
 
   *static_cast<Atomic<bool>*>(aClosure) = Preferences::GetBool(aPrefName);
 }
 
 } // namespace
 
 IndexedDatabaseManager::IndexedDatabaseManager()
-: mFileMutex("IndexedDatabaseManager.mFileMutex")
+  : mFileMutex("IndexedDatabaseManager.mFileMutex")
+  , mBackgroundActor(nullptr)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 }
 
 IndexedDatabaseManager::~IndexedDatabaseManager()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  if (mBackgroundActor) {
+    mBackgroundActor->SendDeleteMeInternal();
+    MOZ_ASSERT(!mBackgroundActor, "SendDeleteMeInternal should have cleared!");
+  }
 }
 
 bool IndexedDatabaseManager::sIsMainProcess = false;
 bool IndexedDatabaseManager::sFullSynchronousMode = false;
 
 PRLogModuleInfo* IndexedDatabaseManager::sLoggingModule;
 
 Atomic<IndexedDatabaseManager::LoggingMode>
@@ -758,16 +729,34 @@ IndexedDatabaseManager::IsFileHandleEnab
 {
   MOZ_ASSERT(gDBManager,
              "IsFileHandleEnabled() called before indexedDB has been "
              "initialized!");
 
   return gFileHandleEnabled;
 }
 
+void
+IndexedDatabaseManager::ClearBackgroundActor()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mBackgroundActor = nullptr;
+}
+
+void
+IndexedDatabaseManager::NoteBackgroundThread(nsIEventTarget* aBackgroundThread)
+{
+  MOZ_ASSERT(IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aBackgroundThread);
+
+  mBackgroundThread = aBackgroundThread;
+}
+
 already_AddRefed<FileManager>
 IndexedDatabaseManager::GetFileManager(PersistenceType aPersistenceType,
                                        const nsACString& aOrigin,
                                        const nsAString& aDatabaseName)
 {
   AssertIsOnIOThread();
 
   FileManagerInfo* info;
@@ -893,43 +882,42 @@ IndexedDatabaseManager::BlockAndGetFileR
                                                bool* aResult)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!InTestingMode())) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  if (IsMainProcess()) {
-    RefPtr<GetFileReferencesHelper> helper =
-      new GetFileReferencesHelper(aPersistenceType, aOrigin, aDatabaseName,
-                                  aFileId);
-
-    nsresult rv =
-      helper->DispatchAndReturnFileReferences(aRefCnt, aDBRefCnt,
-                                              aSliceRefCnt, aResult);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  } else {
-    ContentChild* contentChild = ContentChild::GetSingleton();
-    if (NS_WARN_IF(!contentChild)) {
+  if (!mBackgroundActor) {
+    PBackgroundChild* bgActor = BackgroundChild::GetForCurrentThread();
+    if (NS_WARN_IF(!bgActor)) {
       return NS_ERROR_FAILURE;
     }
 
-    if (!contentChild->SendGetFileReferences(aPersistenceType,
-                                             nsCString(aOrigin),
-                                             nsString(aDatabaseName),
-                                             aFileId,
-                                             aRefCnt,
-                                             aDBRefCnt,
-                                             aSliceRefCnt,
-                                             aResult)) {
-      return NS_ERROR_FAILURE;
-    }
+    BackgroundUtilsChild* actor = new BackgroundUtilsChild(this);
+
+    mBackgroundActor =
+      static_cast<BackgroundUtilsChild*>(
+        bgActor->SendPBackgroundIndexedDBUtilsConstructor(actor));
+  }
+
+  if (NS_WARN_IF(!mBackgroundActor)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!mBackgroundActor->SendGetFileReferences(aPersistenceType,
+                                               nsCString(aOrigin),
+                                               nsString(aDatabaseName),
+                                               aFileId,
+                                               aRefCnt,
+                                               aDBRefCnt,
+                                               aSliceRefCnt,
+                                               aResult)) {
+    return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 nsresult
 IndexedDatabaseManager::FlushPendingFileDeletions()
 {
@@ -1053,21 +1041,21 @@ IndexedDatabaseManager::Notify(nsITimer*
   MOZ_ASSERT(NS_IsMainThread());
 
   for (auto iter = mPendingDeleteInfos.ConstIter(); !iter.Done(); iter.Next()) {
     auto key = iter.Key();
     auto value = iter.Data();
     MOZ_ASSERT(!value->IsEmpty());
 
     RefPtr<DeleteFilesRunnable> runnable =
-      new DeleteFilesRunnable(key, *value);
+      new DeleteFilesRunnable(mBackgroundThread, key, *value);
 
     MOZ_ASSERT(value->IsEmpty());
 
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+    runnable->Dispatch();
   }
 
   mPendingDeleteInfos.Clear();
 
   return NS_OK;
 }
 
 already_AddRefed<FileManager>
@@ -1169,24 +1157,36 @@ FileManagerInfo::GetArray(PersistenceTyp
       return mDefaultStorageFileManagers;
 
     case PERSISTENCE_TYPE_INVALID:
     default:
       MOZ_CRASH("Bad storage type value!");
   }
 }
 
-DeleteFilesRunnable::DeleteFilesRunnable(FileManager* aFileManager,
+DeleteFilesRunnable::DeleteFilesRunnable(nsIEventTarget* aBackgroundThread,
+                                         FileManager* aFileManager,
                                          nsTArray<int64_t>& aFileIds)
-  : mFileManager(aFileManager)
+  : mBackgroundThread(aBackgroundThread)
+  , mFileManager(aFileManager)
   , mState(State_Initial)
 {
   mFileIds.SwapElements(aFileIds);
 }
 
+void
+DeleteFilesRunnable::Dispatch()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_Initial);
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL)));
+}
+
 NS_IMPL_ISUPPORTS(DeleteFilesRunnable, nsIRunnable)
 
 NS_IMETHODIMP
 DeleteFilesRunnable::Run()
 {
   nsresult rv;
 
   switch (mState) {
@@ -1212,17 +1212,17 @@ DeleteFilesRunnable::Run()
   }
 
   return NS_OK;
 }
 
 void
 DeleteFilesRunnable::DirectoryLockAcquired(DirectoryLock* aLock)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   mDirectoryLock = aLock;
 
   QuotaManager* quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
@@ -1234,27 +1234,27 @@ DeleteFilesRunnable::DirectoryLockAcquir
     Finish();
     return;
   }
 }
 
 void
 DeleteFilesRunnable::DirectoryLockFailed()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   Finish();
 }
 
 nsresult
 DeleteFilesRunnable::Open()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_Initial);
 
   QuotaManager* quotaManager = QuotaManager::Get();
   if (NS_WARN_IF(!quotaManager)) {
     return NS_ERROR_FAILURE;
   }
 
   mState = State_DirectoryOpenPending;
@@ -1339,93 +1339,26 @@ DeleteFilesRunnable::DoDatabaseWork()
 
 void
 DeleteFilesRunnable::Finish()
 {
   // Must set mState before dispatching otherwise we will race with the main
   // thread.
   mState = State_UnblockingOpen;
 
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mBackgroundThread->Dispatch(this, NS_DISPATCH_NORMAL)));
 }
 
 void
 DeleteFilesRunnable::UnblockOpen()
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_UnblockingOpen);
 
-  if (mDirectoryLock) {
-    mDirectoryLock = nullptr;
-  }
+  mDirectoryLock = nullptr;
 
   mState = State_Completed;
 }
 
-nsresult
-GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
-                                                         int32_t* aDBRefCnt,
-                                                         int32_t* aSliceRefCnt,
-                                                         bool* aResult)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-  QuotaManager* quotaManager = QuotaManager::Get();
-  NS_ASSERTION(quotaManager, "Shouldn't be null!");
-
-  nsresult rv =
-    quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mozilla::MutexAutoLock autolock(mMutex);
-  while (mWaiting) {
-    mCondVar.Wait();
-  }
-
-  *aMemRefCnt = mMemRefCnt;
-  *aDBRefCnt = mDBRefCnt;
-  *aSliceRefCnt = mSliceRefCnt;
-  *aResult = mResult;
-
-  return NS_OK;
-}
-
-NS_IMPL_ISUPPORTS(GetFileReferencesHelper,
-                  nsIRunnable)
-
-NS_IMETHODIMP
-GetFileReferencesHelper::Run()
-{
-  AssertIsOnIOThread();
-
-  IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
-  NS_ASSERTION(mgr, "This should never fail!");
-
-  RefPtr<FileManager> fileManager =
-    mgr->GetFileManager(mPersistenceType, mOrigin, mDatabaseName);
-
-  if (fileManager) {
-    RefPtr<FileInfo> fileInfo = fileManager->GetFileInfo(mFileId);
-
-    if (fileInfo) {
-      fileInfo->GetReferences(&mMemRefCnt, &mDBRefCnt, &mSliceRefCnt);
-
-      if (mMemRefCnt != -1) {
-        // We added an extra temp ref, so account for that accordingly.
-        mMemRefCnt--;
-      }
-
-      mResult = true;
-    }
-  }
-
-  mozilla::MutexAutoLock lock(mMutex);
-  NS_ASSERTION(mWaiting, "Huh?!");
-
-  mWaiting = false;
-  mCondVar.Notify();
-
-  return NS_OK;
-}
-
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -13,26 +13,28 @@
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/Mutex.h"
 #include "nsClassHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsHashKeys.h"
 #include "nsITimer.h"
 
+class nsIEventTarget;
 struct PRLogModuleInfo;
 
 namespace mozilla {
 
 class EventChainPostVisitor;
 
 namespace dom {
 
 namespace indexedDB {
 
+class BackgroundUtilsChild;
 class FileManager;
 class FileManagerInfo;
 class IDBFactory;
 
 class IndexedDatabaseManager final
   : public nsIObserver
   , public nsITimerCallback
 {
@@ -116,16 +118,22 @@ public:
   ExperimentalFeaturesEnabled(JSContext* /* aCx */, JSObject* /* aGlobal */)
   {
     return ExperimentalFeaturesEnabled();
   }
 
   static bool
   IsFileHandleEnabled();
 
+  void
+  ClearBackgroundActor();
+
+  void
+  NoteBackgroundThread(nsIEventTarget* aBackgroundThread);
+
   already_AddRefed<FileManager>
   GetFileManager(PersistenceType aPersistenceType,
                  const nsACString& aOrigin,
                  const nsAString& aDatabaseName);
 
   void
   AddFileManager(FileManager* aFileManager);
 
@@ -189,16 +197,18 @@ private:
   Init();
 
   void
   Destroy();
 
   static void
   LoggingModePrefChangedCallback(const char* aPrefName, void* aClosure);
 
+  nsCOMPtr<nsIEventTarget> mBackgroundThread;
+
   nsCOMPtr<nsITimer> mDeleteTimer;
 
   // Maintains a list of all file managers per origin. This list isn't
   // protected by any mutex but it is only ever touched on the IO thread.
   nsClassHashtable<nsCStringHashKey, FileManagerInfo> mFileManagerInfos;
 
   nsClassHashtable<nsRefPtrHashKey<FileManager>,
                    nsTArray<int64_t>> mPendingDeleteInfos;
@@ -207,16 +217,18 @@ private:
   // It's s also used to atomically update FileInfo.mRefCnt, FileInfo.mDBRefCnt
   // and FileInfo.mSliceRefCnt
   mozilla::Mutex mFileMutex;
 
 #ifdef ENABLE_INTL_API
   nsCString mLocale;
 #endif
 
+  BackgroundUtilsChild* mBackgroundActor;
+
   static bool sIsMainProcess;
   static bool sFullSynchronousMode;
   static PRLogModuleInfo* sLoggingModule;
   static Atomic<LoggingMode> sLoggingMode;
   static mozilla::Atomic<bool> sLowDiskSpaceMode;
 };
 
 } // namespace indexedDB
--- a/dom/indexedDB/PBackgroundIDBFactory.ipdl
+++ b/dom/indexedDB/PBackgroundIDBFactory.ipdl
@@ -4,16 +4,18 @@
 
 include protocol PBackground;
 include protocol PBackgroundIDBDatabase;
 include protocol PBackgroundIDBFactoryRequest;
 
 include PBackgroundIDBSharedTypes;
 include PBackgroundSharedTypes;
 
+include "mozilla/dom/quota/SerializationHelpers.h";
+
 using struct mozilla::void_t
   from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 struct CommonFactoryRequestParams
--- a/dom/indexedDB/PBackgroundIDBSharedTypes.ipdlh
+++ b/dom/indexedDB/PBackgroundIDBSharedTypes.ipdlh
@@ -4,16 +4,17 @@
 
 include protocol PBackgroundIDBDatabaseFile;
 include protocol PBackgroundMutableFile;
 include protocol PBlob;
 
 include DOMTypes;
 
 include "mozilla/dom/indexedDB/SerializationHelpers.h";
+include "mozilla/dom/quota/SerializationHelpers.h";
 
 using struct mozilla::null_t
   from "ipc/IPCMessageUtils.h";
 
 using struct mozilla::void_t
   from "ipc/IPCMessageUtils.h";
 
 using mozilla::dom::indexedDB::IDBCursor::Direction
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/PBackgroundIndexedDBUtils.ipdl
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+
+include "mozilla/dom/quota/SerializationHelpers.h";
+
+using mozilla::dom::quota::PersistenceType
+  from "mozilla/dom/quota/PersistenceType.h";
+
+namespace mozilla {
+namespace dom {
+namespace indexedDB {
+
+sync protocol PBackgroundIndexedDBUtils
+{
+  manager PBackground;
+
+parent:
+  DeleteMe();
+
+  // Use only for testing!
+  sync GetFileReferences(PersistenceType persistenceType,
+                         nsCString origin,
+                         nsString databaseName,
+                         int64_t fileId)
+    returns (int32_t refCnt, int32_t dBRefCnt, int32_t sliceRefCnt,
+             bool result);
+
+child:
+  __delete__();
+};
+
+} // namespace indexedDB
+} // namespace dom
+} // namespace mozilla
--- a/dom/indexedDB/SerializationHelpers.h
+++ b/dom/indexedDB/SerializationHelpers.h
@@ -8,29 +8,20 @@
 #define mozilla_dom_indexeddb_serializationhelpers_h__
 
 #include "ipc/IPCMessageUtils.h"
 
 #include "mozilla/dom/indexedDB/Key.h"
 #include "mozilla/dom/indexedDB/KeyPath.h"
 #include "mozilla/dom/indexedDB/IDBCursor.h"
 #include "mozilla/dom/indexedDB/IDBTransaction.h"
-#include "mozilla/dom/quota/PersistenceType.h"
 
 namespace IPC {
 
 template <>
-struct ParamTraits<mozilla::dom::quota::PersistenceType> :
-  public ContiguousEnumSerializer<
-                               mozilla::dom::quota::PersistenceType,
-                               mozilla::dom::quota::PERSISTENCE_TYPE_PERSISTENT,
-                               mozilla::dom::quota::PERSISTENCE_TYPE_INVALID>
-{ };
-
-template <>
 struct ParamTraits<mozilla::dom::indexedDB::Key>
 {
   typedef mozilla::dom::indexedDB::Key paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mBuffer);
   }
--- a/dom/indexedDB/moz.build
+++ b/dom/indexedDB/moz.build
@@ -77,16 +77,17 @@ IPDL_SOURCES += [
     'PBackgroundIDBDatabaseFile.ipdl',
     'PBackgroundIDBDatabaseRequest.ipdl',
     'PBackgroundIDBFactory.ipdl',
     'PBackgroundIDBFactoryRequest.ipdl',
     'PBackgroundIDBRequest.ipdl',
     'PBackgroundIDBSharedTypes.ipdlh',
     'PBackgroundIDBTransaction.ipdl',
     'PBackgroundIDBVersionChangeTransaction.ipdl',
+    'PBackgroundIndexedDBUtils.ipdl',
     'PIndexedDBPermissionRequest.ipdl',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['GNU_CC']:
--- a/dom/indexedDB/test/bug839193.js
+++ b/dom/indexedDB/test/bug839193.js
@@ -1,26 +1,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-const nsIQuotaManager = Components.interfaces.nsIQuotaManager;
+const nsIQuotaManagerService = Components.interfaces.nsIQuotaManagerService;
 
 var gURI = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService).newURI("http://localhost", null, null);
 
-function onUsageCallback(principal, usage, fileUsage) {}
+function onUsageCallback(request) {}
 
 function onLoad()
 {
-  var quotaManager = Components.classes["@mozilla.org/dom/quota/manager;1"]
-                               .getService(nsIQuotaManager);
+  var quotaManagerService =
+    Components.classes["@mozilla.org/dom/quota-manager-service;1"]
+              .getService(nsIQuotaManagerService);
   let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
                             .getService(Components.interfaces.nsIScriptSecurityManager)
                             .createCodebasePrincipal(gURI, {});
-  var quotaRequest = quotaManager.getUsageForPrincipal(principal, onUsageCallback);
+  var quotaRequest = quotaManagerService.getUsageForPrincipal(principal,
+                                                              onUsageCallback);
   quotaRequest.cancel();
   Components.classes["@mozilla.org/observer-service;1"]
             .getService(Components.interfaces.nsIObserverService)
             .notifyObservers(window, "bug839193-loaded", null);
 }
 
 function onUnload()
 {
--- a/dom/indexedDB/test/file.js
+++ b/dom/indexedDB/test/file.js
@@ -177,24 +177,27 @@ function verifyMutableFile(mutableFile1,
   ok(mutableFile1 instanceof IDBMutableFile, "Instance of IDBMutableFile");
   is(mutableFile1.name, file2.name, "Correct name");
   is(mutableFile1.type, file2.type, "Correct type");
   executeSoon(function() {
     testGenerator.next();
   });
 }
 
-function grabFileUsageAndContinueHandler(usage, fileUsage)
+function grabFileUsageAndContinueHandler(request)
 {
-  testGenerator.send(fileUsage);
+  testGenerator.send(request.fileUsage);
 }
 
 function getUsage(usageHandler)
 {
-  SpecialPowers.getStorageUsageForDoc(SpecialPowers.wrap(document), usageHandler);
+  let qms = SpecialPowers.Services.qms;
+  let principal = SpecialPowers.wrap(document).nodePrincipal;
+  let cb = SpecialPowers.wrapCallback(usageHandler);
+  qms.getUsageForPrincipal(principal, cb);
 }
 
 function getFileId(file)
 {
   return utils.getFileId(file);
 }
 
 function getFilePath(file)
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -29,17 +29,21 @@ function executeSoon(aFun)
   thread.dispatch({
     run: function() {
       aFun();
     }
   }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
 }
 
 function clearAllDatabases(callback) {
-  SpecialPowers.clearStorageForDoc(SpecialPowers.wrap(document), callback);
+  let qms = SpecialPowers.Services.qms;
+  let principal = SpecialPowers.wrap(document).nodePrincipal;