Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 17 Sep 2015 15:08:41 +0200
changeset 295709 4e5f3ed0c573
parent 295708 2aec63c0c2bd (current diff)
parent 295583 de0e763b5210 (diff)
child 295710 40387ce188ff
push id5245
push userraliiev@mozilla.com
push date2015-10-29 11:30 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone43.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-central to b2g-inbound
dom/media/TimeVarying.h
js/src/jit-test/tests/asm.js/syntax-error-illegal-character.js
js/src/jit-test/tests/basic/syntax-error-illegal-character.js
xpcom/glue/pldhash.cpp
xpcom/glue/pldhash.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1935,17 +1935,17 @@ pref("browser.pocket.enabledLocales", "c
 // View source tabs are only enabled by default for Dev. Ed and Nightly.
 #ifdef RELEASE_BUILD
 pref("view_source.tab", false);
 #else
 pref("view_source.tab", true);
 #endif
 
 // Enable ServiceWorkers for Push API consumers.
-// Interception is still disabled.
+// Interception is still disabled on beta and release.
 pref("dom.serviceWorkers.enabled", true);
 
-#ifdef NIGHTLY_BUILD
+#ifndef RELEASE_BUILD
 pref("dom.serviceWorkers.interception.enabled", true);
 #endif
 
 // Enable Push API.
 pref("dom.push.enabled", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6978,26 +6978,24 @@ var gIdentityHandler = {
 
     // NOTE: We do NOT update the identity popup (the control center) when
     // we receive a new security state. If the user opened the popup and looks
     // at the provided information we don't want to suddenly change the panel
     // contents.
   },
 
   /**
-   * Return the eTLD+1 version of the current hostname
+   * Attempt to provide proper IDN treatment for host names
    */
-  getEffectiveHost : function() {
+  getEffectiveHost: function() {
     if (!this._IDNService)
       this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
                          .getService(Ci.nsIIDNService);
     try {
-      let baseDomain =
-        Services.eTLD.getBaseDomainFromHost(this._uri.host);
-      return this._IDNService.convertToDisplayIDN(baseDomain, {});
+      return this._IDNService.convertToDisplayIDN(this._uri.host, {});
     } catch (e) {
       // If something goes wrong (e.g. host is an IP address) just fail back
       // to the full domain.
       return this._uri.host;
     }
   },
 
   /**
@@ -7157,16 +7155,17 @@ var gIdentityHandler = {
       updateAttribute(element, "isbroken", isBroken);
     }
 
     // Initialize the optional strings to empty values
     let supplemental = "";
     let verifier = "";
     let host = "";
     let owner = "";
+    let crop = "start";
 
     try {
       host = this.getEffectiveHost();
     } catch (e) {
       // Some URIs might have no hosts.
     }
 
     // Fallback for special protocols.
@@ -7176,16 +7175,18 @@ var gIdentityHandler = {
 
     // Fill in the CA name if we have a valid TLS certificate.
     if (isSecure) {
       verifier = this._identityBox.tooltipText;
     }
 
     // Fill in organization information if we have a valid EV certificate.
     if (isEV) {
+      crop = "end";
+
       let iData = this.getIdentityData();
       host = owner = iData.subjectOrg;
       verifier = this._identityBox.tooltipText;
 
       // Build an appropriate supplemental block out of whatever location data we have
       if (iData.city)
         supplemental += iData.city + "\n";
       if (iData.state && iData.country)
@@ -7195,16 +7196,17 @@ var gIdentityHandler = {
         supplemental += iData.state;
       else if (iData.country) // Country only
         supplemental += iData.country;
     }
 
     // Push the appropriate strings out to the UI. Need to use |value| for the
     // host as it's a <label> that will be cropped if too long. Using
     // |textContent| would simply wrap the value.
+    this._identityPopupContentHost.setAttribute("crop", crop);
     this._identityPopupContentHost.setAttribute("value", host);
     this._identityPopupContentOwner.textContent = owner;
     this._identityPopupContentSupp.textContent = supplemental;
     this._identityPopupContentVerif.textContent = verifier;
 
     // Update per-site permissions section.
     this.updateSitePermissions();
   },
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5481,16 +5481,17 @@
           context.fillRect(0, 0, canvas.width, canvas.height);
           // Create a panel to use it in setDragImage
           // which will tell xul to render a panel that follows
           // the pointer while a dnd session is on.
           if (!this._dndPanel) {
             this._dndCanvas = canvas;
             this._dndPanel = document.createElement("panel");
             this._dndPanel.setAttribute("type", "drag");
+            this._dndPanel.setAttribute("mousethrough", "always");
             this._dndPanel.appendChild(canvas);
             document.documentElement.appendChild(this._dndPanel);
           }
           // PageThumb is async with e10s but that's fine
           // since we can update the panel during the dnd.
           PageThumbs.captureToCanvas(browser, canvas);
           toDrag = this._dndPanel;
         } else {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -465,16 +465,17 @@ skip-if = e10s # Bug 1100700 - test reli
 [browser_urlbarAutoFillTrimURLs.js]
 [browser_urlbarCopying.js]
 [browser_urlbarDelete.js]
 [browser_urlbarEnter.js]
 [browser_urlbarEnterAfterMouseOver.js]
 skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_urlbarRevert.js]
 [browser_urlbarSearchSingleWordNotification.js]
+[browser_urlbarSearchSuggestions.js]
 [browser_urlbarSearchSuggestionsNotification.js]
 [browser_urlbarStop.js]
 [browser_urlbarTrimURLs.js]
 [browser_urlbar_autoFill_backspaced.js]
 [browser_urlbar_search_healthreport.js]
 [browser_urlbar_searchsettings.js]
 [browser_utilityOverlay.js]
 [browser_viewSourceInTabOnViewSource.js]
--- a/browser/base/content/test/general/browser_identity_UI.js
+++ b/browser/base/content/test/general/browser_identity_UI.js
@@ -12,43 +12,43 @@ function test() {
 }
 
 // Greek IDN for 'example.test'.
 var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE";
 var tests = [
   {
     name: "normal domain",
     location: "http://test1.example.org/",
-    effectiveHost: "example.org"
+    effectiveHost: "test1.example.org"
   },
   {
     name: "view-source",
     location: "view-source:http://example.com/",
     effectiveHost: null
   },
   {
     name: "normal HTTPS",
     location: "https://example.com/",
     effectiveHost: "example.com",
     isHTTPS: true
   },
   {
     name: "IDN subdomain",
     location: "http://sub1." + idnDomain + "/",
-    effectiveHost: idnDomain
+    effectiveHost: "sub1." + idnDomain
   },
   {
     name: "subdomain with port",
     location: "http://sub1.test1.example.org:8000/",
-    effectiveHost: "example.org"
+    effectiveHost: "sub1.test1.example.org"
   },
   {
     name: "subdomain HTTPS",
     location: "https://test1.example.com/",
-    effectiveHost: "example.com",
+    effectiveHost: "test1.example.com",
     isHTTPS: true
   },
   {
     name: "view-source HTTPS",
     location: "view-source:https://example.com/",
     effectiveHost: null,
     isHTTPS: true
   },
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_urlbarSearchSuggestions.js
@@ -0,0 +1,63 @@
+const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
+const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+
+// Must run first.
+add_task(function* prepare() {
+  Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+  let engine = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+  let oldCurrentEngine = Services.search.currentEngine;
+  Services.search.currentEngine = engine;
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref(SUGGEST_URLBAR_PREF);
+    Services.search.currentEngine = oldCurrentEngine;
+
+    // Clicking suggestions causes visits to search results pages, so clear that
+    // history now.
+    yield PlacesTestUtils.clearHistory();
+
+    // Make sure the popup is closed for the next test.
+    gURLBar.blur();
+    Assert.ok(!gURLBar.popup.popupOpen, "popup should be closed");
+  });
+});
+
+add_task(function* clickSuggestion() {
+  gURLBar.focus();
+  yield promiseAutocompleteResultPopup("foo");
+  let [idx, suggestion] = yield promiseFirstSuggestion();
+  let item = gURLBar.popup.richlistbox.getItemAtIndex(idx);
+  let loadPromise = promiseTabLoaded(gBrowser.selectedTab);
+  item.click();
+  yield loadPromise;
+  let uri = Services.search.currentEngine.getSubmission(suggestion).uri;
+  Assert.ok(uri.equals(gBrowser.currentURI),
+            "The search results page should have loaded");
+});
+
+function getFirstSuggestion() {
+  let controller = gURLBar.popup.input.controller;
+  let matchCount = controller.matchCount;
+  let present = false;
+  for (let i = 0; i < matchCount; i++) {
+    let url = controller.getValueAt(i);
+    let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+    if (mozActionMatch) {
+      let [, type, paramStr] = mozActionMatch;
+      let params = JSON.parse(paramStr);
+      if (type == "searchengine" && "searchSuggestion" in params) {
+        return [i, params.searchSuggestion];
+      }
+    }
+  }
+  return [-1, null];
+}
+
+function promiseFirstSuggestion() {
+  return new Promise(resolve => {
+    let pair;
+    waitForCondition(() => {
+      pair = getFirstSuggestion();
+      return pair[0] >= 0;
+    }, () => resolve(pair));
+  });
+}
--- a/browser/base/content/test/general/browser_urlbarSearchSuggestionsNotification.js
+++ b/browser/base/content/test/general/browser_urlbarSearchSuggestionsNotification.js
@@ -178,16 +178,29 @@ add_task(function* multipleWindows() {
   gURLBar.focus();
   yield promiseAutocompleteResultPopup("win1done");
   assertVisible(false);
 
   yield BrowserTestUtils.closeWindow(win2);
   yield BrowserTestUtils.closeWindow(win3);
 });
 
+add_task(function* enableOutsideNotification() {
+  // Setting the suggest.searches pref outside the notification (e.g., by
+  // ticking the checkbox in the preferences window) should hide it.
+  Services.prefs.setBoolPref(SUGGEST_ALL_PREF, true);
+  Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, false);
+  yield setUserMadeChoicePref(false);
+
+  Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
+  gURLBar.focus();
+  yield promiseAutocompleteResultPopup("foo");
+  assertVisible(false);
+});
+
 /**
  * Setting the choice pref triggers a pref observer in the urlbar, which hides
  * the notification if it's present.  This function returns a promise that's
  * resolved once the observer fires.
  *
  * @param userMadeChoice  A boolean, the pref's new value.
  * @return A Promise that's resolved when the observer fires -- or, if the pref
  *         is currently the given value, that's resolved immediately.
@@ -209,23 +222,23 @@ function setUserMadeChoicePref(userMadeC
 }
 
 function suggestionsPresent() {
   let controller = gURLBar.popup.input.controller;
   let matchCount = controller.matchCount;
   let present = false;
   for (let i = 0; i < matchCount; i++) {
     let url = controller.getValueAt(i);
-    let [, type, paramStr] = url.match(/^moz-action:([^,]+),(.*)$/);
-    let params = {};
-    try {
-      params = JSON.parse(paramStr);
-    } catch (err) {}
-    if (type == "searchengine" && "searchSuggestion" in params) {
-      return true;
+    let mozActionMatch = url.match(/^moz-action:([^,]+),(.*)$/);
+    if (mozActionMatch) {
+      let [, type, paramStr] = mozActionMatch;
+      let params = JSON.parse(paramStr);
+      if (type == "searchengine" && "searchSuggestion" in params) {
+        return true;
+      }
     }
   }
   return false;
 }
 
 function promiseSuggestionsPresent() {
   return new Promise(resolve => {
     waitForCondition(suggestionsPresent, resolve);
--- a/browser/base/content/test/general/searchSuggestionEngine.xml
+++ b/browser/base/content/test/general/searchSuggestionEngine.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
 <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
-<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine&amp;terms={searchTerms}" rel="searchform"/>
+<Url type="text/html" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}" rel="searchform"/>
 </SearchPlugin>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -61,19 +61,17 @@ file, You can obtain one at http://mozil
 
         this._prefs.addObserver("", this, false);
         this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
         this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
         this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
         this.timeout = this._prefs.getIntPref("delay");
         this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
         this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
-        this._userMadeSearchSuggestionsChoice =
-          this._prefs.getBoolPref("userMadeSearchSuggestionsChoice");
-
+        this._cacheUserMadeSearchSuggestionsChoice();
         this.inputField.controllers.insertControllerAt(0, this._copyCutController);
         this.inputField.addEventListener("paste", this, false);
         this.inputField.addEventListener("mousedown", this, false);
         this.inputField.addEventListener("mousemove", this, false);
         this.inputField.addEventListener("mouseout", this, false);
         this.inputField.addEventListener("overflow", this, false);
         this.inputField.addEventListener("underflow", this, false);
 
@@ -658,21 +656,29 @@ file, You can obtain one at http://mozil
                 break;
               case "delay":
                 this.timeout = this._prefs.getIntPref(aData);
                 break;
               case "formatting.enabled":
                 this._formattingEnabled = this._prefs.getBoolPref(aData);
                 break;
               case "userMadeSearchSuggestionsChoice":
-                this._userMadeSearchSuggestionsChoice =
-                  this._prefs.getBoolPref(aData);
-                this.popup.searchSuggestionsNotificationWasDismissed(
-                  this._prefs.getBoolPref("suggest.searches")
-                );
+              case "suggest.searches":
+                this._cacheUserMadeSearchSuggestionsChoice();
+                // Make sure the urlbar is focused.  It won't be, for example,
+                // if the user used an accesskey to make an opt-in choice.
+                // mIgnoreFocus prevents the text from being selected.
+                this.mIgnoreFocus = true;
+                this.focus();
+                this.mIgnoreFocus = false;
+                if (this._userMadeSearchSuggestionsChoice) {
+                  this.popup.searchSuggestionsNotificationWasDismissed(
+                    this._prefs.getBoolPref("suggest.searches")
+                  );
+                }
                 break;
               case "trimURLs":
                 this._mayTrimURLs = this._prefs.getBoolPref(aData);
                 break;
               case "unifiedcomplete":
                 let useUnifiedComplete = false;
                 try {
                   useUnifiedComplete = this._prefs.getBoolPref(aData);
@@ -820,16 +826,17 @@ file, You can obtain one at http://mozil
             // a URL.
             action.params = {
               url: params,
             }
             return action;
           }
 
           for (let key of [
+            "engineName",
             "input",
             "searchQuery",
             "searchSuggestion",
           ]) {
             if (action.params[key]) {
               action.params[key] = decodeURIComponent(action.params[key]);
             }
           }
@@ -918,16 +925,24 @@ file, You can obtain one at http://mozil
           return this.mController.handleDelete();
         ]]></body>
       </method>
 
       <field name="_userMadeSearchSuggestionsChoice"><![CDATA[
         false
       ]]></field>
 
+      <method name="_cacheUserMadeSearchSuggestionsChoice">
+        <body><![CDATA[
+          this._userMadeSearchSuggestionsChoice =
+            this._prefs.getBoolPref("userMadeSearchSuggestionsChoice") ||
+            this._prefs.getBoolPref("suggest.searches");
+        ]]></body>
+      </method>
+
       <property name="shouldShowSearchSuggestionsNotification" readonly="true">
         <getter><![CDATA[
           return !this._userMadeSearchSuggestionsChoice &&
                  !this.inPrivateContext &&
                  // When _urlbarFocused is true, tabbrowser would close the
                  // popup if it's opened here, so don't show the notification.
                  !gBrowser.selectedBrowser._urlbarFocused &&
                  Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -5,17 +5,17 @@
 <panel id="identity-popup"
        type="arrow"
        hidden="true"
        onpopupshown="gIdentityHandler.onPopupShown(event);"
        onpopuphidden="gIdentityHandler.onPopupHidden(event);"
        orient="vertical">
 
   <broadcasterset>
-    <broadcaster id="identity-popup-content-host" class="identity-popup-headline" crop="end"/>
+    <broadcaster id="identity-popup-content-host" class="identity-popup-headline" crop="start"/>
     <broadcaster id="identity-popup-mcb-learn-more" class="text-link plain" value="&identity.learnMore;"/>
   </broadcasterset>
 
   <panelmultiview id="identity-popup-multiView"
                   mainViewId="identity-popup-mainView">
     <panelview id="identity-popup-mainView" flex="1">
 
       <!-- Security Section -->
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-html[dir="rtl"]:not(.outer-html) {
+html[dir="rtl"]:not(#outer-html) {
   /* Temporary work around and visual hack -
    * Scope the root body overflow visible to remove the scroll bars that appear
    * inside an iFrame. Can remove this rule after core layout fix for
    * https://bugzilla.mozilla.org/show_bug.cgi?id=1204680 has landed.
    */
   overflow: hidden;
 }
 
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -14,16 +14,20 @@ function EdgeProfileMigrator() {
 
 EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 EdgeProfileMigrator.prototype.getResources = function() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
     MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
   ];
+  let windowsVaultFormPasswordsMigrator =
+    MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+  windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
+  resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
 /* Somewhat counterintuitively, this returns:
  * - |null| to indicate "There is only 1 (default) profile" (on win10+)
  * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
  * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
  */
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -13,28 +13,32 @@ const kLoginsKey = "Software\\Microsoft\
 const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
 
 Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
+
 
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
+let CtypesKernelHelpers = MSMigrationUtils.CtypesKernelHelpers;
+
 ////////////////////////////////////////////////////////////////////////////////
 //// Resources
 
 
 function History() {
 }
 
 History.prototype = {
@@ -121,22 +125,29 @@ History.prototype = {
         aCallback(this._success);
       }
     });
   }
 };
 
 // IE form password migrator supporting windows from XP until 7 and IE from 7 until 11
 function IE7FormPasswords () {
+  // used to distinguish between this migrator and other passwords migrators in tests.
+  this.name = "IE7FormPasswords";
 }
 
 IE7FormPasswords.prototype = {
   type: MigrationUtils.resourceTypes.PASSWORDS,
 
   get exists() {
+    // work only on windows until 7
+    if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+      return false;
+    }
+
     try {
       let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
       let key = Cc["@mozilla.org/windows-registry-key;1"].
                 createInstance(nsIWindowsRegKey);
       key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
                nsIWindowsRegKey.ACCESS_READ);
       let count = key.valueCount;
       key.close();
@@ -166,17 +177,17 @@ IE7FormPasswords.prototype = {
     aCallback(true);
   },
 
   /**
    * Migrate the logins that were saved for the uris arguments.
    * @param {nsIURI[]} uris - the uris that are going to be migrated.
    */
   _migrateURIs(uris) {
-    this.ctypesHelpers = new MSMigrationUtils.CtypesHelpers();
+    this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
     this._crypto = new OSCrypto();
     let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
     let key = Cc["@mozilla.org/windows-registry-key;1"].
               createInstance(nsIWindowsRegKey);
     key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
              nsIWindowsRegKey.ACCESS_READ);
 
     let urlsSet = new Set(); // set of the already processed urls.
@@ -235,72 +246,36 @@ IE7FormPasswords.prototype = {
       Cu.reportError("We failed to decrypt and import some logins. " +
                      "This is likely because we didn't find the URLs where these " +
                      "passwords were submitted in the IE history and which are needed to be used " +
                      "as keys in the decryption.");
     }
 
     key.close();
     this._crypto.finalize();
-    this.ctypesHelpers.finalize();
+    this.ctypesKernelHelpers.finalize();
   },
 
   _crypto: null,
 
   /**
    * Add the logins to the password manager.
    * @param {Object[]} logins - array of the login details.
    */
   _addLogins(ieLogins) {
-    function addLogin(login, existingLogins) {
-      // Add the login only if it doesn't already exist
-      // if the login is not already available, it s going to be added or merged with another
-      // login
-      if (existingLogins.some(l => login.matches(l, true))) {
-        return;
-      }
-      let isUpdate = false; // the login is just an update for an old one
-      for (let existingLogin of existingLogins) {
-        if (login.username == existingLogin.username && login.password != existingLogin.password) {
-          // if a login with the same username and different password already exists and it's older
-          // than the current one, that login needs to be updated using the current one details
-          if (login.timePasswordChanged > existingLogin.timePasswordChanged) {
-            // Bug 1187190: Password changes should be propagated depending on timestamps.
-
-            // the existing login password and timestamps should be updated
-            let propBag = Cc["@mozilla.org/hash-property-bag;1"].
-                          createInstance(Ci.nsIWritablePropertyBag);
-            propBag.setProperty("password", login.password);
-            propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
-            Services.logins.modifyLogin(existingLogin, propBag);
-            // make sure not to add the new login
-            isUpdate = true;
-          }
-        }
-      }
-      // if the new login is not an update, add it.
-      if (!isUpdate) {
-        Services.logins.addLogin(login);
-      }
-    }
-
     for (let ieLogin of ieLogins) {
       try {
-        let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
-
-        login.init(ieLogin.url, "", null,
-                   ieLogin.username, ieLogin.password, "", "");
-        login.QueryInterface(Ci.nsILoginMetaInfo);
-        login.timeCreated = ieLogin.creation;
-        login.timeLastUsed = ieLogin.creation;
-        login.timePasswordChanged = ieLogin.creation;
-        // login.timesUsed is going to set to the default value 1
-        // Add the login only if there's not an existing entry
-        let existingLogins = Services.logins.findLogins({}, login.hostname, "", null);
-        addLogin(login, existingLogins);
+        // create a new login
+        let login = {
+          username: ieLogin.username,
+          password: ieLogin.password,
+          hostname: ieLogin.url,
+          timeCreated: ieLogin.creation,
+          };
+        LoginHelper.maybeImportLogin(login);
       } catch (e) {
         Cu.reportError(e);
       }
     }
   },
 
   /**
    * Extract the details of one or more logins from the raw decrypted data.
@@ -360,17 +335,17 @@ IE7FormPasswords.prototype = {
                                               loginItem.ptr);
     // currentLoginData.dataMax is the data count: each username and password is considered as
     // a data. So, the number of logins is the number of data dived by 2
     let numLogins = currentLoginData.dataMax / 2;
     for (let n = 0; n < numLogins; n++) {
       // Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
       // current login
       let currentLoginItem = currentLoginItemPointer.contents;
-      let creation = this.ctypesHelpers.
+      let creation = this.ctypesKernelHelpers.
                      fileTimeToSecondsSinceEpoch(currentLoginItem.hiDateTime,
                                                  currentLoginItem.loDateTime) * 1000;
       let currentResult = {
         creation: creation,
         url: url,
       };
       // The username is UTF-16 and null-terminated.
       currentResult.username =
@@ -526,16 +501,20 @@ IEProfileMigrator.prototype.getResources
   , new History()
   , MSMigrationUtils.getCookiesMigrator()
   , new Settings()
   ];
   // Only support the form password migrator for Windows XP to 7.
   if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
     resources.push(new IE7FormPasswords());
   }
+  let windowsVaultFormPasswordsMigrator =
+    MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+  windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
+  resources.push(windowsVaultFormPasswordsMigrator);
   return [r for each (r in resources) if (r.exists)];
 };
 
 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
   get: function IE_get_sourceHomePageURL() {
     let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                                       kMainKey, "Default_Page_URL");
     let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -3,92 +3,116 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["MSMigrationUtils"];
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 
 const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
 const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
 const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
 const EDGE_READINGLIST = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+const FREE_CLOSE_FAILED = 0;
+const INTERNET_EXPLORER_EDGE_GUID = [0x3CCD5499,
+                                     0x4B1087A8,
+                                     0x886015A2,
+                                     0x553BDD88];
+const RESULT_SUCCESS = 0;
+const VAULT_ENUMERATE_ALL_ITEMS = 512;
+const WEB_CREDENTIALS_VAULT_ID = [0x4BF4C442,
+                                  0x41A09B8A,
+                                  0x4ADD80B3,
+                                  0x28DB4D70];
 
 Cu.importGlobalProperties(["File"]);
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Helpers.
 
-function CtypesHelpers() {
+const wintypes = {
+  BOOL: ctypes.int,
+  DWORD: ctypes.uint32_t,
+  DWORDLONG: ctypes.uint64_t,
+  CHAR: ctypes.char,
+  PCHAR: ctypes.char.ptr,
+  LPCWSTR: ctypes.char16_t.ptr,
+  PDWORD: ctypes.uint32_t.ptr,
+  VOIDP: ctypes.voidptr_t,
+  WORD: ctypes.uint16_t,
+}
+
+// TODO: Bug 1202978 - Refactor MSMigrationUtils ctypes helpers
+function CtypesKernelHelpers() {
   this._structs = {};
   this._functions = {};
   this._libs = {};
 
-  const WORD = ctypes.uint16_t;
-  const DWORD = ctypes.uint32_t;
-  const BOOL = ctypes.int;
-
-  this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
-    {wYear: WORD},
-    {wMonth: WORD},
-    {wDayOfWeek: WORD},
-    {wDay: WORD},
-    {wHour: WORD},
-    {wMinute: WORD},
-    {wSecond: WORD},
-    {wMilliseconds: WORD}
+  this._structs.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
+    {wYear: wintypes.WORD},
+    {wMonth: wintypes.WORD},
+    {wDayOfWeek: wintypes.WORD},
+    {wDay: wintypes.WORD},
+    {wHour: wintypes.WORD},
+    {wMinute: wintypes.WORD},
+    {wSecond: wintypes.WORD},
+    {wMilliseconds: wintypes.WORD}
   ]);
 
-  this._structs.FILETIME = new ctypes.StructType('FILETIME', [
-    {dwLowDateTime: DWORD},
-    {dwHighDateTime: DWORD}
+  this._structs.FILETIME = new ctypes.StructType("FILETIME", [
+    {dwLowDateTime: wintypes.DWORD},
+    {dwHighDateTime: wintypes.DWORD}
   ]);
 
   try {
     this._libs.kernel32 = ctypes.open("Kernel32");
+
     this._functions.FileTimeToSystemTime =
       this._libs.kernel32.declare("FileTimeToSystemTime",
                                   ctypes.default_abi,
-                                  BOOL,
+                                  wintypes.BOOL,
                                   this._structs.FILETIME.ptr,
                                   this._structs.SYSTEMTIME.ptr);
   } catch (ex) {
     this.finalize();
   }
 }
 
-CtypesHelpers.prototype = {
+CtypesKernelHelpers.prototype = {
   /**
    * Must be invoked once after last use of any of the provided helpers.
    */
   finalize() {
     this._structs = {};
     this._functions = {};
     for each (let lib in this._libs) {
       try {
         lib.close();
       } catch (ex) {}
     }
     this._libs = {};
   },
 
-  /**
+   /**
    * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
    * and then deduces the number of seconds since the epoch (which
    * is the data we want for the cookie expiry date).
    *
    * @param aTimeHi
    *        Least significant DWORD.
    * @param aTimeLo
    *        Most significant DWORD.
@@ -111,16 +135,145 @@ CtypesHelpers.prototype = {
                                systemTime.wDay,
                                systemTime.wHour,
                                systemTime.wMinute,
                                systemTime.wSecond,
                                systemTime.wMilliseconds) / 1000);
   }
 };
 
+function CtypesVaultHelpers() {
+  this._structs = {};
+  this._functions = {};
+  // the size of the vault handle in 32 bits version is 32 and 64 in 64 bits version
+  if (wintypes.VOIDP.size == 4) {
+    this._vaultHandleType = wintypes.DWORD;
+  } else {
+    this._vaultHandleType = wintypes.DWORDLONG;
+  }
+
+  this._structs.GUID = new ctypes.StructType("GUID", [
+    {id: wintypes.DWORD.array(4)},
+  ]);
+
+  this._structs.VAULT_ITEM_ELEMENT = new ctypes.StructType("VAULT_ITEM_ELEMENT", [
+    // not documented
+    {schemaElementId: wintypes.DWORD},
+    // not documented
+    {unknown1: wintypes.DWORD},
+    // vault type
+    {type: wintypes.DWORD},
+    // not documented
+    {unknown2: wintypes.DWORD},
+    // value of the item
+    {itemValue: wintypes.LPCWSTR},
+    // not documented
+    {unknown3: wintypes.CHAR.array(12)},
+  ]);
+
+  this._structs.VAULT_ELEMENT = new ctypes.StructType("VAULT_ELEMENT", [
+    // vault item schemaId
+    {schemaId: this._structs.GUID},
+    // a pointer to the name of the browser VAULT_ITEM_ELEMENT
+    {pszCredentialFriendlyName: wintypes.LPCWSTR},
+    // a pointer to the url VAULT_ITEM_ELEMENT
+    {pResourceElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // a pointer to the username VAULT_ITEM_ELEMENT
+    {pIdentityElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // not documented
+    {pAuthenticatorElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // not documented
+    {pPackageSid: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // time stamp in local format
+    {lowLastModified: wintypes.DWORD},
+    {highLastModified: wintypes.DWORD},
+    // not documented
+    {flags: wintypes.DWORD},
+    // not documented
+    {dwPropertiesCount: wintypes.DWORD},
+    // not documented
+    {pPropertyElements: this._structs.VAULT_ITEM_ELEMENT.ptr},
+  ]);
+
+  try {
+    this._vaultcliLib = ctypes.open("vaultcli.dll");
+
+    this._functions.VaultOpenVault =
+      this._vaultcliLib.declare("VaultOpenVault",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // GUID
+                                this._structs.GUID.ptr,
+                                // Flags
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType.ptr);
+    this._functions.VaultEnumerateItems =
+      this._vaultcliLib.declare("VaultEnumerateItems",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType,
+                                // Flags
+                                wintypes.DWORD,
+                                // Items Count
+                                wintypes.PDWORD,
+                                // Items
+                                ctypes.voidptr_t);
+    this._functions.VaultCloseVault =
+      this._vaultcliLib.declare("VaultCloseVault",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType);
+    this._functions.VaultGetItem =
+      this._vaultcliLib.declare("VaultGetItem",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType,
+                                // Schema Id
+                                this._structs.GUID.ptr,
+                                // Resource
+                                this._structs.VAULT_ITEM_ELEMENT.ptr,
+                                // Identity
+                                this._structs.VAULT_ITEM_ELEMENT.ptr,
+                                // Package Sid
+                                this._structs.VAULT_ITEM_ELEMENT.ptr,
+                                // HWND Owner
+                                wintypes.DWORD,
+                                // Flags
+                                wintypes.DWORD,
+                                // Items
+                                this._structs.VAULT_ELEMENT.ptr.ptr);
+    this._functions.VaultFree =
+      this._vaultcliLib.declare("VaultFree",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Memory
+                                this._structs.VAULT_ELEMENT.ptr);
+  } catch (ex) {
+    this.finalize();
+  }
+}
+
+CtypesVaultHelpers.prototype = {
+  /**
+   * Must be invoked once after last use of any of the provided helpers.
+   */
+  finalize() {
+    this._structs = {};
+    this._functions = {};
+    try {
+      this._vaultcliLib.close();
+    } catch (ex) {}
+    this._vaultcliLib = null;
+  }
+}
+
 /**
  * Checks whether an host is an IP (v4 or v6) address.
  *
  * @param aHost
  *        The host to check.
  * @return whether aHost is an IP address.
  */
 function hostIsIPAddress(aHost) {
@@ -157,17 +310,17 @@ function getEdgeLocalDataFolder() {
         gEdgeDir = subDir;
         return subDir.clone();
       }
     }
   } catch (ex) {
     Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
   }
   return null;
-}
+};
 
 
 function Bookmarks(migrationType) {
   this._migrationType = migrationType;
 }
 
 Bookmarks.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
@@ -425,17 +578,17 @@ Cookies.prototype = {
           folders.push(folder);
         }
       }
     }
     return this.__cookiesFolders = folders.length ? folders : null;
   },
 
   migrate(aCallback) {
-    this.ctypesHelpers = new CtypesHelpers();
+    this.ctypesKernelHelpers = new CtypesKernelHelpers();
 
     let cookiesGenerator = (function genCookie() {
       let success = false;
       let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
                       this.__cookiesFolders : [this.__cookiesFolder];
       for (let folder of folders) {
         let entries = folder.directoryEntries;
         while (entries.hasMoreElements()) {
@@ -452,17 +605,17 @@ Cookies.prototype = {
               cookiesGenerator.next();
             } catch (ex) {}
           });
 
           yield undefined;
         }
       }
 
-      this.ctypesHelpers.finalize();
+      this.ctypesKernelHelpers.finalize();
 
       aCallback(success);
     }).apply(this);
     cookiesGenerator.next();
   },
 
   _readCookieFile(aFile, aCallback) {
     let fileReader = Cc["@mozilla.org/files/filereader;1"].
@@ -528,34 +681,173 @@ Cookies.prototype = {
       if (host.length > 0) {
         // Fist delete any possible extant matching host cookie.
         Services.cookies.remove(host, name, path, false);
         // Now make it a domain cookie.
         if (host[0] != "." && !hostIsIPAddress(host))
           host = "." + host;
       }
 
-      let expireTime = this.ctypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+      let expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
                                                                       Number(expireTimeLo));
       Services.cookies.add(host,
                            path,
                            name,
                            value,
                            Number(flags) & 0x1, // secure
                            false, // httpOnly
                            false, // session
                            expireTime);
     }
   }
 };
 
+// Migrator for form passwords on Windows 8 and higher.
+function WindowsVaultFormPasswords () {
+}
+
+WindowsVaultFormPasswords.prototype = {
+  type: MigrationUtils.resourceTypes.PASSWORDS,
+
+  get exists() {
+    // work only on windows 8+
+    if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+      // check if there are passwords available for migration.
+      return this.migrate(() => {}, true);
+    }
+    return false;
+  },
+
+  /**
+   * If aOnlyCheckExists is false, import the form passwords on Windows 8 and higher from the vault
+   * and then call the aCallback.
+   * Otherwise, check if there are passwords in the vault.
+   * @param {function} aCallback - a callback called when the migration is done.
+   * @param {boolean} [aOnlyCheckExists=false] - if aOnlyCheckExists is true, just check if there are some
+   * passwords to migrate. Import the passwords from the vault and call aCallback otherwise.
+   * @return true if there are passwords in the vault and aOnlyCheckExists is set to true,
+   * false if there is no password in the vault and aOnlyCheckExists is set to true, undefined if
+   * aOnlyCheckExists is set to false.
+   */
+  migrate(aCallback, aOnlyCheckExists = false) {
+    // check if the vault item is an IE/Edge one
+    function _isIEOrEdgePassword(id) {
+      return id[0] == INTERNET_EXPLORER_EDGE_GUID[0] &&
+             id[1] == INTERNET_EXPLORER_EDGE_GUID[1] &&
+             id[2] == INTERNET_EXPLORER_EDGE_GUID[2] &&
+             id[3] == INTERNET_EXPLORER_EDGE_GUID[3];
+    }
+
+    let ctypesVaultHelpers = new CtypesVaultHelpers();
+    let ctypesKernelHelpers = new CtypesKernelHelpers();
+    let migrationSucceeded = true;
+    let successfulVaultOpen = false;
+    let error, vault;
+    try {
+
+      // web credentials vault id
+      let vaultGuid = new ctypesVaultHelpers._structs.GUID(WEB_CREDENTIALS_VAULT_ID);
+      // number of available vaults
+      let vaultCount = new wintypes.DWORD;
+      error = new wintypes.DWORD;
+      // web credentials vault
+      vault = new ctypesVaultHelpers._vaultHandleType;
+      // open the current vault using the vaultGuid
+      error = ctypesVaultHelpers._functions.VaultOpenVault(vaultGuid.address(), 0, vault.address());
+      if (error != RESULT_SUCCESS) {
+        throw new Error("Unable to open Vault: " + error);
+      }
+      successfulVaultOpen = true;
+
+      let item = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr;
+      let itemCount = new wintypes.DWORD;
+      // enumerate all the available items. This api is going to return a table of all the
+      // available items and item is going to point to the first element of this table.
+      error = ctypesVaultHelpers._functions.VaultEnumerateItems(vault, VAULT_ENUMERATE_ALL_ITEMS,
+                                                                itemCount.address(),
+                                                                item.address());
+      if (error != RESULT_SUCCESS) {
+        throw new Error("Unable to enumerate Vault items: " + error);
+      }
+      for (let j = 0; j < itemCount.value; j++) {
+        try {
+          // if it's not an ie/edge password, skip it
+          if (!_isIEOrEdgePassword(item.contents.schemaId.id)) {
+            continue;
+          }
+          // if aOnlyCheckExists is set to true, the purpose of the call is to return true if there is at
+          // least a password which is true in this case because a password was by now already found
+          if (aOnlyCheckExists) {
+            return true;
+          }
+          let url = item.contents.pResourceElement.contents.itemValue.readString();
+          let username = item.contents.pIdentityElement.contents.itemValue.readString();
+          // the current login credential object
+          let credential = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr;
+          error = ctypesVaultHelpers._functions.VaultGetItem(vault,
+                                                             item.contents.schemaId.address(),
+                                                             item.contents.pResourceElement,
+                                                             item.contents.pIdentityElement, null,
+                                                             0, 0, credential.address());
+          if (error != RESULT_SUCCESS) {
+            throw new Error("Unable to get item: " + error);
+          }
+
+          let password = credential.contents.pAuthenticatorElement.contents.itemValue.readString();
+          let creation = ctypesKernelHelpers.
+                         fileTimeToSecondsSinceEpoch(item.contents.highLastModified,
+                                                     item.contents.lowLastModified) * 1000;
+          // create a new login
+          let login = {
+            username, password,
+            hostname: NetUtil.newURI(url).prePath,
+            timeCreated: creation,
+          };
+          LoginHelper.maybeImportLogin(login);
+
+          // close current item
+          error = ctypesVaultHelpers._functions.VaultFree(credential);
+          if (error == FREE_CLOSE_FAILED) {
+            throw new Error("Unable to free item: " + error);
+          }
+        } catch (e) {
+          migrationSucceeded = false;
+          Cu.reportError(e);
+        } finally {
+          // move to next item in the table returned by VaultEnumerateItems
+          item = item.increment();
+        }
+      }
+    } catch (e) {
+      Cu.reportError(e);
+      migrationSucceeded = false;
+    } finally {
+      if (successfulVaultOpen) {
+        // close current vault
+        error = ctypesVaultHelpers._functions.VaultCloseVault(vault);
+        if (error == FREE_CLOSE_FAILED) {
+          Cu.reportError("Unable to close vault: " + error);
+        }
+      }
+      ctypesKernelHelpers.finalize();
+      ctypesVaultHelpers.finalize();
+      aCallback(migrationSucceeded);
+    }
+    if (aOnlyCheckExists) {
+      return false;
+    }
+  }
+};
 
 var MSMigrationUtils = {
   MIGRATION_TYPE_IE: 1,
   MIGRATION_TYPE_EDGE: 2,
-  CtypesHelpers: CtypesHelpers,
+  CtypesKernelHelpers: CtypesKernelHelpers,
   getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
     return new Bookmarks(migrationType);
   },
   getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
     return new Cookies(migrationType);
   },
+  getWindowsVaultFormPasswordsMigrator() {
+    return new WindowsVaultFormPasswords();
+  },
 };
--- a/browser/components/migration/tests/unit/test_Edge_availability.js
+++ b/browser/components/migration/tests/unit/test_Edge_availability.js
@@ -1,11 +1,12 @@
 const EDGE_AVAILABLE_MIGRATIONS = 
   MigrationUtils.resourceTypes.COOKIES |
-  MigrationUtils.resourceTypes.BOOKMARKS;
+  MigrationUtils.resourceTypes.BOOKMARKS |
+  MigrationUtils.resourceTypes.PASSWORDS;
 
 add_task(function* () {
   let migrator = MigrationUtils.getMigrator("edge");
   Cu.import("resource://gre/modules/AppConstants.jsm");
   Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
                "Edge should be available for migration if and only if we're on Win 10+");
   if (migrator) {
     let migratableData = migrator.getMigrateData(null, false);
--- a/browser/components/migration/tests/unit/test_IE7_passwords.js
+++ b/browser/components/migration/tests/unit/test_IE7_passwords.js
@@ -2,16 +2,17 @@ Cu.import("resource://gre/modules/AppCon
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
 
 const CRYPT_PROTECT_UI_FORBIDDEN = 1;
+const IE7_FORM_PASSWORDS_MIGRATOR_NAME = "IE7FormPasswords";
 const LOGINS_KEY =  "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
 const EXTENSION = "-backup";
 const TESTED_WEBSITES = {
   twitter: {
     uri: makeURI("https://twitter.com"),
     hash: "A89D42BC6406E27265B1AD0782B6F376375764A301",
     data: [12, 0, 0, 0, 56, 0, 0, 0, 38, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 8, 0, 0, 0, 18, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 9, 0, 0, 0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 0, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 0, 0, 0],
     logins: [
@@ -268,17 +269,17 @@ function createRegistryPath(path) {
 }
 
 function getFirstResourceOfType(type) {
   let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=ie"]
                  .createInstance(Ci.nsISupports)
                  .wrappedJSObject;
   let migrators = migrator.getResources();
   for (let m of migrators) {
-    if (m.type == type) {
+    if (m.name == IE7_FORM_PASSWORDS_MIGRATOR_NAME && m.type == type) {
       return m;
     }
   }
   throw new Error("failed to find the " + type + " migrator");
 }
 
 function makeURI(aURL) {
   return Services.io.newURI(aURL, null, null);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2798,17 +2798,17 @@ var DefaultBrowserCheck = {
       ShellService.setDefaultBrowser(claimAllTypes, false);
 
       if (this._setAsDefaultTimer) {
         this._setAsDefaultTimer.cancel();
       }
 
       this._setAsDefaultButtonClickStartTime = Math.floor(Date.now() / 1000);
       this._setAsDefaultTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-      this._setAsDefaultTimer.init(function() {
+      this._setAsDefaultTimer.init(() => {
         let isDefault = false;
         let isDefaultError = false;
         try {
           isDefault = ShellService.isDefaultBrowser(true, false);
         } catch (ex) {
           isDefaultError = true;
         }
 
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -56,25 +56,45 @@ var gSearchPane = {
 
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
     window.addEventListener("unload", () => {
       Services.obs.removeObserver(this, "browser-search-engine-modified", false);
     });
 
     this._initAutocomplete();
 
+    let suggestsPref =
+      document.getElementById("browser.search.suggest.enabled");
+    suggestsPref.addEventListener("change", () => {
+      this.updateSuggestsCheckbox();
+    });
+    this.updateSuggestsCheckbox();
+  },
+
+  updateSuggestsCheckbox() {
     let urlbarSuggests = document.getElementById("urlBarSuggestion");
-    urlbarSuggests.hidden = !Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
+    urlbarSuggests.hidden =
+      !Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
 
-    let suggestsPref = document.getElementById("browser.search.suggest.enabled")
-    let updateSuggestsCheckbox = () => {
-      urlbarSuggests.disabled = !suggestsPref.value;
+    let suggestsPref =
+      document.getElementById("browser.search.suggest.enabled");
+    let permanentPB =
+      Services.prefs.getBoolPref("browser.privatebrowsing.autostart");
+    urlbarSuggests.disabled = !suggestsPref.value || permanentPB;
+
+    let urlbarSuggestsPref =
+      document.getElementById("browser.urlbar.suggest.searches");
+    urlbarSuggests.checked = urlbarSuggestsPref.value;
+    if (urlbarSuggests.disabled) {
+      urlbarSuggests.checked = false;
     }
-    suggestsPref.addEventListener("change", updateSuggestsCheckbox);
-    updateSuggestsCheckbox();
+
+    let permanentPBLabel =
+      document.getElementById("urlBarSuggestionPermanentPBLabel");
+    permanentPBLabel.hidden = urlbarSuggests.hidden || !permanentPB;
   },
 
   buildDefaultEngineDropDown: function() {
     // This is called each time something affects the list of engines.
     let list = document.getElementById("defaultEngine");
     let currentEngine;
 
     // First, try to preserve the current selection.
--- a/browser/components/preferences/in-content/search.xul
+++ b/browser/components/preferences/in-content/search.xul
@@ -36,22 +36,25 @@
       <label>&chooseYourDefaultSearchEngine.label;</label>
       <menulist id="defaultEngine">
         <menupopup/>
       </menulist>
       <checkbox id="suggestionsInSearchFieldsCheckbox"
                 label="&provideSearchSuggestions.label;"
                 accesskey="&provideSearchSuggestions.accesskey;"
                 preference="browser.search.suggest.enabled"/>
-      <hbox class="indent">
+      <vbox class="indent">
         <checkbox id="urlBarSuggestion" label="&showURLBarSuggestions.label;"
-                  hidden="true"
                   accesskey="&showURLBarSuggestions.accesskey;"
                   preference="browser.urlbar.suggest.searches"/>
-      </hbox>
+        <hbox id="urlBarSuggestionPermanentPBLabel"
+              align="center" class="indent">
+          <label flex="1">&urlBarSuggestionsPermanentPB.label;</label>
+        </hbox>
+      </vbox>
       <checkbox id="redirectSearchCheckbox"
                 label="&redirectWindowsSearch.label;"
                 accesskey="&redirectWindowsSearch.accesskey;"
                 preference="browser.search.redirectWindowsSearch"/>
     </groupbox>
 
     <groupbox id="oneClickSearchProvidersGroup" data-category="paneSearch">
       <caption label="&oneClickSearchEngines.label;"/>
--- a/browser/locales/en-US/chrome/browser/preferences/search.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/search.dtd
@@ -6,16 +6,17 @@
 
 <!ENTITY chooseYourDefaultSearchEngine.label   "Choose your default search engine. &brandShortName; uses it in the location bar, search bar, and start page.">
 
 <!ENTITY provideSearchSuggestions.label        "Provide search suggestions">
 <!ENTITY provideSearchSuggestions.accesskey    "s">
 
 <!ENTITY showURLBarSuggestions.label           "Show search suggestions in location bar results">
 <!ENTITY showURLBarSuggestions.accesskey       "l">
+<!ENTITY urlBarSuggestionsPermanentPB.label    "Search suggestions will not be shown in location bar results because you have configured &brandShortName; to never remember history.">
 
 <!ENTITY redirectWindowsSearch.label "Use this search engine for searches from Windows">
 <!ENTITY redirectWindowsSearch.accesskey "W">
 
 <!ENTITY oneClickSearchEngines.label           "One-click search engines">
 
 <!ENTITY chooseWhichOneToDisplay.label         "The search bar lets you search alternate engines directly. Choose which ones to display.">
 
--- a/docshell/shistory/nsSHEntryShared.cpp
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -33,17 +33,17 @@ uint64_t gSHEntrySharedID = 0;
 // Default this to time out unused content viewers after 30 minutes
 #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
 
 typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
 class HistoryTracker final : public HistoryTrackerBase
 {
 public:
   explicit HistoryTracker(uint32_t aTimeout)
-    : HistoryTrackerBase(1000 * aTimeout / 2)
+    : HistoryTrackerBase(1000 * aTimeout / 2, "HistoryTracker")
   {
   }
 
 protected:
   virtual void NotifyExpired(nsSHEntryShared* aObj)
   {
     RemoveObject(aObj);
     aObj->Expire();
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -13,32 +13,34 @@
 #include "mozilla/dom/StructuredCloneHelper.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Maybe.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDocument.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
+#include "nsNetUtil.h"
 #include "nsPerformance.h"
 #include "ScriptSettings.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "xpcprivate.h"
 #include "nsContentUtils.h"
 #include "nsDocShell.h"
 #include "nsProxyRelease.h"
 #include "mozilla/ConsoleTimelineMarker.h"
 #include "mozilla/TimestampTimelineMarker.h"
 
 #include "nsIConsoleAPIStorage.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadContext.h"
 #include "nsIProgrammingLanguage.h"
+#include "nsISensitiveInfoHiddenURI.h"
 #include "nsIServiceManager.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIWebNavigation.h"
 #include "nsIXPConnect.h"
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "nsIProfiler.h"
 #endif
@@ -1205,16 +1207,29 @@ Console::ProcessCallData(ConsoleCallData
   } else {
     MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
     event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
     event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
   }
 
   event.mLevel = aData->mMethodString;
   event.mFilename = frame.mFilename;
+
+  nsCOMPtr<nsIURI> filenameURI;
+  nsAutoCString pass;
+  if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
+      NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
+    nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
+    nsAutoCString spec;
+    if (safeURI &&
+        NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
+      CopyUTF8toUTF16(spec, event.mFilename);
+    }
+  }
+
   event.mLineNumber = frame.mLineNumber;
   event.mColumnNumber = frame.mColumnNumber;
   event.mFunctionName = frame.mFunctionName;
   event.mTimeStamp = aData->mTimeStamp;
   event.mPrivate = aData->mPrivate;
 
   switch (aData->mMethodName) {
     case MethodLog:
--- a/dom/base/nsContentList.cpp
+++ b/dom/base/nsContentList.cpp
@@ -25,17 +25,17 @@
 #include "nsGenericHTMLElement.h"
 #include "jsfriendapi.h"
 #include <algorithm>
 #include "mozilla/dom/NodeInfoInlines.h"
 
 // Form related includes
 #include "nsIDOMHTMLFormElement.h"
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 
 #ifdef DEBUG_CONTENT_LIST
 #include "nsIContentIterator.h"
 #define ASSERT_IN_SYNC AssertInSync()
 #else
 #define ASSERT_IN_SYNC PR_BEGIN_MACRO PR_END_MACRO
 #endif
 
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -18,16 +18,17 @@
 #include "nsIDocShell.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMWindow.h"
 #include "nsIContent.h"
 #include "nsILoadContext.h"
 #include "nsCOMArray.h"
 #include "nsContentUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
 
 using mozilla::LogLevel;
 
 NS_IMPL_ISUPPORTS(nsContentPolicy, nsIContentPolicy)
 
 static PRLogModuleInfo* gConPolLog;
 
 nsresult
@@ -114,27 +115,41 @@ nsContentPolicy::CheckPolicy(CPMethod   
         if (doc) {
             requestingLocation = doc->GetDocumentURI();
         }
     }
 
     nsContentPolicyType externalType =
         nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
 
+    nsContentPolicyType externalTypeOrScript =
+        nsContentUtils::InternalContentPolicyTypeToExternalOrScript(contentType);
+
+    nsCOMPtr<nsIContentPolicy> mixedContentBlocker =
+        do_GetService(NS_MIXEDCONTENTBLOCKER_CONTRACTID);
+
     /* 
      * Enumerate mPolicies and ask each of them, taking the logical AND of
      * their permissions.
      */
     nsresult rv;
     nsCOMArray<nsIContentPolicy> entries;
     mPolicies.GetEntries(entries);
     int32_t count = entries.Count();
     for (int32_t i = 0; i < count; i++) {
         /* check the appropriate policy */
-        rv = (entries[i]->*policyMethod)(externalType, contentLocation,
+        // Send the internal content policy type to the mixed content blocker
+        // which needs to know about TYPE_INTERNAL_WORKER,
+        // TYPE_INTERNAL_SHARED_WORKER and TYPE_INTERNAL_SERVICE_WORKER.
+        bool isMixedContentBlocker = mixedContentBlocker == entries[i];
+        nsContentPolicyType type = externalType;
+        if (isMixedContentBlocker) {
+            type = externalTypeOrScript;
+        }
+        rv = (entries[i]->*policyMethod)(type, contentLocation,
                                          requestingLocation, requestingContext,
                                          mimeType, extra, requestPrincipal,
                                          decision);
 
         if (NS_SUCCEEDED(rv) && NS_CP_REJECTED(*decision)) {
             /* policy says no, no point continuing to check */
             return NS_OK;
         }
--- a/dom/base/nsContentPolicyUtils.h
+++ b/dom/base/nsContentPolicyUtils.h
@@ -121,16 +121,17 @@ NS_CP_ContentTypeName(uint32_t contentTy
     CASE_RETURN( TYPE_INTERNAL_OBJECT         );
     CASE_RETURN( TYPE_INTERNAL_FRAME          );
     CASE_RETURN( TYPE_INTERNAL_IFRAME         );
     CASE_RETURN( TYPE_INTERNAL_AUDIO          );
     CASE_RETURN( TYPE_INTERNAL_VIDEO          );
     CASE_RETURN( TYPE_INTERNAL_TRACK          );
     CASE_RETURN( TYPE_INTERNAL_XMLHTTPREQUEST );
     CASE_RETURN( TYPE_INTERNAL_EVENTSOURCE    );
+    CASE_RETURN( TYPE_INTERNAL_SERVICE_WORKER );
    default:
     return "<Unknown Type>";
   }
 }
 
 #undef CASE_RETURN
 
 /* Passes on parameters from its "caller"'s context. */
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7923,16 +7923,17 @@ nsContentUtils::GetWindowRoot(nsIDocumen
 /* static */
 nsContentPolicyType
 nsContentUtils::InternalContentPolicyTypeToExternal(nsContentPolicyType aType)
 {
   switch (aType) {
   case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
   case nsIContentPolicy::TYPE_INTERNAL_WORKER:
   case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+  case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
     return nsIContentPolicy::TYPE_SCRIPT;
 
   case nsIContentPolicy::TYPE_INTERNAL_EMBED:
   case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
     return nsIContentPolicy::TYPE_OBJECT;
 
   case nsIContentPolicy::TYPE_INTERNAL_FRAME:
   case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
@@ -7947,16 +7948,32 @@ nsContentUtils::InternalContentPolicyTyp
   case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
     return nsIContentPolicy::TYPE_XMLHTTPREQUEST;
 
   default:
     return aType;
   }
 }
 
+/* static */
+nsContentPolicyType
+nsContentUtils::InternalContentPolicyTypeToExternalOrScript(nsContentPolicyType aType)
+{
+  switch (aType) {
+  case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+  case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+  case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+  case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+    return aType;
+
+  default:
+    return InternalContentPolicyTypeToExternal(aType);
+  }
+}
+
 
 nsresult
 nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
                                               nsIDocument* aDoc,
                                               nsIHttpChannel* aChannel)
 {
   NS_ENSURE_ARG_POINTER(aPrincipal);
   NS_ENSURE_ARG_POINTER(aChannel);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -949,16 +949,28 @@ public:
   static nsIContentPolicy *GetContentPolicy();
 
   /**
    * Map internal content policy types to external ones.
    */
   static nsContentPolicyType InternalContentPolicyTypeToExternal(nsContentPolicyType aType);
 
   /**
+   * Map internal content policy types to external ones or script types:
+   *   * TYPE_INTERNAL_SCRIPT
+   *   * TYPE_INTERNAL_WORKER
+   *   * TYPE_INTERNAL_SHARED_WORKER
+   *   * TYPE_INTERNAL_SERVICE_WORKER
+   *
+   *
+   * Note: DO NOT call this function unless you know what you're doing!
+   */
+  static nsContentPolicyType InternalContentPolicyTypeToExternalOrScript(nsContentPolicyType aType);
+
+  /**
    * Quick helper to determine whether there are any mutation listeners
    * of a given type that apply to this content or any of its ancestors.
    * The method has the side effect to call document's MayDispatchMutationEvent
    * using aTargetForSubtreeModified as the parameter.
    *
    * @param aNode  The node to search for listeners
    * @param aType  The type of listener (NS_EVENT_BITS_MUTATION_*)
    * @param aTargetForSubtreeModified The node which is the target of the
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3261,16 +3261,17 @@ private:
         OldWindowSize* thisItem = item;
         item = thisItem->getNext();
         delete thisItem;
         continue;
       }
       if (window == aWindow) {
         break;
       }
+      item = item->getNext();
     }
     return item;
   }
 
   static LinkedList<OldWindowSize> sList;
   nsWeakPtr mWindow;
   nsSize mSize;
 };
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1424,17 +1424,18 @@ nsDOMStyleSheetSetList::EnsureFresh()
     if (!title.IsEmpty() && !mNames.Contains(title) && !Add(title)) {
       return;
     }
   }
 }
 
 // ==================================================================
 nsIDocument::SelectorCache::SelectorCache()
-  : nsExpirationTracker<SelectorCacheKey, 4>(1000) { }
+  : nsExpirationTracker<SelectorCacheKey, 4>(1000, "nsIDocument::SelectorCache")
+{ }
 
 // CacheList takes ownership of aSelectorList.
 void nsIDocument::SelectorCache::CacheList(const nsAString& aSelector,
                                            nsCSSSelectorList* aSelectorList)
 {
   SelectorCacheKey* key = new SelectorCacheKey(aSelector);
   mTable.Put(key->mKey, aSelectorList);
   AddObject(key);
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -40,17 +40,17 @@
 #include "nsStubMutationObserver.h"
 #include "nsIChannel.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsContentList.h"
 #include "nsGkAtoms.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsStyleSet.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsAttrAndChildArray.h"
 #include "nsDOMAttributeMap.h"
 #include "nsIContentViewer.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILoadContext.h"
 #include "nsIProgressEventSink.h"
 #include "nsISecurityEventSink.h"
 #include "nsIChannelEventSink.h"
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -28,17 +28,17 @@
 #include "nsChangeHint.h"
 #include "nsCOMArray.h"
 #include "nsNodeUtils.h"
 #include "mozilla/dom/DirectionalityUtils.h"
 #include "nsBindingManager.h"
 #include "nsCCUncollectableMarker.h"
 #include "mozAutoDocUpdate.h"
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "prprf.h"
 #include "nsWrapperCacheInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsGenericDOMDataNode::nsGenericDOMDataNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsIContent(aNodeInfo)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -48,16 +48,17 @@
 #include "jsapi.h"              // for JSAutoRequest
 #include "jswrapper.h"
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
 #include "nsJSEnvironment.h"
 #include "ScriptSettings.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Likely.h"
+#include "mozilla/Snprintf.h"
 #include "mozilla/unused.h"
 
 // Other Classes
 #include "mozilla/dom/BarProps.h"
 #include "nsContentCID.h"
 #include "nsLayoutStatics.h"
 #include "nsCCUncollectableMarker.h"
 #include "mozilla/dom/workers/Workers.h"
@@ -537,16 +538,24 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsTime
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTimeout)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTimeout, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTimeout, Release)
 
+nsresult
+nsTimeout::InitTimer(uint32_t aDelay)
+{
+  return mTimer->InitWithNameableFuncCallback(
+    nsGlobalWindow::TimerCallback, this, aDelay,
+    nsITimer::TYPE_ONE_SHOT, nsGlobalWindow::TimerNameCallback);
+}
+
 // Return true if this timeout has a refcount of 1. This is used to check
 // that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout.
 bool
 nsTimeout::HasRefCntOne()
 {
   return mRefCnt.get() == 1;
 }
 
@@ -12496,17 +12505,17 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
     nsresult rv;
     timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     nsRefPtr<nsTimeout> copy = timeout;
 
-    rv = timeout->InitTimer(TimerCallback, realInterval);
+    rv = timeout->InitTimer(realInterval);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // The timeout is now also held in the timer's closure.
     unused << copy.forget();
   } else {
     // If we are frozen, however, then we instead simply set
@@ -12752,17 +12761,17 @@ nsGlobalWindow::RescheduleTimeout(nsTime
     aTimeout->mTimeRemaining = delay;
     return true;
   }
 
   aTimeout->mWhen = currentNow + delay;
 
   // Reschedule the OS timer. Don't bother returning any error codes if
   // this fails since the callers of this method don't care about them.
-  nsresult rv = aTimeout->InitTimer(TimerCallback, delay.ToMilliseconds());
+  nsresult rv = aTimeout->InitTimer(delay.ToMilliseconds());
 
   if (NS_FAILED(rv)) {
     NS_ERROR("Error initializing timer for DOM timeout!");
 
     // We failed to initialize the new OS timer, this timer does
     // us no good here so we just cancel it (just in case) and
     // null out the pointer to the OS timer, this will release the
     // OS timer. As we continue executing the code below we'll end
@@ -13052,17 +13061,17 @@ nsresult nsGlobalWindow::ResetTimersForN
       timeout->remove();
       // InsertTimeoutIntoList will addref |timeout| and reset
       // mFiringDepth.  Make sure to undo that after calling it.
       uint32_t firingDepth = timeout->mFiringDepth;
       InsertTimeoutIntoList(timeout);
       timeout->mFiringDepth = firingDepth;
       timeout->Release();
 
-      nsresult rv = timeout->InitTimer(TimerCallback, delay.ToMilliseconds());
+      nsresult rv = timeout->InitTimer(delay.ToMilliseconds());
 
       if (NS_FAILED(rv)) {
         NS_WARNING("Error resetting non background timer for DOM timeout!");
         return rv;
       }
 
       timeout = nextTimeout;
     } else {
@@ -13149,16 +13158,29 @@ nsGlobalWindow::InsertTimeoutIntoList(ns
 void
 nsGlobalWindow::TimerCallback(nsITimer *aTimer, void *aClosure)
 {
   nsRefPtr<nsTimeout> timeout = (nsTimeout *)aClosure;
 
   timeout->mWindow->RunTimeout(timeout);
 }
 
+// static
+void
+nsGlobalWindow::TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
+                                  size_t aLen)
+{
+  nsRefPtr<nsTimeout> timeout = (nsTimeout*)aClosure;
+
+  const char* filename;
+  uint32_t lineNum, column;
+  timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column);
+  snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column);
+}
+
 //*****************************************************************************
 // nsGlobalWindow: Helper Functions
 //*****************************************************************************
 
 already_AddRefed<nsIDocShellTreeOwner>
 nsGlobalWindow::GetTreeOwner()
 {
   FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
@@ -13494,17 +13516,17 @@ nsGlobalWindow::ResumeTimeouts(bool aTha
 
       // Set mWhen back to the time when the timer is supposed to
       // fire.
       t->mWhen = now + t->mTimeRemaining;
 
       t->mTimer = do_CreateInstance("@mozilla.org/timer;1");
       NS_ENSURE_TRUE(t->mTimer, NS_ERROR_OUT_OF_MEMORY);
 
-      rv = t->InitTimer(TimerCallback, delay);
+      rv = t->InitTimer(delay);
       if (NS_FAILED(rv)) {
         t->mTimer = nullptr;
         return rv;
       }
 
       // Add a reference for the new timer's closure.
       t->AddRef();
     }
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -154,21 +154,17 @@ private:
   ~nsTimeout();
 
 public:
   nsTimeout();
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTimeout)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsTimeout)
 
-  nsresult InitTimer(nsTimerCallbackFunc aFunc, uint32_t aDelay)
-  {
-    return mTimer->InitWithFuncCallback(aFunc, this, aDelay,
-                                        nsITimer::TYPE_ONE_SHOT);
-  }
+  nsresult InitTimer(uint32_t aDelay);
 
   bool HasRefCntOne();
 
   // Window for which this timeout fires
   nsRefPtr<nsGlobalWindow> mWindow;
 
   // The actual timer object
   nsCOMPtr<nsITimer> mTimer;
@@ -1423,16 +1419,18 @@ public:
   bool RescheduleTimeout(nsTimeout* aTimeout, const TimeStamp& now,
                          bool aRunningPendingTimeouts);
 
   void ClearAllTimeouts();
   // Insert aTimeout into the list, before all timeouts that would
   // fire after it, but no earlier than mTimeoutInsertionPoint, if any.
   void InsertTimeoutIntoList(nsTimeout *aTimeout);
   static void TimerCallback(nsITimer *aTimer, void *aClosure);
+  static void TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
+                                size_t aLen);
 
   // Helper Functions
   already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
   already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
   already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
   nsresult SecurityCheckURL(const char *aURL);
   bool IsPrivateBrowsing();
 
--- a/dom/base/nsIContentPolicy.idl
+++ b/dom/base/nsIContentPolicy.idl
@@ -15,17 +15,17 @@ interface nsIPrincipal;
  * Interface for content policy mechanism.  Implementations of this
  * interface can be used to control loading of various types of out-of-line
  * content, or processing of certain types of in-line content.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(3663021e-5670-496f-887b-b408d6526b5b)]
+[scriptable,uuid(ce321216-c404-40a7-a711-d80454ec6b76)]
 interface nsIContentPolicy : nsIContentPolicyBase
 {
   /**
    * Should the resource at this location be loaded?
    * ShouldLoad will be called before loading the resource at aContentLocation
    * to determine whether to start the load at all.
    *
    * @param aContentType      the type of content being tested. This will be one
--- a/dom/base/nsIContentPolicyBase.idl
+++ b/dom/base/nsIContentPolicyBase.idl
@@ -19,17 +19,17 @@ typedef unsigned long nsContentPolicyTyp
  * Interface for content policy mechanism.  Implementations of this
  * interface can be used to control loading of various types of out-of-line
  * content, or processing of certain types of in-line content.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(20f7b9bf-d7d5-4987-ade8-b7dc0398d44a)]
+[scriptable,uuid(8527ae0d-0c43-4413-bc46-85c0bcb66876)]
 interface nsIContentPolicyBase : nsISupports
 {
   /**
    * Indicates a unset or bogus policy type.
    */
   const nsContentPolicyType TYPE_INVALID = 0;
 
   /**
@@ -266,16 +266,25 @@ interface nsIContentPolicyBase : nsISupp
 
   /**
    * Indicates an internal constant for EventSource.
    *
    * This will be mapped to TYPE_DATAREQUEST.
    */
   const nsContentPolicyType TYPE_INTERNAL_EVENTSOURCE = 34;
 
+  /**
+   * Indicates an internal constant for scripts loaded through a service
+   * worker.
+   *
+   * This will be mapped to TYPE_SCRIPT before being passed to content policy
+   * implementations.
+   */
+  const nsContentPolicyType TYPE_INTERNAL_SERVICE_WORKER = 35;
+
   /* When adding new content types, please update nsContentBlocker,
    * NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
    * implementations, the static_assert in dom/cache/DBSchema.cpp,
    * and other things that are not listed here that are related to
    * nsIContentPolicy. */
 
   //////////////////////////////////////////////////////////////////////
 
--- a/dom/base/nsISimpleContentPolicy.idl
+++ b/dom/base/nsISimpleContentPolicy.idl
@@ -23,17 +23,17 @@ interface nsIDOMElement;
  * to block loads without using cross-process wrappers (CPOWs). Add-ons should
  * prefer this interface to nsIContentPolicy because it should be faster in
  * e10s. In the future, it may also be run asynchronously.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(b181c97c-9d67-4da1-95a0-e0a202e1807c)]
+[scriptable,uuid(b9df71e3-a9b3-4706-b2d5-e6c0d3d68ec7)]
 interface nsISimpleContentPolicy : nsIContentPolicyBase
 {
   /**
    * Should the resource at this location be loaded?
    * ShouldLoad will be called before loading the resource at aContentLocation
    * to determine whether to start the load at all.
    *
    * @param aContentType      the type of content being tested. This will be one
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1631,20 +1631,20 @@ nsJSContext::BeginCycleCollectionCallbac
   gCCStats.RunForgetSkippable();
 
   MOZ_ASSERT(!sICCTimer, "Tried to create a new ICC timer when one already existed.");
 
   // Create an ICC timer even if ICC is globally disabled, because we could be manually triggering
   // an incremental collection, and we want to be sure to finish it.
   CallCreateInstance("@mozilla.org/timer;1", &sICCTimer);
   if (sICCTimer) {
-    sICCTimer->InitWithFuncCallback(ICCTimerFired,
-                                    nullptr,
-                                    kICCIntersliceDelay,
-                                    nsITimer::TYPE_REPEATING_SLACK);
+    sICCTimer->InitWithNamedFuncCallback(ICCTimerFired, nullptr,
+                                         kICCIntersliceDelay,
+                                         nsITimer::TYPE_REPEATING_SLACK,
+                                         "ICCTimerFired");
   }
 }
 
 static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
 
 //static
 void
 nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
@@ -2031,24 +2031,25 @@ nsJSContext::PokeGC(JS::gcreason::Reason
 
   if (!sGCTimer) {
     // Failed to create timer (probably because we're in XPCOM shutdown)
     return;
   }
 
   static bool first = true;
 
-  sGCTimer->InitWithFuncCallback(GCTimerFired, reinterpret_cast<void *>(aReason),
-                                 aDelay
-                                 ? aDelay
-                                 : (first
-                                    ? NS_FIRST_GC_DELAY
-                                    : NS_GC_DELAY),
-                                 nsITimer::TYPE_ONE_SHOT);
-
+  sGCTimer->InitWithNamedFuncCallback(GCTimerFired,
+                                      reinterpret_cast<void *>(aReason),
+                                      aDelay
+                                      ? aDelay
+                                      : (first
+                                         ? NS_FIRST_GC_DELAY
+                                         : NS_GC_DELAY),
+                                      nsITimer::TYPE_ONE_SHOT,
+                                      "GCTimerFired");
   first = false;
 }
 
 // static
 void
 nsJSContext::PokeShrinkGCBuffers()
 {
   if (sShrinkGCBuffersTimer || sShuttingDown) {
@@ -2057,19 +2058,21 @@ nsJSContext::PokeShrinkGCBuffers()
 
   CallCreateInstance("@mozilla.org/timer;1", &sShrinkGCBuffersTimer);
 
   if (!sShrinkGCBuffersTimer) {
     // Failed to create timer (probably because we're in XPCOM shutdown)
     return;
   }
 
-  sShrinkGCBuffersTimer->InitWithFuncCallback(ShrinkGCBuffersTimerFired, nullptr,
-                                              NS_SHRINK_GC_BUFFERS_DELAY,
-                                              nsITimer::TYPE_ONE_SHOT);
+  sShrinkGCBuffersTimer->InitWithNamedFuncCallback(ShrinkGCBuffersTimerFired,
+                                                   nullptr,
+                                                   NS_SHRINK_GC_BUFFERS_DELAY,
+                                                   nsITimer::TYPE_ONE_SHOT,
+                                                   "ShrinkGCBuffersTimerFired");
 }
 
 // static
 void
 nsJSContext::PokeShrinkingGC()
 {
   if (sShrinkingGCTimer || sShuttingDown) {
     return;
@@ -2077,19 +2080,20 @@ nsJSContext::PokeShrinkingGC()
 
   CallCreateInstance("@mozilla.org/timer;1", &sShrinkingGCTimer);
 
   if (!sShrinkingGCTimer) {
     // Failed to create timer (probably because we're in XPCOM shutdown)
     return;
   }
 
-  sShrinkingGCTimer->InitWithFuncCallback(ShrinkingGCTimerFired, nullptr,
-                                          sCompactOnUserInactiveDelay,
-                                          nsITimer::TYPE_ONE_SHOT);
+  sShrinkingGCTimer->InitWithNamedFuncCallback(ShrinkingGCTimerFired, nullptr,
+                                               sCompactOnUserInactiveDelay,
+                                               nsITimer::TYPE_ONE_SHOT,
+                                               "ShrinkingGCTimerFired");
 }
 
 // static
 void
 nsJSContext::MaybePokeCC()
 {
   if (sCCTimer || sICCTimer || sShuttingDown || !sHasRunGC) {
     return;
@@ -2099,19 +2103,20 @@ nsJSContext::MaybePokeCC()
     sCCTimerFireCount = 0;
     CallCreateInstance("@mozilla.org/timer;1", &sCCTimer);
     if (!sCCTimer) {
       return;
     }
     // We can kill some objects before running forgetSkippable.
     nsCycleCollector_dispatchDeferredDeletion();
 
-    sCCTimer->InitWithFuncCallback(CCTimerFired, nullptr,
-                                   NS_CC_SKIPPABLE_DELAY,
-                                   nsITimer::TYPE_REPEATING_SLACK);
+    sCCTimer->InitWithNamedFuncCallback(CCTimerFired, nullptr,
+                                        NS_CC_SKIPPABLE_DELAY,
+                                        nsITimer::TYPE_REPEATING_SLACK,
+                                        "CCTimerFired");
   }
 }
 
 //static
 void
 nsJSContext::KillGCTimer()
 {
   if (sGCTimer) {
@@ -2258,20 +2263,21 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
       sCleanupsSinceLastGC = 0;
       sNeedsFullCC = true;
       sHasRunGC = true;
       nsJSContext::MaybePokeCC();
 
       if (aDesc.isCompartment_) {
         if (!sFullGCTimer && !sShuttingDown) {
           CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer);
-          sFullGCTimer->InitWithFuncCallback(FullGCTimerFired,
-                                             nullptr,
-                                             NS_FULL_GC_DELAY,
-                                             nsITimer::TYPE_ONE_SHOT);
+          sFullGCTimer->InitWithNamedFuncCallback(FullGCTimerFired,
+                                                  nullptr,
+                                                  NS_FULL_GC_DELAY,
+                                                  nsITimer::TYPE_ONE_SHOT,
+                                                  "FullGCTimerFired");
         }
       } else {
         nsJSContext::KillFullGCTimer();
 
         // Avoid shrinking during heavy activity, which is suggested by
         // compartment GC. We don't need to shrink after a shrinking GC as this
         // happens automatically in this case.
         if (aDesc.invocationKind_ == GC_NORMAL) {
@@ -2290,20 +2296,21 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
       break;
 
     case JS::GC_SLICE_END:
 
       // The GC has more work to do, so schedule another GC slice.
       nsJSContext::KillInterSliceGCTimer();
       if (!sShuttingDown) {
         CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
-        sInterSliceGCTimer->InitWithFuncCallback(InterSliceGCTimerFired,
-                                                 nullptr,
-                                                 NS_INTERSLICE_GC_DELAY,
-                                                 nsITimer::TYPE_ONE_SHOT);
+        sInterSliceGCTimer->InitWithNamedFuncCallback(InterSliceGCTimerFired,
+                                                      nullptr,
+                                                      NS_INTERSLICE_GC_DELAY,
+                                                      nsITimer::TYPE_ONE_SHOT,
+                                                      "InterSliceGCTimerFired");
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
         nsString gcstats;
--- a/dom/base/nsNodeUtils.cpp
+++ b/dom/base/nsNodeUtils.cpp
@@ -8,17 +8,17 @@
 #include "nsContentUtils.h"
 #include "nsINode.h"
 #include "nsIContent.h"
 #include "mozilla/dom/Element.h"
 #include "nsIMutationObserver.h"
 #include "nsIDocument.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsIXPConnect.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsIDOMAttr.h"
 #include "nsCOMArray.h"
 #include "nsPIDOMWindow.h"
 #include "nsDocument.h"
 #ifdef MOZ_XUL
 #include "nsXULElement.h"
 #endif
 #include "nsBindingManager.h"
--- a/dom/base/nsPropertyTable.cpp
+++ b/dom/base/nsPropertyTable.cpp
@@ -18,17 +18,17 @@
  * themselves.  Nodes can be any type of object; the hashtable keys are
  * nsIAtom pointers, and the values are void pointers.
  */
 
 #include "nsPropertyTable.h"
 
 #include "mozilla/MemoryReporting.h"
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsError.h"
 #include "nsIAtom.h"
 
 struct PropertyListMapEntry : public PLDHashEntryHdr {
   const void  *key;
   void        *value;
 };
 
--- a/dom/base/nsScriptNameSpaceManager.h
+++ b/dom/base/nsScriptNameSpaceManager.h
@@ -22,17 +22,17 @@
 #define nsScriptNameSpaceManager_h__
 
 #include "mozilla/MemoryReporting.h"
 #include "nsBaseHashtable.h"
 #include "nsIMemoryReporter.h"
 #include "nsIScriptNameSpaceManager.h"
 #include "nsString.h"
 #include "nsID.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsDOMClassInfo.h"
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "xpcpublic.h"
 
 
 struct nsGlobalNameStruct
 {
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -269,18 +269,19 @@ static_assert(nsIContentPolicy::TYPE_INV
               nsIContentPolicy::TYPE_INTERNAL_EMBED == 26 &&
               nsIContentPolicy::TYPE_INTERNAL_OBJECT == 27 &&
               nsIContentPolicy::TYPE_INTERNAL_FRAME == 28 &&
               nsIContentPolicy::TYPE_INTERNAL_IFRAME == 29 &&
               nsIContentPolicy::TYPE_INTERNAL_AUDIO == 30 &&
               nsIContentPolicy::TYPE_INTERNAL_VIDEO == 31 &&
               nsIContentPolicy::TYPE_INTERNAL_TRACK == 32 &&
               nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST == 33 &&
-              nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE == 34,
-              "nsContentPolicytType values are as expected");
+              nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE == 34 &&
+              nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER == 35,
+              "nsContentPolicyType values are as expected");
 
 namespace {
 
 typedef int32_t EntryId;
 
 struct IdCount
 {
   IdCount() : mId(-1), mCount(0) { }
--- a/dom/camera/CameraPreviewMediaStream.cpp
+++ b/dom/camera/CameraPreviewMediaStream.cpp
@@ -35,17 +35,16 @@ CameraPreviewMediaStream::CameraPreviewM
   , mDiscardedFrames(0)
   , mRateLimit(false)
   , mTrackCreated(false)
 {
   SetGraphImpl(
       MediaStreamGraph::GetInstance(
         MediaStreamGraph::SYSTEM_THREAD_DRIVER, AudioChannel::Normal));
   mFakeMediaStreamGraph = new FakeMediaStreamGraph();
-  mIsConsumed = false;
 }
 
 void
 CameraPreviewMediaStream::AddAudioOutput(void* aKey)
 {
 }
 
 void
@@ -59,46 +58,23 @@ CameraPreviewMediaStream::RemoveAudioOut
 }
 
 void
 CameraPreviewMediaStream::AddVideoOutput(VideoFrameContainer* aContainer)
 {
   MutexAutoLock lock(mMutex);
   nsRefPtr<VideoFrameContainer> container = aContainer;
   AddVideoOutputImpl(container.forget());
-
-  if (mVideoOutputs.Length() > 1) {
-    return;
-  }
-  mIsConsumed = true;
-  for (uint32_t j = 0; j < mListeners.Length(); ++j) {
-    MediaStreamListener* l = mListeners[j];
-    l->NotifyConsumptionChanged(mFakeMediaStreamGraph, MediaStreamListener::CONSUMED);
-  }
 }
 
 void
 CameraPreviewMediaStream::RemoveVideoOutput(VideoFrameContainer* aContainer)
 {
   MutexAutoLock lock(mMutex);
   RemoveVideoOutputImpl(aContainer);
-
-  if (!mVideoOutputs.IsEmpty()) {
-    return;
-  }
-  mIsConsumed = false;
-  for (uint32_t j = 0; j < mListeners.Length(); ++j) {
-    MediaStreamListener* l = mListeners[j];
-    l->NotifyConsumptionChanged(mFakeMediaStreamGraph, MediaStreamListener::NOT_CONSUMED);
-  }
-}
-
-void
-CameraPreviewMediaStream::ChangeExplicitBlockerCount(int32_t aDelta)
-{
 }
 
 void
 CameraPreviewMediaStream::AddListener(MediaStreamListener* aListener)
 {
   MutexAutoLock lock(mMutex);
 
   MediaStreamListener* listener = *mListeners.AppendElement() = aListener;
--- a/dom/camera/CameraPreviewMediaStream.h
+++ b/dom/camera/CameraPreviewMediaStream.h
@@ -43,17 +43,18 @@ public:
   explicit CameraPreviewMediaStream(DOMMediaStream* aWrapper);
 
   virtual CameraPreviewMediaStream* AsCameraPreviewStream() override { return this; };
   virtual void AddAudioOutput(void* aKey) override;
   virtual void SetAudioOutputVolume(void* aKey, float aVolume) override;
   virtual void RemoveAudioOutput(void* aKey) override;
   virtual void AddVideoOutput(VideoFrameContainer* aContainer) override;
   virtual void RemoveVideoOutput(VideoFrameContainer* aContainer) override;
-  virtual void ChangeExplicitBlockerCount(int32_t aDelta) override;
+  virtual void Suspend() override {}
+  virtual void Resume() override {}
   virtual void AddListener(MediaStreamListener* aListener) override;
   virtual void RemoveListener(MediaStreamListener* aListener) override;
   virtual void Destroy() override;
   void OnPreviewStateChange(bool aActive);
 
   void Invalidate();
 
   // Call these on any thread.
--- a/dom/canvas/CanvasImageCache.cpp
+++ b/dom/canvas/CanvasImageCache.cpp
@@ -211,17 +211,17 @@ class CanvasImageCacheShutdownObserver f
 {
   ~CanvasImageCacheShutdownObserver() {}
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 };
 
 ImageCache::ImageCache()
-  : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS)
+  : nsExpirationTracker<ImageCacheEntryData,4>(GENERATION_MS, "ImageCache")
   , mTotal(0)
 {
   if (!sPrefsInitialized) {
     sPrefsInitialized = true;
     Preferences::AddIntVarCache(&sCanvasImageCacheLimit, "canvas.image.cache.limit", 0);
   }
   mImageCacheObserver = new ImageCacheObserver(this);
   MOZ_RELEASE_ASSERT(mImageCacheObserver, "Can't alloc ImageCacheObserver");
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -43,43 +43,16 @@ PRLogModuleInfo* sIMECOLog = nullptr;
 
 static const char*
 ToChar(bool aBool)
 {
   return aBool ? "true" : "false";
 }
 
 static const char*
-ToChar(EventMessage aEventMessage)
-{
-  switch (aEventMessage) {
-    case eQuerySelectedText:
-      return "eQuerySelectedText";
-    case eQueryTextContent:
-      return "eQueryTextContent";
-    case eQueryCaretRect:
-      return "eQueryCaretRect";
-    case eQueryTextRect:
-      return "eQueryTextRect";
-    case eQueryEditorRect:
-      return "eQueryEditorRect";
-    case eQueryContentState:
-      return "eQueryContentState";
-    case eQuerySelectionAsTransferable:
-      return "eQuerySelectionAsTransferable";
-    case eQueryCharacterAtPoint:
-      return "eQueryCharacterAtPoint";
-    case eQueryDOMWidgetHittest:
-      return "eQueryDOMWidgetHittest";
-    default:
-      return "Unsupported message";
-  }
-}
-
-static const char*
 ToChar(IMEMessage aIMEMessage)
 {
   switch (aIMEMessage) {
     case NOTIFY_IME_OF_NOTHING:
       return "NOTIFY_IME_OF_NOTHING";
     case NOTIFY_IME_OF_FOCUS:
       return "NOTIFY_IME_OF_FOCUS";
     case NOTIFY_IME_OF_BLUR:
--- a/dom/events/IMEStateManager.cpp
+++ b/dom/events/IMEStateManager.cpp
@@ -129,39 +129,16 @@ GetIMEStateSetOpenName(IMEState::Open aO
     case IMEState::CLOSED:
       return "CLOSED";
     default:
       return "illegal value";
   }
 }
 
 static const char*
-GetEventMessageName(EventMessage aMessage)
-{
-  switch (aMessage) {
-    case eCompositionStart:
-      return "eCompositionStart";
-    case eCompositionEnd:
-      return "eCompositionEnd";
-    case eCompositionUpdate:
-      return "eCompositionUpdate";
-    case eCompositionChange:
-      return "eCompositionChange";
-    case eCompositionCommitAsIs:
-      return "eCompositionCommitAsIs";
-    case eCompositionCommit:
-      return "eCompositionCommit";
-    case eSetSelection:
-      return "eSetSelection";
-    default:
-      return "unacceptable event message";
-  }
-}
-
-static const char*
 GetNotifyIMEMessageName(IMEMessage aMessage)
 {
   switch (aMessage) {
     case NOTIFY_IME_OF_FOCUS:
       return "NOTIFY_IME_OF_FOCUS";
     case NOTIFY_IME_OF_BLUR:
       return "NOTIFY_IME_OF_BLUR";
     case NOTIFY_IME_OF_SELECTION_CHANGE:
@@ -1132,17 +1109,17 @@ IMEStateManager::DispatchCompositionEven
       TabParent::GetFrom(aEventTargetNode->AsContent()) : nullptr;
 
   MOZ_LOG(sISMLog, LogLevel::Info,
     ("ISM: IMEStateManager::DispatchCompositionEvent(aNode=0x%p, "
      "aPresContext=0x%p, aCompositionEvent={ message=%s, "
      "mFlags={ mIsTrusted=%s, mPropagationStopped=%s } }, "
      "aIsSynthesized=%s), tabParent=%p",
      aEventTargetNode, aPresContext,
-     GetEventMessageName(aCompositionEvent->mMessage),
+     ToChar(aCompositionEvent->mMessage),
      GetBoolName(aCompositionEvent->mFlags.mIsTrusted),
      GetBoolName(aCompositionEvent->mFlags.mPropagationStopped),
      GetBoolName(aIsSynthesized), tabParent.get()));
 
   if (!aCompositionEvent->mFlags.mIsTrusted ||
       aCompositionEvent->mFlags.mPropagationStopped) {
     return;
   }
@@ -1230,17 +1207,17 @@ IMEStateManager::HandleSelectionEvent(ns
   nsRefPtr<TabParent> tabParent =
     eventTargetContent ? TabParent::GetFrom(eventTargetContent) : nullptr;
 
   MOZ_LOG(sISMLog, LogLevel::Info,
     ("ISM: IMEStateManager::HandleSelectionEvent(aPresContext=0x%p, "
      "aEventTargetContent=0x%p, aSelectionEvent={ mMessage=%s, "
      "mFlags={ mIsTrusted=%s } }), tabParent=%p",
      aPresContext, aEventTargetContent,
-     GetEventMessageName(aSelectionEvent->mMessage),
+     ToChar(aSelectionEvent->mMessage),
      GetBoolName(aSelectionEvent->mFlags.mIsTrusted),
      tabParent.get()));
 
   if (!aSelectionEvent->mFlags.mIsTrusted) {
     return;
   }
 
   nsRefPtr<TextComposition> composition = sTextCompositions ?
@@ -1263,17 +1240,17 @@ IMEStateManager::OnCompositionEventDisca
                    WidgetCompositionEvent* aCompositionEvent)
 {
   // Note that this method is never called for synthesized events for emulating
   // commit or cancel composition.
 
   MOZ_LOG(sISMLog, LogLevel::Info,
     ("ISM: IMEStateManager::OnCompositionEventDiscarded(aCompositionEvent={ "
      "mMessage=%s, mFlags={ mIsTrusted=%s } })",
-     GetEventMessageName(aCompositionEvent->mMessage),
+     ToChar(aCompositionEvent->mMessage),
      GetBoolName(aCompositionEvent->mFlags.mIsTrusted)));
 
   if (!aCompositionEvent->mFlags.mIsTrusted) {
     return;
   }
 
   // Ignore compositionstart for now because sTextCompositions may not have
   // been created yet.
--- a/dom/fetch/InternalRequest.cpp
+++ b/dom/fetch/InternalRequest.cpp
@@ -111,16 +111,17 @@ RequestContext
 InternalRequest::MapContentPolicyTypeToRequestContext(nsContentPolicyType aContentPolicyType)
 {
   RequestContext context = RequestContext::Internal;
   switch (aContentPolicyType) {
   case nsIContentPolicy::TYPE_OTHER:
     context = RequestContext::Internal;
     break;
   case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+  case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
     context = RequestContext::Script;
     break;
   case nsIContentPolicy::TYPE_INTERNAL_WORKER:
     context = RequestContext::Worker;
     break;
   case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
     context = RequestContext::Sharedworker;
     break;
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1870,20 +1870,16 @@ HTMLMediaElement::CaptureStreamInternal(
   OutputMediaStream* out = mOutputStreams.AppendElement();
   out->mStream = DOMMediaStream::CreateTrackUnionStream(window, aGraph);
   nsRefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   out->mStream->CombineWithPrincipal(principal);
   out->mStream->SetCORSMode(mCORSMode);
   out->mFinishWhenEnded = aFinishWhenEnded;
 
   mAudioCaptured = true;
-  // Block the output stream initially.
-  // Decoders are responsible for removing the block while they are playing
-  // back into the output stream.
-  out->mStream->GetStream()->ChangeExplicitBlockerCount(1);
   if (mDecoder) {
     mDecoder->AddOutputStream(out->mStream->GetStream()->AsProcessedStream(),
                               aFinishWhenEnded);
     if (mReadyState >= HAVE_METADATA) {
       // Expose the tracks to JS directly.
       if (HasAudio()) {
         TrackID audioTrackId = mMediaInfo.mAudio.mTrackId;
         out->mStream->CreateDOMTrack(audioTrackId, MediaSegment::AUDIO);
@@ -3521,20 +3517,19 @@ void HTMLMediaElement::ProgressTimerCall
 
 void HTMLMediaElement::StartProgressTimer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING);
   NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
 
   mProgressTimer = do_CreateInstance("@mozilla.org/timer;1");
-  mProgressTimer->InitWithFuncCallback(ProgressTimerCallback,
-                                       this,
-                                       PROGRESS_MS,
-                                       nsITimer::TYPE_REPEATING_SLACK);
+  mProgressTimer->InitWithNamedFuncCallback(
+    ProgressTimerCallback, this, PROGRESS_MS, nsITimer::TYPE_REPEATING_SLACK,
+    "HTMLMediaElement::ProgressTimerCallback");
 }
 
 void HTMLMediaElement::StartProgress()
 {
   // Record the time now for detecting stalled.
   mDataTime = TimeStamp::NowLoRes();
   // Reset mProgressTime so that mDataTime is not indicating bytes received
   // after the last progress event.
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -9,17 +9,17 @@
 #include "mozilla/Attributes.h"
 #include "nsDocument.h"
 #include "nsIHTMLDocument.h"
 #include "nsIDOMHTMLDocument.h"
 #include "nsIDOMHTMLCollection.h"
 #include "nsIScriptElement.h"
 #include "nsTArray.h"
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsIHttpChannel.h"
 #include "nsHTMLStyleSheet.h"
 
 #include "nsICommandManager.h"
 #include "mozilla/dom/HTMLSharedElement.h"
 
 class nsIEditor;
 class nsIURI;
--- a/dom/media/AudioCaptureStream.cpp
+++ b/dom/media/AudioCaptureStream.cpp
@@ -60,47 +60,46 @@ AudioCaptureStream::ProcessInput(GraphTi
     }
     mTrackCreated = true;
   }
 
   // If the captured stream is connected back to a object on the page (be it an
   // HTMLMediaElement with a stream as source, or an AudioContext), a cycle
   // situation occur. This can work if it's an AudioContext with at least one
   // DelayNode, but the MSG will mute the whole cycle otherwise.
-  bool blocked = mFinished || mBlocked.GetAt(aFrom);
-  if (blocked || InMutedCycle() || inputCount == 0) {
+  if (mFinished || InMutedCycle() || inputCount == 0) {
     track->Get<AudioSegment>()->AppendNullData(aTo - aFrom);
   } else {
     // We mix down all the tracks of all inputs, to a stereo track. Everything
     // is {up,down}-mixed to stereo.
     mMixer.StartMixing();
     AudioSegment output;
     for (uint32_t i = 0; i < inputCount; i++) {
       MediaStream* s = mInputs[i]->GetSource();
       StreamBuffer::TrackIter tracks(s->GetStreamBuffer(), MediaSegment::AUDIO);
       while (!tracks.IsEnded()) {
         AudioSegment* inputSegment = tracks->Get<AudioSegment>();
-        StreamTime inputStart = s->GraphTimeToStreamTime(aFrom);
-        StreamTime inputEnd = s->GraphTimeToStreamTime(aTo);
+        StreamTime inputStart = s->GraphTimeToStreamTimeWithBlocking(aFrom);
+        StreamTime inputEnd = s->GraphTimeToStreamTimeWithBlocking(aTo);
         AudioSegment toMix;
         toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
         // Care for streams blocked in the [aTo, aFrom] range.
         if (inputEnd - inputStart < aTo - aFrom) {
           toMix.AppendNullData((aTo - aFrom) - (inputEnd - inputStart));
         }
         toMix.Mix(mMixer, MONO, Graph()->GraphRate());
         tracks.Next();
       }
     }
     // This calls MixerCallback below
     mMixer.FinishMixing();
   }
 
   // Regardless of the status of the input tracks, we go foward.
-  mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime((aTo)));
+  mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTimeWithBlocking((aTo)));
 }
 
 void
 AudioCaptureStream::MixerCallback(AudioDataValue* aMixedBuffer,
                                   AudioSampleFormat aFormat, uint32_t aChannels,
                                   uint32_t aFrames, uint32_t aSampleRate)
 {
   nsAutoTArray<nsTArray<AudioDataValue>, MONO> output;
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -294,18 +294,16 @@ ThreadedDriver::RunThread()
                   (long)stateComputedTime, (long)nextStateComputedTime));
       nextStateComputedTime = stateComputedTime;
     }
     STREAM_LOG(LogLevel::Debug,
                ("interval[%ld; %ld] state[%ld; %ld]",
                (long)mIterationStart, (long)mIterationEnd,
                (long)stateComputedTime, (long)nextStateComputedTime));
 
-    mGraphImpl->mFlushSourcesNow = mGraphImpl->mFlushSourcesOnNextIteration;
-    mGraphImpl->mFlushSourcesOnNextIteration = false;
     stillProcessing = mGraphImpl->OneIteration(nextStateComputedTime);
 
     if (mNextDriver && stillProcessing) {
       STREAM_LOG(LogLevel::Debug, ("Switching to AudioCallbackDriver"));
       mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd);
       mGraphImpl->SetCurrentDriver(mNextDriver);
       mNextDriver->Start();
       return;
@@ -962,17 +960,16 @@ AudioCallbackDriver::DeviceChangedCallba
   }
 
   if (mSelfReference) {
     return;
   }
   STREAM_LOG(LogLevel::Error, ("Switching to SystemClockDriver during output switch"));
   mSelfReference.Take(this);
   mCallbackReceivedWhileSwitching = 0;
-  mGraphImpl->mFlushSourcesOnNextIteration = true;
   mNextDriver = new SystemClockDriver(GraphImpl());
   mNextDriver->SetGraphTime(this, mIterationStart, mIterationEnd);
   mGraphImpl->SetCurrentDriver(mNextDriver);
   mNextDriver->Start();
 #endif
 }
 
 void
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -812,17 +812,17 @@ public:
       // Normal case, connect the source stream to the track union stream to
       // avoid us blocking
       nsRefPtr<nsDOMUserMediaStream> trackunion =
         nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
                                                      mAudioSource, mVideoSource,
                                                      msg);
       trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
       nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
-        AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
+        AllocateInputPort(stream);
       trackunion->mSourceStream = stream;
       trackunion->mPort = port.forget();
       // Log the relationship between SourceMediaStream and TrackUnion stream
       // Make sure logger starts before capture
       AsyncLatencyLogger::Get(true);
       LogLatency(AsyncLatencyLogger::MediaStreamCreate,
           reinterpret_cast<uint64_t>(stream.get()),
           reinterpret_cast<int64_t>(trackunion->GetStream()));
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -402,28 +402,28 @@ public:
   }
 
   nsresult Pause()
   {
     LOG(LogLevel::Debug, ("Session.Pause"));
     MOZ_ASSERT(NS_IsMainThread());
 
     NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
-    mTrackUnionStream->ChangeExplicitBlockerCount(1);
+    mTrackUnionStream->Suspend();
 
     return NS_OK;
   }
 
   nsresult Resume()
   {
     LOG(LogLevel::Debug, ("Session.Resume"));
     MOZ_ASSERT(NS_IsMainThread());
 
     NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
-    mTrackUnionStream->ChangeExplicitBlockerCount(-1);
+    mTrackUnionStream->Resume();
 
     return NS_OK;
   }
 
   nsresult RequestData()
   {
     LOG(LogLevel::Debug, ("Session.RequestData"));
     MOZ_ASSERT(NS_IsMainThread());
@@ -783,23 +783,21 @@ MediaRecorder::MediaRecorder(AudioNode& 
   // union stream in recorder session won't be able to copy data from the
   // stream of non-destination node. Create a pipe stream in this case.
   if (aSrcAudioNode.NumberOfOutputs() > 0) {
     AudioContext* ctx = aSrcAudioNode.Context();
     AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
     AudioNodeStream::Flags flags =
       AudioNodeStream::EXTERNAL_OUTPUT |
       AudioNodeStream::NEED_MAIN_THREAD_FINISHED;
-    mPipeStream = AudioNodeStream::Create(ctx->Graph(), engine, flags);
+    mPipeStream = AudioNodeStream::Create(ctx, engine, flags);
     AudioNodeStream* ns = aSrcAudioNode.GetStream();
     if (ns) {
       mInputPort = mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
-                                                  0,
-                                                  0,
-                                                  aSrcOutput);
+                                                  0, aSrcOutput);
     }
   }
   mAudioNode = &aSrcAudioNode;
   if (!gMediaRecorderLog) {
     gMediaRecorderLog = PR_NewLogModule("MediaRecorder");
   }
   RegisterActivityObserver();
 }
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -81,28 +81,17 @@ MediaStreamGraphImpl::FinishStream(Media
 
   SetStreamOrderDirty();
 }
 
 void
 MediaStreamGraphImpl::AddStreamGraphThread(MediaStream* aStream)
 {
   aStream->mBufferStartTime = mProcessedTime;
-  // Check if we're adding a stream to a suspended context, in which case, we
-  // add it to mSuspendedStreams
-  bool contextSuspended = false;
-  if (aStream->AsAudioNodeStream()) {
-    for (uint32_t i = 0; i < mSuspendedStreams.Length(); i++) {
-      if (aStream->AudioContextId() == mSuspendedStreams[i]->AudioContextId()) {
-        contextSuspended = true;
-      }
-    }
-  }
-
-  if (contextSuspended) {
+  if (aStream->IsSuspended()) {
     mSuspendedStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph, in the suspended stream array", aStream));
   } else {
     mStreams.AppendElement(aStream);
     STREAM_LOG(LogLevel::Debug, ("Adding media stream %p to the graph", aStream));
   }
 
   SetStreamOrderDirty();
@@ -121,55 +110,40 @@ MediaStreamGraphImpl::RemoveStreamGraphT
         mStreamUpdates[i].mStream = nullptr;
       }
     }
   }
 
   // Ensure that mFirstCycleBreaker and mMixer are updated when necessary.
   SetStreamOrderDirty();
 
-  mStreams.RemoveElement(aStream);
-  mSuspendedStreams.RemoveElement(aStream);
+  if (aStream->IsSuspended()) {
+    mSuspendedStreams.RemoveElement(aStream);
+  } else {
+    mStreams.RemoveElement(aStream);
+  }
 
   NS_RELEASE(aStream); // probably destroying it
 
   STREAM_LOG(LogLevel::Debug, ("Removing media stream %p from the graph", aStream));
 }
 
 void
-MediaStreamGraphImpl::UpdateConsumptionState(SourceMediaStream* aStream)
-{
-  MediaStreamListener::Consumption state =
-      aStream->mIsConsumed ? MediaStreamListener::CONSUMED
-      : MediaStreamListener::NOT_CONSUMED;
-  if (state != aStream->mLastConsumptionState) {
-    aStream->mLastConsumptionState = state;
-    for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-      MediaStreamListener* l = aStream->mListeners[j];
-      l->NotifyConsumptionChanged(this, state);
-    }
-  }
-}
-
-void
 MediaStreamGraphImpl::ExtractPendingInput(SourceMediaStream* aStream,
                                           GraphTime aDesiredUpToTime,
                                           bool* aEnsureNextIteration)
 {
   bool finished;
   {
     MutexAutoLock lock(aStream->mMutex);
     if (aStream->mPullEnabled && !aStream->mFinished &&
         !aStream->mListeners.IsEmpty()) {
       // Compute how much stream time we'll need assuming we don't block
-      // the stream at all between mBlockingDecisionsMadeUntilTime and
-      // aDesiredUpToTime.
-      StreamTime t =
-        GraphTimeToStreamTime(aStream, mStateComputedTime) +
-        (aDesiredUpToTime - mStateComputedTime);
+      // the stream at all.
+      StreamTime t = aStream->GraphTimeToStreamTime(aDesiredUpToTime);
       STREAM_LOG(LogLevel::Verbose, ("Calling NotifyPull aStream=%p t=%f current end=%f", aStream,
                                   MediaTimeToSeconds(t),
                                   MediaTimeToSeconds(aStream->mBuffer.GetEnd())));
       if (t > aStream->mBuffer.GetEnd()) {
         *aEnsureNextIteration = true;
 #ifdef DEBUG
         if (aStream->mListeners.Length() == 0) {
           STREAM_LOG(LogLevel::Error, ("No listeners in NotifyPull aStream=%p desired=%f current end=%f",
@@ -238,249 +212,113 @@ MediaStreamGraphImpl::ExtractPendingInpu
     aStream->mHasCurrentData = true;
   }
   if (finished) {
     FinishStream(aStream);
   }
 }
 
 StreamTime
-MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
-                                            GraphTime aTime)
+MediaStreamGraphImpl::GraphTimeToStreamTimeWithBlocking(MediaStream* aStream,
+                                                        GraphTime aTime)
 {
   MOZ_ASSERT(aTime <= mStateComputedTime,
-               "Don't ask about times where we haven't made blocking decisions yet");
-  if (aTime <= mProcessedTime) {
-    return std::max<StreamTime>(0, aTime - aStream->mBufferStartTime);
-  }
-  GraphTime t = mProcessedTime;
-  StreamTime s = t - aStream->mBufferStartTime;
-  while (t < aTime) {
-    GraphTime end;
-    if (!aStream->mBlocked.GetAt(t, &end)) {
-      s += std::min(aTime, end) - t;
-    }
-    t = end;
-  }
-  return std::max<StreamTime>(0, s);
-}
-
-StreamTime
-MediaStreamGraphImpl::GraphTimeToStreamTimeOptimistic(MediaStream* aStream,
-                                                      GraphTime aTime)
-{
-  GraphTime computedUpToTime = std::min(mStateComputedTime, aTime);
-  StreamTime s = GraphTimeToStreamTime(aStream, computedUpToTime);
-  return s + (aTime - computedUpToTime);
-}
-
-GraphTime
-MediaStreamGraphImpl::StreamTimeToGraphTime(MediaStream* aStream,
-                                            StreamTime aTime, uint32_t aFlags)
-{
-  if (aTime >= STREAM_TIME_MAX) {
-    return GRAPH_TIME_MAX;
-  }
-  MediaTime bufferElapsedToCurrentTime =
-    mProcessedTime - aStream->mBufferStartTime;
-  if (aTime < bufferElapsedToCurrentTime ||
-      (aTime == bufferElapsedToCurrentTime && !(aFlags & INCLUDE_TRAILING_BLOCKED_INTERVAL))) {
-    return aTime + aStream->mBufferStartTime;
-  }
-
-  MediaTime streamAmount = aTime - bufferElapsedToCurrentTime;
-  NS_ASSERTION(streamAmount >= 0, "Can't answer queries before current time");
-
-  GraphTime t = mProcessedTime;
-  while (t < GRAPH_TIME_MAX) {
-    if (!(aFlags & INCLUDE_TRAILING_BLOCKED_INTERVAL) && streamAmount == 0) {
-      return t;
-    }
-    bool blocked;
-    GraphTime end;
-    if (t < mStateComputedTime) {
-      blocked = aStream->mBlocked.GetAt(t, &end);
-      end = std::min(end, mStateComputedTime);
-    } else {
-      blocked = false;
-      end = GRAPH_TIME_MAX;
-    }
-    if (blocked) {
-      t = end;
-    } else {
-      if (streamAmount == 0) {
-        // No more stream time to consume at time t, so we're done.
-        break;
-      }
-      MediaTime consume = std::min(end - t, streamAmount);
-      streamAmount -= consume;
-      t += consume;
-    }
-  }
-  return t;
+             "Don't ask about times where we haven't made blocking decisions yet");
+  return std::max<StreamTime>(0,
+      std::min(aTime, aStream->mStartBlocking) - aStream->mBufferStartTime);
 }
 
 GraphTime
 MediaStreamGraphImpl::IterationEnd() const
 {
   return CurrentDriver()->IterationEnd();
 }
 
 void
-MediaStreamGraphImpl::StreamNotifyOutput(MediaStream* aStream)
-{
-  for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-    MediaStreamListener* l = aStream->mListeners[j];
-    l->NotifyOutput(this, mProcessedTime);
-  }
-}
-
-void
-MediaStreamGraphImpl::StreamReadyToFinish(MediaStream* aStream)
-{
-  MOZ_ASSERT(aStream->mFinished);
-  MOZ_ASSERT(!aStream->mNotifiedFinished);
-
-  // The stream is fully finished when all of its track data has been played
-  // out.
-  if (mProcessedTime >=
-      aStream->StreamTimeToGraphTime(aStream->GetStreamBuffer().GetAllTracksEnd()))  {
-    aStream->mNotifiedFinished = true;
-    SetStreamOrderDirty();
-    for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
-      MediaStreamListener* l = aStream->mListeners[j];
-      l->NotifyEvent(this, MediaStreamListener::EVENT_FINISHED);
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime,
-                                                  GraphTime aNextCurrentTime)
+MediaStreamGraphImpl::UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime)
 {
   for (MediaStream* stream : AllStreams()) {
+    bool isAnyBlocked = stream->mStartBlocking < mStateComputedTime;
+    bool isAnyUnblocked = stream->mStartBlocking > aPrevCurrentTime;
+
     // Calculate blocked time and fire Blocked/Unblocked events
-    GraphTime blockedTime = 0;
-    GraphTime t = aPrevCurrentTime;
-    // include |nextCurrentTime| to ensure NotifyBlockingChanged() is called
-    // before NotifyEvent(this, EVENT_FINISHED) when |nextCurrentTime ==
-    // stream end time|
-    while (t <= aNextCurrentTime) {
-      GraphTime end;
-      bool blocked = stream->mBlocked.GetAt(t, &end);
-      if (blocked) {
-        blockedTime += std::min(end, aNextCurrentTime) - t;
-      }
-      if (blocked != stream->mNotifiedBlocked) {
-        for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
-          MediaStreamListener* l = stream->mListeners[j];
-          l->NotifyBlockingChanged(this, blocked
-                                           ? MediaStreamListener::BLOCKED
-                                           : MediaStreamListener::UNBLOCKED);
-        }
-        stream->mNotifiedBlocked = blocked;
-      }
-      t = end;
-    }
-
-    stream->AdvanceTimeVaryingValuesToCurrentTime(aNextCurrentTime,
+    GraphTime blockedTime = mStateComputedTime - stream->mStartBlocking;
+    NS_ASSERTION(blockedTime >= 0, "Error in blocking time");
+    stream->AdvanceTimeVaryingValuesToCurrentTime(mStateComputedTime,
                                                   blockedTime);
-    // Advance mBlocked last so that AdvanceTimeVaryingValuesToCurrentTime
-    // can rely on the value of mBlocked.
-    stream->mBlocked.AdvanceCurrentTime(aNextCurrentTime);
-
     STREAM_LOG(LogLevel::Verbose,
                ("MediaStream %p bufferStartTime=%f blockedTime=%f", stream,
                 MediaTimeToSeconds(stream->mBufferStartTime),
                 MediaTimeToSeconds(blockedTime)));
-
-    bool streamHasOutput = blockedTime < aNextCurrentTime - aPrevCurrentTime;
-    NS_ASSERTION(!streamHasOutput || !stream->mNotifiedFinished,
-      "Shouldn't have already notified of finish *and* have output!");
-
-    if (streamHasOutput) {
-      StreamNotifyOutput(stream);
+    stream->mStartBlocking = mStateComputedTime;
+
+    if (isAnyUnblocked && stream->mNotifiedBlocked) {
+      for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
+        MediaStreamListener* l = stream->mListeners[j];
+        l->NotifyBlockingChanged(this, MediaStreamListener::UNBLOCKED);
+      }
+      stream->mNotifiedBlocked = false;
+    }
+    if (isAnyBlocked && !stream->mNotifiedBlocked) {
+      for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
+        MediaStreamListener* l = stream->mListeners[j];
+        l->NotifyBlockingChanged(this, MediaStreamListener::BLOCKED);
+      }
+      stream->mNotifiedBlocked = true;
     }
 
-    if (stream->mFinished && !stream->mNotifiedFinished) {
-      StreamReadyToFinish(stream);
+    if (isAnyUnblocked) {
+      NS_ASSERTION(!stream->mNotifiedFinished,
+        "Shouldn't have already notified of finish *and* have output!");
+      for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
+        MediaStreamListener* l = stream->mListeners[j];
+        l->NotifyOutput(this, mProcessedTime);
+      }
+    }
+
+    // The stream is fully finished when all of its track data has been played
+    // out.
+    if (stream->mFinished && !stream->mNotifiedFinished &&
+        mProcessedTime >=
+          stream->StreamTimeToGraphTime(stream->GetStreamBuffer().GetAllTracksEnd())) {
+      stream->mNotifiedFinished = true;
+      SetStreamOrderDirty();
+      for (uint32_t j = 0; j < stream->mListeners.Length(); ++j) {
+        MediaStreamListener* l = stream->mListeners[j];
+        l->NotifyEvent(this, MediaStreamListener::EVENT_FINISHED);
+      }
     }
   }
 }
 
-bool
-MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream, GraphTime aTime,
-                                   GraphTime aEndBlockingDecisions, GraphTime* aEnd)
+GraphTime
+MediaStreamGraphImpl::WillUnderrun(MediaStream* aStream,
+                                   GraphTime aEndBlockingDecisions)
 {
   // Finished streams can't underrun. ProcessedMediaStreams also can't cause
   // underrun currently, since we'll always be able to produce data for them
   // unless they block on some other stream.
   if (aStream->mFinished || aStream->AsProcessedStream()) {
-    return false;
+    return aEndBlockingDecisions;
   }
-  GraphTime bufferEnd =
-    StreamTimeToGraphTime(aStream, aStream->GetBufferEnd(),
-                          INCLUDE_TRAILING_BLOCKED_INTERVAL);
+  // This stream isn't finished or suspended. We don't need to call
+  // StreamTimeToGraphTime since an underrun is the only thing that can block
+  // it.
+  GraphTime bufferEnd = aStream->GetBufferEnd() + aStream->mBufferStartTime;
 #ifdef DEBUG
   if (bufferEnd < mProcessedTime) {
     STREAM_LOG(LogLevel::Error, ("MediaStream %p underrun, "
                               "bufferEnd %f < mProcessedTime %f (%lld < %lld), Streamtime %lld",
                               aStream, MediaTimeToSeconds(bufferEnd), MediaTimeToSeconds(mProcessedTime),
                               bufferEnd, mProcessedTime, aStream->GetBufferEnd()));
     aStream->DumpTrackInfo();
     NS_ASSERTION(bufferEnd >= mProcessedTime, "Buffer underran");
   }
 #endif
-  // We should block after bufferEnd.
-  if (bufferEnd <= aTime) {
-    STREAM_LOG(LogLevel::Verbose, ("MediaStream %p will block due to data underrun at %ld, "
-                                "bufferEnd %ld",
-                                aStream, aTime, bufferEnd));
-    return true;
-  }
-  // We should keep blocking if we're currently blocked and we don't have
-  // data all the way through to aEndBlockingDecisions. If we don't have
-  // data all the way through to aEndBlockingDecisions, we'll block soon,
-  // but we might as well remain unblocked and play the data we've got while
-  // we can.
-  if (bufferEnd < aEndBlockingDecisions && aStream->mBlocked.GetBefore(aTime)) {
-    STREAM_LOG(LogLevel::Verbose, ("MediaStream %p will block due to speculative data underrun, "
-                                "bufferEnd %f (end at %ld)",
-                                aStream, MediaTimeToSeconds(bufferEnd), bufferEnd));
-    return true;
-  }
-  // Reconsider decisions at bufferEnd
-  *aEnd = std::min(*aEnd, bufferEnd);
-  return false;
-}
-
-void
-MediaStreamGraphImpl::MarkConsumed(MediaStream* aStream)
-{
-  if (aStream->mIsConsumed) {
-    return;
-  }
-  aStream->mIsConsumed = true;
-
-  ProcessedMediaStream* ps = aStream->AsProcessedStream();
-  if (!ps) {
-    return;
-  }
-  // Mark all the inputs to this stream as consumed
-  for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-    MarkConsumed(ps->mInputs[i]->mSource);
-  }
-}
-
-bool
-MediaStreamGraphImpl::StreamSuspended(MediaStream* aStream)
-{
-  // Only AudioNodeStreams can be suspended, so we can shortcut here.
-  return aStream->AsAudioNodeStream() &&
-         mSuspendedStreams.IndexOf(aStream) != mSuspendedStreams.NoIndex;
+  return std::min(bufferEnd, aEndBlockingDecisions);
 }
 
 namespace {
   // Value of mCycleMarker for unvisited streams in cycle detection.
   const uint32_t NOT_VISITED = UINT32_MAX;
   // Value of mCycleMarker for ordered streams in muted cycles.
   const uint32_t IN_MUTED_CYCLE = 1;
 } // namespace
@@ -489,18 +327,16 @@ void
 MediaStreamGraphImpl::UpdateStreamOrder()
 {
 #ifdef MOZ_WEBRTC
   bool shouldAEC = false;
 #endif
   bool audioTrackPresent = false;
   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
     MediaStream* stream = mStreams[i];
-    stream->mIsConsumed = false;
-    stream->mInBlockingSet = false;
 #ifdef MOZ_WEBRTC
     if (stream->AsSourceStream() &&
         stream->AsSourceStream()->NeedsMixing()) {
       shouldAEC = true;
     }
 #endif
     // If this is a AudioNodeStream, force a AudioCallbackDriver.
     if (stream->AsAudioNodeStream()) {
@@ -560,19 +396,16 @@ MediaStreamGraphImpl::UpdateStreamOrder(
   mozilla::LinkedList<MediaStream> sccStack;
 
   // An index into mStreams for the next stream found with no unsatisfied
   // upstream dependencies.
   uint32_t orderedStreamCount = 0;
 
   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
     MediaStream* s = mStreams[i];
-    if (s->IsIntrinsicallyConsumed()) {
-      MarkConsumed(s);
-    }
     ProcessedMediaStream* ps = s->AsProcessedStream();
     if (ps) {
       // The dfsStack initially contains a list of all processed streams in
       // unchanged order.
       dfsStack.insertBack(s);
       ps->mCycleMarker = NOT_VISITED;
     } else {
       // SourceMediaStreams have no inputs and so can be ordered now.
@@ -603,17 +436,17 @@ MediaStreamGraphImpl::UpdateStreamOrder(
     if (ps->mCycleMarker == NOT_VISITED) {
       // Record the position on the visited stack, so that any searches
       // finding this stream again know how much of the stack is in the cycle.
       ps->mCycleMarker = nextStackMarker;
       --nextStackMarker;
       // Not-visited input streams should be processed first.
       // SourceMediaStreams have already been ordered.
       for (uint32_t i = inputs.Length(); i--; ) {
-        if (StreamSuspended(inputs[i]->mSource)) {
+        if (inputs[i]->mSource->IsSuspended()) {
           continue;
         }
         auto input = inputs[i]->mSource->AsProcessedStream();
         if (input && input->mCycleMarker == NOT_VISITED) {
           // It can be that this stream has an input which is from a suspended
           // AudioContext.
           if (input->isInList()) {
             input->remove();
@@ -629,17 +462,17 @@ MediaStreamGraphImpl::UpdateStreamOrder(
 
     // cycleStackMarker keeps track of the highest marker value on any
     // upstream stream, if any, found receiving input, directly or indirectly,
     // from the visited stack (and so from |ps|, making a cycle).  In a
     // variation from Tarjan's SCC algorithm, this does not include |ps|
     // unless it is part of the cycle.
     uint32_t cycleStackMarker = 0;
     for (uint32_t i = inputs.Length(); i--; ) {
-      if (StreamSuspended(inputs[i]->mSource)) {
+      if (inputs[i]->mSource->IsSuspended()) {
         continue;
       }
       auto input = inputs[i]->mSource->AsProcessedStream();
       if (input) {
         cycleStackMarker = std::max(cycleStackMarker, input->mCycleMarker);
       }
     }
 
@@ -721,173 +554,29 @@ MediaStreamGraphImpl::UpdateStreamOrder(
       }
     }
   }
 
   MOZ_ASSERT(orderedStreamCount == mFirstCycleBreaker);
 }
 
 void
-MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions)
-{
-  STREAM_LOG(LogLevel::Verbose, ("Media graph %p computing blocking for time %f",
-                              this, MediaTimeToSeconds(mStateComputedTime)));
-  for (MediaStream* stream : AllStreams()) {
-    if (!stream->mInBlockingSet) {
-      // Compute a partition of the streams containing 'stream' such that we
-      // can
-      // compute the blocking status of each subset independently.
-      nsAutoTArray<MediaStream*, 10> streamSet;
-      AddBlockingRelatedStreamsToSet(&streamSet, stream);
-
-      GraphTime end;
-      for (GraphTime t = mStateComputedTime;
-           t < aEndBlockingDecisions; t = end) {
-        end = GRAPH_TIME_MAX;
-        RecomputeBlockingAt(streamSet, t, aEndBlockingDecisions, &end);
-      }
-    }
-  }
-  STREAM_LOG(LogLevel::Verbose, ("Media graph %p computed blocking for interval %f to %f",
-                              this, MediaTimeToSeconds(mStateComputedTime),
-                              MediaTimeToSeconds(aEndBlockingDecisions)));
-
-  MOZ_ASSERT(aEndBlockingDecisions >= mProcessedTime);
-  // The next state computed time can be the same as the previous: it
-  // means the driver would be have been blocking indefinitly, but the graph has
-  // been woken up right after having been to sleep.
-  MOZ_ASSERT(aEndBlockingDecisions >= mStateComputedTime);
-  mStateComputedTime = aEndBlockingDecisions;
-}
-
-void
-MediaStreamGraphImpl::AddBlockingRelatedStreamsToSet(nsTArray<MediaStream*>* aStreams,
-                                                     MediaStream* aStream)
-{
-  if (aStream->mInBlockingSet)
-    return;
-  aStream->mInBlockingSet = true;
-  aStreams->AppendElement(aStream);
-  for (uint32_t i = 0; i < aStream->mConsumers.Length(); ++i) {
-    MediaInputPort* port = aStream->mConsumers[i];
-    if (port->mFlags & (MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT)) {
-      AddBlockingRelatedStreamsToSet(aStreams, port->mDest);
-    }
-  }
-  ProcessedMediaStream* ps = aStream->AsProcessedStream();
-  if (ps) {
-    for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-      MediaInputPort* port = ps->mInputs[i];
-      if (port->mFlags & (MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT)) {
-        AddBlockingRelatedStreamsToSet(aStreams, port->mSource);
-      }
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::MarkStreamBlocking(MediaStream* aStream)
-{
-  if (aStream->mBlockInThisPhase)
-    return;
-  aStream->mBlockInThisPhase = true;
-  for (uint32_t i = 0; i < aStream->mConsumers.Length(); ++i) {
-    MediaInputPort* port = aStream->mConsumers[i];
-    if (port->mFlags & MediaInputPort::FLAG_BLOCK_OUTPUT) {
-      MarkStreamBlocking(port->mDest);
-    }
-  }
-  ProcessedMediaStream* ps = aStream->AsProcessedStream();
-  if (ps) {
-    for (uint32_t i = 0; i < ps->mInputs.Length(); ++i) {
-      MediaInputPort* port = ps->mInputs[i];
-      if (port->mFlags & MediaInputPort::FLAG_BLOCK_INPUT) {
-        MarkStreamBlocking(port->mSource);
-      }
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
-                                          GraphTime aTime,
-                                          GraphTime aEndBlockingDecisions,
-                                          GraphTime* aEnd)
-{
-  for (uint32_t i = 0; i < aStreams.Length(); ++i) {
-    MediaStream* stream = aStreams[i];
-    stream->mBlockInThisPhase = false;
-  }
-
-  for (uint32_t i = 0; i < aStreams.Length(); ++i) {
-    MediaStream* stream = aStreams[i];
-
-    if (stream->mFinished) {
-      GraphTime endTime = StreamTimeToGraphTime(stream,
-         stream->GetStreamBuffer().GetAllTracksEnd());
-      if (endTime <= aTime) {
-        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to being finished", stream));
-        // We'll block indefinitely
-        MarkStreamBlocking(stream);
-        *aEnd = std::min(*aEnd, aEndBlockingDecisions);
-        continue;
-      } else {
-        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is finished, but not blocked yet (end at %f, with blocking at %f)",
-                                    stream, MediaTimeToSeconds(stream->GetBufferEnd()),
-                                    MediaTimeToSeconds(endTime)));
-        *aEnd = std::min(*aEnd, endTime);
-      }
-    }
-
-    GraphTime end;
-    bool explicitBlock = stream->mExplicitBlockerCount.GetAt(aTime, &end) > 0;
-    *aEnd = std::min(*aEnd, end);
-    if (explicitBlock) {
-      STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to explicit blocker", stream));
-      MarkStreamBlocking(stream);
-      continue;
-    }
-
-    if (StreamSuspended(stream)) {
-      STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to being suspended", stream));
-      MarkStreamBlocking(stream);
-      continue;
-    }
-
-    bool underrun = WillUnderrun(stream, aTime, aEndBlockingDecisions, aEnd);
-    if (underrun) {
-      // We'll block indefinitely
-      MarkStreamBlocking(stream);
-      *aEnd = std::min(*aEnd, aEndBlockingDecisions);
-      continue;
-    }
-  }
-  NS_ASSERTION(*aEnd > aTime, "Failed to advance!");
-
-  for (uint32_t i = 0; i < aStreams.Length(); ++i) {
-    MediaStream* stream = aStreams[i];
-    stream->mBlocked.SetAtAndAfter(aTime, stream->mBlockInThisPhase);
-  }
-}
-
-void
 MediaStreamGraphImpl::NotifyHasCurrentData(MediaStream* aStream)
 {
   if (!aStream->mNotifiedHasCurrentData && aStream->mHasCurrentData) {
     for (uint32_t j = 0; j < aStream->mListeners.Length(); ++j) {
       MediaStreamListener* l = aStream->mListeners[j];
       l->NotifyHasCurrentData(this);
     }
     aStream->mNotifiedHasCurrentData = true;
   }
 }
 
 void
-MediaStreamGraphImpl::CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime,
-                                                  MediaStream* aStream)
+MediaStreamGraphImpl::CreateOrDestroyAudioStreams(MediaStream* aStream)
 {
   MOZ_ASSERT(mRealtime, "Should only attempt to create audio streams in real-time mode");
 
   if (aStream->mAudioOutputs.IsEmpty()) {
     aStream->mAudioOutputStreams.Clear();
     return;
   }
 
@@ -908,17 +597,17 @@ MediaStreamGraphImpl::CreateOrDestroyAud
         break;
       }
     }
     if (i < audioOutputStreamsFound.Length()) {
       audioOutputStreamsFound[i] = true;
     } else {
       MediaStream::AudioOutputStream* audioOutputStream =
         aStream->mAudioOutputStreams.AppendElement();
-      audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
+      audioOutputStream->mAudioPlaybackStartTime = mProcessedTime;
       audioOutputStream->mBlockedAudioTime = 0;
       audioOutputStream->mLastTickWritten = 0;
       audioOutputStream->mTrackID = tracks->GetID();
 
       if (!CurrentDriver()->AsAudioCallbackDriver() &&
           !CurrentDriver()->Switching()) {
         MonitorAutoLock mon(mMonitor);
         if (mLifecycleState == LIFECYCLE_RUNNING) {
@@ -933,70 +622,51 @@ MediaStreamGraphImpl::CreateOrDestroyAud
   for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) {
     if (!audioOutputStreamsFound[i]) {
       aStream->mAudioOutputStreams.RemoveElementAt(i);
     }
   }
 }
 
 StreamTime
-MediaStreamGraphImpl::PlayAudio(MediaStream* aStream,
-                                GraphTime aFrom, GraphTime aTo)
+MediaStreamGraphImpl::PlayAudio(MediaStream* aStream)
 {
   MOZ_ASSERT(mRealtime, "Should only attempt to play audio in realtime mode");
 
-  StreamTime ticksWritten = 0;
-  // We compute the number of needed ticks by converting a difference of graph
-  // time rather than by substracting two converted stream time to ensure that
-  // the rounding between {Graph,Stream}Time and track ticks is not dependant
-  // on the absolute value of the {Graph,Stream}Time, and so that number of
-  // ticks to play is the same for each cycle.
-  StreamTime ticksNeeded = aTo - aFrom;
-
-  if (aStream->mAudioOutputStreams.IsEmpty()) {
-    return 0;
-  }
-
   float volume = 0.0f;
   for (uint32_t i = 0; i < aStream->mAudioOutputs.Length(); ++i) {
     volume += aStream->mAudioOutputs[i].mVolume;
   }
 
+  StreamTime ticksWritten = 0;
+
   for (uint32_t i = 0; i < aStream->mAudioOutputStreams.Length(); ++i) {
+    ticksWritten = 0;
+
     MediaStream::AudioOutputStream& audioOutput = aStream->mAudioOutputStreams[i];
     StreamBuffer::Track* track = aStream->mBuffer.FindTrack(audioOutput.mTrackID);
     AudioSegment* audio = track->Get<AudioSegment>();
     AudioSegment output;
 
-    // offset and audioOutput.mLastTickWritten can differ by at most one sample,
-    // because of the rounding issue. We track that to ensure we don't skip a
-    // sample. One sample may be played twice, but this should not happen
-    // again during an unblocked sequence of track samples.
-    StreamTime offset = GraphTimeToStreamTime(aStream, aFrom);
+    StreamTime offset = aStream->GraphTimeToStreamTime(mProcessedTime);
 
     // We don't update aStream->mBufferStartTime here to account for time spent
     // blocked. Instead, we'll update it in UpdateCurrentTimeForStreams after
     // the blocked period has completed. But we do need to make sure we play
     // from the right offsets in the stream buffer, even if we've already
     // written silence for some amount of blocked time after the current time.
-    GraphTime t = aFrom;
-    while (ticksNeeded) {
-      GraphTime end;
-      bool blocked = aStream->mBlocked.GetAt(t, &end);
-      end = std::min(end, aTo);
+    GraphTime t = mProcessedTime;
+    while (t < mStateComputedTime) {
+      bool blocked = t >= aStream->mStartBlocking;
+      GraphTime end = blocked ? mStateComputedTime : aStream->mStartBlocking;
+      NS_ASSERTION(end <= mStateComputedTime, "mStartBlocking is wrong!");
 
       // Check how many ticks of sound we can provide if we are blocked some
       // time in the middle of this cycle.
-      StreamTime toWrite = 0;
-      if (end >= aTo) {
-        toWrite = ticksNeeded;
-      } else {
-        toWrite = end - t;
-      }
-      ticksNeeded -= toWrite;
+      StreamTime toWrite = end - t;
 
       if (blocked) {
         output.InsertNullDataAtStart(toWrite);
         ticksWritten += toWrite;
         STREAM_LOG(LogLevel::Verbose, ("MediaStream %p writing %ld blocking-silence samples for %f to %f (%ld to %ld)\n",
                                     aStream, toWrite, MediaTimeToSeconds(t), MediaTimeToSeconds(end),
                                     offset, offset + toWrite));
       } else {
@@ -1092,18 +762,19 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
 
   TimeStamp currentTimeStamp = CurrentDriver()->GetCurrentTimeStamp();
 
   // Collect any new frames produced in this iteration.
   nsAutoTArray<ImageContainer::NonOwningImage,4> newImages;
   nsRefPtr<Image> blackImage;
 
   MOZ_ASSERT(mProcessedTime >= aStream->mBufferStartTime, "frame position before buffer?");
-  StreamTime frameBufferTime = GraphTimeToStreamTime(aStream, mProcessedTime);
-  StreamTime bufferEndTime = GraphTimeToStreamTime(aStream, mStateComputedTime);
+  // We only look at the non-blocking interval
+  StreamTime frameBufferTime = aStream->GraphTimeToStreamTime(mProcessedTime);
+  StreamTime bufferEndTime = aStream->GraphTimeToStreamTime(aStream->mStartBlocking);
   StreamTime start;
   const VideoChunk* chunk;
   for ( ;
        frameBufferTime < bufferEndTime;
        frameBufferTime = start + chunk->GetDuration()) {
     // Pick the last track that has a video chunk for the time, and
     // schedule its frame.
     chunk = nullptr;
@@ -1131,19 +802,18 @@ MediaStreamGraphImpl::PlayVideo(MediaStr
     Image* image = frame->GetImage();
     STREAM_LOG(LogLevel::Verbose,
                ("MediaStream %p writing video frame %p (%dx%d)",
                 aStream, image, frame->GetIntrinsicSize().width,
                 frame->GetIntrinsicSize().height));
     // Schedule this frame after the previous frame finishes, instead of at
     // its start time.  These times only differ in the case of multiple
     // tracks.
-    GraphTime frameTime =
-      StreamTimeToGraphTime(aStream, frameBufferTime,
-                            INCLUDE_TRAILING_BLOCKED_INTERVAL);
+    // frameBufferTime is in the non-blocking interval.
+    GraphTime frameTime = aStream->StreamTimeToGraphTime(frameBufferTime);
     TimeStamp targetTime = currentTimeStamp +
       TimeDuration::FromSeconds(MediaTimeToSeconds(frameTime - IterationEnd()));
 
     if (frame->GetForceBlack()) {
       if (!blackImage) {
         blackImage = aStream->mVideoOutputs[0]->
           GetImageContainer()->CreateImage(ImageFormat::PLANAR_YCBCR);
         if (blackImage) {
@@ -1253,18 +923,20 @@ MediaStreamGraphImpl::PrepareUpdatesToMa
     mStreamUpdates.SetCapacity(mStreamUpdates.Length() + mStreams.Length() +
         mSuspendedStreams.Length());
     for (MediaStream* stream : AllStreams()) {
       if (!stream->MainThreadNeedsUpdates()) {
         continue;
       }
       StreamUpdate* update = mStreamUpdates.AppendElement();
       update->mStream = stream;
+      // No blocking to worry about here, since we've passed
+      // UpdateCurrentTimeForStreams.
       update->mNextMainThreadCurrentTime =
-        GraphTimeToStreamTime(stream, mProcessedTime);
+        stream->GraphTimeToStreamTime(mProcessedTime);
       update->mNextMainThreadFinished = stream->mNotifiedFinished;
     }
     if (!mPendingUpdateRunnables.IsEmpty()) {
       mUpdateRunnables.AppendElements(Move(mPendingUpdateRunnables));
     }
   }
 
   // Don't send the message to the main thread if it's not going to have
@@ -1283,97 +955,122 @@ MediaStreamGraphImpl::RoundUpToNextAudio
   uint64_t block = ticks >> WEBAUDIO_BLOCK_SIZE_BITS;
   uint64_t nextBlock = block + 1;
   StreamTime nextTicks = nextBlock << WEBAUDIO_BLOCK_SIZE_BITS;
   return nextTicks;
 }
 
 void
 MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
-                                                        TrackRate aSampleRate,
-                                                        GraphTime aFrom,
-                                                        GraphTime aTo)
+                                                        TrackRate aSampleRate)
 {
   MOZ_ASSERT(aStreamIndex <= mFirstCycleBreaker,
              "Cycle breaker is not AudioNodeStream?");
-  GraphTime t = aFrom;
-  while (t < aTo) {
+  GraphTime t = mProcessedTime;
+  while (t < mStateComputedTime) {
     GraphTime next = RoundUpToNextAudioBlock(t);
     for (uint32_t i = mFirstCycleBreaker; i < mStreams.Length(); ++i) {
       auto ns = static_cast<AudioNodeStream*>(mStreams[i]);
       MOZ_ASSERT(ns->AsAudioNodeStream());
       ns->ProduceOutputBeforeInput(t);
     }
     for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
       ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
       if (ps) {
-        ps->ProcessInput(t, next, (next == aTo) ? ProcessedMediaStream::ALLOW_FINISH : 0);
+        ps->ProcessInput(t, next,
+            (next == mStateComputedTime) ? ProcessedMediaStream::ALLOW_FINISH : 0);
       }
     }
     t = next;
   }
-  NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
+  NS_ASSERTION(t == mStateComputedTime,
+               "Something went wrong with rounding to block boundaries");
 }
 
 bool
 MediaStreamGraphImpl::AllFinishedStreamsNotified()
 {
   for (MediaStream* stream : AllStreams()) {
     if (stream->mFinished && !stream->mNotifiedFinished) {
       return false;
     }
   }
   return true;
 }
 
 void
-MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecision)
+MediaStreamGraphImpl::UpdateGraph(GraphTime aEndBlockingDecisions)
 {
   // Calculate independent action times for each batch of messages (each
   // batch corresponding to an event loop task). This isolates the performance
   // of different scripts to some extent.
   for (uint32_t i = 0; i < mFrontMessageQueue.Length(); ++i) {
     nsTArray<nsAutoPtr<ControlMessage> >& messages = mFrontMessageQueue[i].mMessages;
 
     for (uint32_t j = 0; j < messages.Length(); ++j) {
       messages[j]->Run();
     }
   }
   mFrontMessageQueue.Clear();
 
+  MOZ_ASSERT(aEndBlockingDecisions >= mProcessedTime);
+  // The next state computed time can be the same as the previous: it
+  // means the driver would be have been blocking indefinitly, but the graph has
+  // been woken up right after having been to sleep.
+  MOZ_ASSERT(aEndBlockingDecisions >= mStateComputedTime);
+
   UpdateStreamOrder();
 
   bool ensureNextIteration = false;
 
-  // Grab pending stream input.
-  for (uint32_t i = 0; i < mStreams.Length(); ++i) {
-    SourceMediaStream* is = mStreams[i]->AsSourceStream();
-    if (is) {
-      UpdateConsumptionState(is);
-      ExtractPendingInput(is, aEndBlockingDecision, &ensureNextIteration);
+  // Grab pending stream input and compute blocking time
+  for (MediaStream* stream : mStreams) {
+    if (SourceMediaStream* is = stream->AsSourceStream()) {
+      ExtractPendingInput(is, aEndBlockingDecisions, &ensureNextIteration);
     }
+
+    if (stream->mFinished) {
+      // The stream's not suspended, and since it's finished, underruns won't
+      // stop it playing out. So there's no blocking other than what we impose
+      // here.
+      GraphTime endTime = stream->GetStreamBuffer().GetAllTracksEnd() +
+          stream->mBufferStartTime;
+      if (endTime <= mStateComputedTime) {
+        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is blocked due to being finished", stream));
+        stream->mStartBlocking = mStateComputedTime;
+      } else {
+        STREAM_LOG(LogLevel::Verbose, ("MediaStream %p is finished, but not blocked yet (end at %f, with blocking at %f)",
+            stream, MediaTimeToSeconds(stream->GetBufferEnd()),
+            MediaTimeToSeconds(endTime)));
+        // Data can't be added to a finished stream, so underruns are irrelevant.
+        stream->mStartBlocking = std::min(endTime, aEndBlockingDecisions);
+      }
+    } else {
+      stream->mStartBlocking = WillUnderrun(stream, aEndBlockingDecisions);
+    }
+  }
+
+  for (MediaStream* stream : mSuspendedStreams) {
+    stream->mStartBlocking = mStateComputedTime;
   }
 
   // The loop is woken up so soon that IterationEnd() barely advances and we
   // end up having aEndBlockingDecision == mStateComputedTime.
   // Since stream blocking is computed in the interval of
   // [mStateComputedTime, aEndBlockingDecision), it won't be computed at all.
   // We should ensure next iteration so that pending blocking changes will be
   // computed in next loop.
   if (ensureNextIteration ||
-      aEndBlockingDecision == mStateComputedTime) {
+      aEndBlockingDecisions == mStateComputedTime) {
     EnsureNextIteration();
   }
-
-  // Figure out which streams are blocked and when.
-  RecomputeBlocking(aEndBlockingDecision);
 }
 
 void
-MediaStreamGraphImpl::Process(GraphTime aFrom, GraphTime aTo)
+MediaStreamGraphImpl::Process()
 {
   // Play stream contents.
   bool allBlockedForever = true;
   // True when we've done ProcessInput for all processed streams.
   bool doneAllProducing = false;
   // This is the number of frame that are written to the AudioStreams, for
   // this cycle.
   StreamTime ticksPlayed = 0;
@@ -1395,43 +1092,43 @@ MediaStreamGraphImpl::Process(GraphTime 
             if (nextStream) {
               MOZ_ASSERT(n->SampleRate() == nextStream->SampleRate(),
                          "All AudioNodeStreams in the graph must have the same sampling rate");
             }
           }
 #endif
           // Since an AudioNodeStream is present, go ahead and
           // produce audio block by block for all the rest of the streams.
-          ProduceDataForStreamsBlockByBlock(i, n->SampleRate(), aFrom, aTo);
+          ProduceDataForStreamsBlockByBlock(i, n->SampleRate());
           doneAllProducing = true;
         } else {
-          ps->ProcessInput(aFrom, aTo, ProcessedMediaStream::ALLOW_FINISH);
+          ps->ProcessInput(mProcessedTime, mStateComputedTime,
+                           ProcessedMediaStream::ALLOW_FINISH);
           NS_WARN_IF_FALSE(stream->mBuffer.GetEnd() >=
-                           GraphTimeToStreamTime(stream, aTo),
+                           GraphTimeToStreamTimeWithBlocking(stream, mStateComputedTime),
                            "Stream did not produce enough data");
         }
       }
     }
     NotifyHasCurrentData(stream);
     // Only playback audio and video in real-time mode
     if (mRealtime) {
-      CreateOrDestroyAudioStreams(aFrom, stream);
+      CreateOrDestroyAudioStreams(stream);
       if (CurrentDriver()->AsAudioCallbackDriver()) {
-        StreamTime ticksPlayedForThisStream = PlayAudio(stream, aFrom, aTo);
+        StreamTime ticksPlayedForThisStream = PlayAudio(stream);
         if (!ticksPlayed) {
           ticksPlayed = ticksPlayedForThisStream;
         } else {
           MOZ_ASSERT(!ticksPlayedForThisStream || ticksPlayedForThisStream == ticksPlayed,
               "Each stream should have the same number of frame.");
         }
       }
       PlayVideo(stream);
     }
-    GraphTime end;
-    if (!stream->mBlocked.GetAt(aTo, &end) || end < GRAPH_TIME_MAX) {
+    if (stream->mStartBlocking > mProcessedTime) {
       allBlockedForever = false;
     }
   }
 
   if (CurrentDriver()->AsAudioCallbackDriver() && ticksPlayed) {
     mMixer.FinishMixing();
   }
 
@@ -1449,72 +1146,79 @@ MediaStreamGraphImpl::Process(GraphTime 
     }
   }
 
   if (!allBlockedForever) {
     EnsureNextIteration();
   }
 }
 
+void
+MediaStreamGraphImpl::MaybeProduceMemoryReport()
+{
+  MonitorAutoLock lock(mMemoryReportMonitor);
+  if (mNeedsMemoryReport) {
+    mNeedsMemoryReport = false;
+
+    for (MediaStream* s : AllStreams()) {
+      AudioNodeStream* stream = s->AsAudioNodeStream();
+      if (stream) {
+        AudioNodeSizes usage;
+        stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, usage);
+        mAudioStreamSizes.AppendElement(usage);
+      }
+    }
+
+    lock.Notify();
+  }
+}
+
+bool
+MediaStreamGraphImpl::UpdateMainThreadState()
+{
+  MonitorAutoLock lock(mMonitor);
+  bool finalUpdate = mForceShutDown ||
+    (mProcessedTime >= mEndTime && AllFinishedStreamsNotified()) ||
+    (IsEmpty() && mBackMessageQueue.IsEmpty());
+  PrepareUpdatesToMainThreadState(finalUpdate);
+  if (finalUpdate) {
+    // Enter shutdown mode. The stable-state handler will detect this
+    // and complete shutdown. Destroy any streams immediately.
+    STREAM_LOG(LogLevel::Debug, ("MediaStreamGraph %p waiting for main thread cleanup", this));
+    // We'll shut down this graph object if it does not get restarted.
+    mLifecycleState = LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP;
+    // No need to Destroy streams here. The main-thread owner of each
+    // stream is responsible for calling Destroy on them.
+    return false;
+  }
+
+  CurrentDriver()->WaitForNextIteration();
+
+  SwapMessageQueues();
+  return true;
+}
+
 bool
 MediaStreamGraphImpl::OneIteration(GraphTime aStateEnd)
 {
-  {
-    MonitorAutoLock lock(mMemoryReportMonitor);
-    if (mNeedsMemoryReport) {
-      mNeedsMemoryReport = false;
-
-      for (MediaStream* s : AllStreams()) {
-        AudioNodeStream* stream = s->AsAudioNodeStream();
-        if (stream) {
-          AudioNodeSizes usage;
-          stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, usage);
-          mAudioStreamSizes.AppendElement(usage);
-        }
-      }
-
-      lock.Notify();
-    }
-  }
-
-  GraphTime stateFrom = mStateComputedTime;
+  MaybeProduceMemoryReport();
+
   GraphTime stateEnd = std::min(aStateEnd, mEndTime);
   UpdateGraph(stateEnd);
 
-  Process(stateFrom, stateEnd);
+  mStateComputedTime = stateEnd;
+
+  Process();
+
+  GraphTime oldProcessedTime = mProcessedTime;
   mProcessedTime = stateEnd;
 
-  UpdateCurrentTimeForStreams(stateFrom, stateEnd);
-
-  // Send updates to the main thread and wait for the next control loop
-  // iteration.
-  {
-    MonitorAutoLock lock(mMonitor);
-    bool finalUpdate = mForceShutDown ||
-      (stateEnd >= mEndTime && AllFinishedStreamsNotified()) ||
-      (IsEmpty() && mBackMessageQueue.IsEmpty());
-    PrepareUpdatesToMainThreadState(finalUpdate);
-    if (finalUpdate) {
-      // Enter shutdown mode. The stable-state handler will detect this
-      // and complete shutdown. Destroy any streams immediately.
-      STREAM_LOG(LogLevel::Debug, ("MediaStreamGraph %p waiting for main thread cleanup", this));
-      // We'll shut down this graph object if it does not get restarted.
-      mLifecycleState = LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP;
-      // No need to Destroy streams here. The main-thread owner of each
-      // stream is responsible for calling Destroy on them.
-      return false;
-    }
-
-    CurrentDriver()->WaitForNextIteration();
-
-    SwapMessageQueues();
-  }
-  mFlushSourcesNow = false;
-
-  return true;
+  UpdateCurrentTimeForStreams(oldProcessedTime);
+
+  return UpdateMainThreadState();
 }
 
 void
 MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate)
 {
   mMonitor.AssertCurrentThreadOwns();
 
   MediaStream* stream = aUpdate->mStream;
@@ -1873,18 +1577,18 @@ MediaStreamGraphImpl::AppendMessage(Cont
   }
 
   mCurrentTaskMessageQueue.AppendElement(aMessage);
   EnsureRunInStableState();
 }
 
 MediaStream::MediaStream(DOMMediaStream* aWrapper)
   : mBufferStartTime(0)
-  , mExplicitBlockerCount(0)
-  , mBlocked(false)
+  , mStartBlocking(GRAPH_TIME_MAX)
+  , mSuspendedCount(0)
   , mFinished(false)
   , mNotifiedFinished(false)
   , mNotifiedBlocked(false)
   , mHasCurrentData(false)
   , mNotifiedHasCurrentData(false)
   , mWrapper(aWrapper)
   , mMainThreadCurrentTime(0)
   , mMainThreadFinished(false)
@@ -1914,21 +1618,19 @@ MediaStream::SizeOfExcludingThis(MallocS
   // - mVideoOutputs - elements
   // - mLastPlayedVideoFrame
   // - mListeners - elements
   // - mAudioOutputStream - elements
 
   amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
   amount += mAudioOutputs.ShallowSizeOfExcludingThis(aMallocSizeOf);
   amount += mVideoOutputs.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  amount += mExplicitBlockerCount.SizeOfExcludingThis(aMallocSizeOf);
   amount += mListeners.ShallowSizeOfExcludingThis(aMallocSizeOf);
   amount += mMainThreadListeners.ShallowSizeOfExcludingThis(aMallocSizeOf);
   amount += mDisabledTrackIDs.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  amount += mBlocked.SizeOfExcludingThis(aMallocSizeOf);
   amount += mConsumers.ShallowSizeOfExcludingThis(aMallocSizeOf);
 
   return amount;
 }
 
 size_t
 MediaStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
@@ -1961,29 +1663,35 @@ MediaStream::SetGraphImpl(MediaStreamGra
 {
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(aGraph);
   SetGraphImpl(graph);
 }
 
 StreamTime
 MediaStream::GraphTimeToStreamTime(GraphTime aTime)
 {
-  return GraphImpl()->GraphTimeToStreamTime(this, aTime);
-}
-
-StreamTime
-MediaStream::GraphTimeToStreamTimeOptimistic(GraphTime aTime)
-{
-  return GraphImpl()->GraphTimeToStreamTimeOptimistic(this, aTime);
+  NS_ASSERTION(mStartBlocking == GraphImpl()->mStateComputedTime ||
+               aTime <= mStartBlocking,
+               "Incorrectly ignoring blocking!");
+  return aTime - mBufferStartTime;
 }
 
 GraphTime
 MediaStream::StreamTimeToGraphTime(StreamTime aTime)
 {
-  return GraphImpl()->StreamTimeToGraphTime(this, aTime, 0);
+  NS_ASSERTION(mStartBlocking == GraphImpl()->mStateComputedTime ||
+               aTime + mBufferStartTime <= mStartBlocking,
+               "Incorrectly ignoring blocking!");
+  return aTime + mBufferStartTime;
+}
+
+StreamTime
+MediaStream::GraphTimeToStreamTimeWithBlocking(GraphTime aTime)
+{
+  return GraphImpl()->GraphTimeToStreamTimeWithBlocking(this, aTime);
 }
 
 void
 MediaStream::FinishOnGraphThread()
 {
   GraphImpl()->FinishStream(this);
 }
 
@@ -2153,72 +1861,51 @@ MediaStream::RemoveVideoOutput(VideoFram
       mStream->RemoveVideoOutputImpl(mContainer);
     }
     nsRefPtr<VideoFrameContainer> mContainer;
   };
   GraphImpl()->AppendMessage(new Message(this, aContainer));
 }
 
 void
-MediaStream::ChangeExplicitBlockerCount(int32_t aDelta)
+MediaStream::Suspend()
 {
   class Message : public ControlMessage {
   public:
-    Message(MediaStream* aStream, int32_t aDelta) :
-      ControlMessage(aStream), mDelta(aDelta) {}
+    explicit Message(MediaStream* aStream) :
+      ControlMessage(aStream) {}
     virtual void Run()
     {
-      mStream->ChangeExplicitBlockerCountImpl(
-          mStream->GraphImpl()->mStateComputedTime, mDelta);
+      mStream->GraphImpl()->IncrementSuspendCount(mStream);
     }
-    int32_t mDelta;
   };
 
   // This can happen if this method has been called asynchronously, and the
   // stream has been destroyed since then.
   if (mMainThreadDestroyed) {
     return;
   }
-  GraphImpl()->AppendMessage(new Message(this, aDelta));
-}
-
-void
-MediaStream::BlockStreamIfNeeded()
-{
-  class Message : public ControlMessage {
-  public:
-    explicit Message(MediaStream* aStream) : ControlMessage(aStream)
-    { }
-    virtual void Run()
-    {
-      mStream->BlockStreamIfNeededImpl(
-          mStream->GraphImpl()->mStateComputedTime);
-    }
-  };
-
-  if (mMainThreadDestroyed) {
-    return;
-  }
   GraphImpl()->AppendMessage(new Message(this));
 }
 
 void
-MediaStream::UnblockStreamIfNeeded()
+MediaStream::Resume()
 {
   class Message : public ControlMessage {
   public:
-    explicit Message(MediaStream* aStream) : ControlMessage(aStream)
-    { }
+    explicit Message(MediaStream* aStream) :
+      ControlMessage(aStream) {}
     virtual void Run()
     {
-      mStream->UnblockStreamIfNeededImpl(
-          mStream->GraphImpl()->mStateComputedTime);
+      mStream->GraphImpl()->DecrementSuspendCount(mStream);
     }
   };
 
+  // This can happen if this method has been called asynchronously, and the
+  // stream has been destroyed since then.
   if (mMainThreadDestroyed) {
     return;
   }
   GraphImpl()->AppendMessage(new Message(this));
 }
 
 void
 MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
@@ -2639,30 +2326,16 @@ SourceMediaStream::EndAllTrackAndFinish(
     SourceMediaStream::TrackData* data = &mUpdateTracks[i];
     data->mCommands |= TRACK_END;
   }
   mPendingTracks.Clear();
   FinishWithLockHeld();
   // we will call NotifyEvent() to let GetUserMedia know
 }
 
-StreamTime
-SourceMediaStream::GetBufferedTicks(TrackID aID)
-{
-  StreamBuffer::Track* track  = mBuffer.FindTrack(aID);
-  if (track) {
-    MediaSegment* segment = track->GetSegment();
-    if (segment) {
-      return segment->GetDuration() -
-          GraphTimeToStreamTime(GraphImpl()->mStateComputedTime);
-    }
-  }
-  return 0;
-}
-
 void
 SourceMediaStream::RegisterForAudioMixing()
 {
   MutexAutoLock lock(mMutex);
   mNeedsMixing = true;
 }
 
 bool
@@ -2698,31 +2371,25 @@ MediaInputPort::Disconnect()
 
   GraphImpl()->SetStreamOrderDirty();
 }
 
 MediaInputPort::InputInterval
 MediaInputPort::GetNextInputInterval(GraphTime aTime)
 {
   InputInterval result = { GRAPH_TIME_MAX, GRAPH_TIME_MAX, false };
-  GraphTime t = aTime;
-  GraphTime end;
-  for (;;) {
-    if (!mDest->mBlocked.GetAt(t, &end)) {
-      break;
-    }
-    if (end >= GRAPH_TIME_MAX) {
-      return result;
-    }
-    t = end;
+  if (aTime >= mDest->mStartBlocking) {
+    return result;
   }
-  result.mStart = t;
-  GraphTime sourceEnd;
-  result.mInputIsBlocked = mSource->mBlocked.GetAt(t, &sourceEnd);
-  result.mEnd = std::min(end, sourceEnd);
+  result.mStart = aTime;
+  result.mEnd = mDest->mStartBlocking;
+  result.mInputIsBlocked = aTime >= mSource->mStartBlocking;
+  if (!result.mInputIsBlocked) {
+    result.mEnd = std::min(result.mEnd, mSource->mStartBlocking);
+  }
   return result;
 }
 
 void
 MediaInputPort::Destroy()
 {
   class Message : public ControlMessage {
   public:
@@ -2759,17 +2426,17 @@ MediaInputPort::Graph()
 void
 MediaInputPort::SetGraphImpl(MediaStreamGraphImpl* aGraph)
 {
   MOZ_ASSERT(!mGraph || !aGraph, "Should only be set once");
   mGraph = aGraph;
 }
 
 already_AddRefed<MediaInputPort>
-ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags,
+ProcessedMediaStream::AllocateInputPort(MediaStream* aStream,
                                         uint16_t aInputNumber, uint16_t aOutputNumber)
 {
   // This method creates two references to the MediaInputPort: one for
   // the main thread, and one for the MediaStreamGraph.
   class Message : public ControlMessage {
   public:
     explicit Message(MediaInputPort* aPort)
       : ControlMessage(aPort->GetDestination()),
@@ -2782,17 +2449,17 @@ ProcessedMediaStream::AllocateInputPort(
       unused << mPort.forget();
     }
     virtual void RunDuringShutdown()
     {
       Run();
     }
     nsRefPtr<MediaInputPort> mPort;
   };
-  nsRefPtr<MediaInputPort> port = new MediaInputPort(aStream, this, aFlags,
+  nsRefPtr<MediaInputPort> port = new MediaInputPort(aStream, this,
                                                      aInputNumber, aOutputNumber);
   port->SetGraphImpl(GraphImpl());
   GraphImpl()->AppendMessage(new Message(port));
   return port.forget();
 }
 
 void
 ProcessedMediaStream::Finish()
@@ -2845,18 +2512,16 @@ MediaStreamGraphImpl::MediaStreamGraphIm
   , mPortCount(0)
   , mNeedAnotherIteration(false)
   , mGraphDriverAsleep(false)
   , mMonitor("MediaStreamGraphImpl")
   , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
   , mEndTime(GRAPH_TIME_MAX)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
-  , mFlushSourcesNow(false)
-  , mFlushSourcesOnNextIteration(false)
   , mDetectedNotRunning(false)
   , mPostedRunInStableState(false)
   , mRealtime(aDriverRequested != OFFLINE_THREAD_DRIVER)
   , mNonRealtimeProcessing(false)
   , mStreamOrderDirty(false)
   , mLatencyLog(AsyncLatencyLogger::Get())
 #ifdef MOZ_WEBRTC
   , mFarendObserverRef(nullptr)
@@ -3103,21 +2768,24 @@ ProcessedMediaStream*
 MediaStreamGraph::CreateAudioCaptureStream(DOMMediaStream* aWrapper)
 {
   AudioCaptureStream* stream = new AudioCaptureStream(aWrapper);
   AddStream(stream);
   return stream;
 }
 
 void
-MediaStreamGraph::AddStream(MediaStream* aStream)
+MediaStreamGraph::AddStream(MediaStream* aStream, uint32_t aFlags)
 {
   NS_ADDREF(aStream);
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
   aStream->SetGraphImpl(graph);
+  if (aFlags & ADD_STREAM_SUSPENDED) {
+    aStream->IncrementSuspendCount();
+  }
   graph->AppendMessage(new CreateMessage(aStream));
 }
 
 class GraphStartedRunnable final : public nsRunnable
 {
 public:
   GraphStartedRunnable(AudioNodeStream* aStream, MediaStreamGraph* aGraph)
   : mStream(aStream)
@@ -3170,71 +2838,56 @@ MediaStreamGraph::NotifyWhenGraphStarted
 
   if (!aStream->IsDestroyed()) {
     MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this);
     graphImpl->AppendMessage(new GraphStartedNotificationControlMessage(aStream));
   }
 }
 
 void
-MediaStreamGraphImpl::ResetVisitedStreamState()
+MediaStreamGraphImpl::IncrementSuspendCount(MediaStream* aStream)
 {
-  // Reset the visited/consumed/blocked state of the streams.
-  for (MediaStream* stream : AllStreams()) {
-    ProcessedMediaStream* ps = stream->AsProcessedStream();
+  if (!aStream->IsSuspended()) {
+    MOZ_ASSERT(mStreams.Contains(aStream));
+    mStreams.RemoveElement(aStream);
+    mSuspendedStreams.AppendElement(aStream);
+    SetStreamOrderDirty();
+  }
+  aStream->IncrementSuspendCount();
+}
+
+void
+MediaStreamGraphImpl::DecrementSuspendCount(MediaStream* aStream)
+{
+  bool wasSuspended = aStream->IsSuspended();
+  aStream->DecrementSuspendCount();
+  if (wasSuspended && !aStream->IsSuspended()) {
+    MOZ_ASSERT(mSuspendedStreams.Contains(aStream));
+    mSuspendedStreams.RemoveElement(aStream);
+    mStreams.AppendElement(aStream);
+    ProcessedMediaStream* ps = aStream->AsProcessedStream();
     if (ps) {
       ps->mCycleMarker = NOT_VISITED;
-      ps->mIsConsumed = false;
-      ps->mInBlockingSet = false;
     }
+    SetStreamOrderDirty();
   }
 }
 
 void
-MediaStreamGraphImpl::StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId,
-                                  mozilla::LinkedList<MediaStream>& aStreamSet)
-{
-   nsTArray<MediaStream*>* runningAndSuspendedPair[2];
-   runningAndSuspendedPair[0] = &mStreams;
-   runningAndSuspendedPair[1] = &mSuspendedStreams;
-
-  for (uint32_t array = 0; array < 2; array++) {
-    for (uint32_t i = 0; i < runningAndSuspendedPair[array]->Length(); ++i) {
-      MediaStream* stream = (*runningAndSuspendedPair[array])[i];
-      if (aAudioContextId == stream->AudioContextId()) {
-        aStreamSet.insertFront(stream);
-      }
-    }
-  }
-}
-
-void
-MediaStreamGraphImpl::MoveStreams(AudioContextOperation aAudioContextOperation,
-                                  mozilla::LinkedList<MediaStream>& aStreamSet)
+MediaStreamGraphImpl::SuspendOrResumeStreams(AudioContextOperation aAudioContextOperation,
+                                             const nsTArray<MediaStream*>& aStreamSet)
 {
   // For our purpose, Suspend and Close are equivalent: we want to remove the
   // streams from the set of streams that are going to be processed.
-  nsTArray<MediaStream*>& from =
-    aAudioContextOperation == AudioContextOperation::Resume ? mSuspendedStreams
-                                                            : mStreams;
-  nsTArray<MediaStream*>& to =
-    aAudioContextOperation == AudioContextOperation::Resume ? mStreams
-                                                            : mSuspendedStreams;
-
-  MediaStream* stream;
-  while ((stream = aStreamSet.getFirst())) {
-    // It is posible to not find the stream here, if there has been two
-    // suspend/resume/close calls in a row.
-    auto i = from.IndexOf(stream);
-    if (i != from.NoIndex) {
-      from.RemoveElementAt(i);
-      to.AppendElement(stream);
+  for (MediaStream* stream : aStreamSet) {
+    if (aAudioContextOperation == AudioContextOperation::Resume) {
+      DecrementSuspendCount(stream);
+    } else {
+      IncrementSuspendCount(stream);
     }
-
-    stream->remove();
   }
   STREAM_LOG(LogLevel::Debug, ("Moving streams between suspended and running"
       "state: mStreams: %d, mSuspendedStreams: %d\n", mStreams.Length(),
       mSuspendedStreams.Length()));
 #ifdef DEBUG
   // The intersection of the two arrays should be null.
   for (uint32_t i = 0; i < mStreams.Length(); i++) {
     for (uint32_t j = 0; j < mSuspendedStreams.Length(); j++) {
@@ -3265,32 +2918,23 @@ MediaStreamGraphImpl::AudioContextOperat
   }
 
   nsCOMPtr<nsIRunnable> event = new dom::StateChangeTask(
       aStream->AsAudioNodeStream(), aPromise, state);
   NS_DispatchToMainThread(event.forget());
 }
 
 void
-MediaStreamGraphImpl::ApplyAudioContextOperationImpl(AudioNodeStream* aStream,
-                                               AudioContextOperation aOperation,
-                                               void* aPromise)
+MediaStreamGraphImpl::ApplyAudioContextOperationImpl(
+    MediaStream* aDestinationStream, const nsTArray<MediaStream*>& aStreams,
+    AudioContextOperation aOperation, void* aPromise)
 {
   MOZ_ASSERT(CurrentDriver()->OnThread());
-  mozilla::LinkedList<MediaStream> streamSet;
-
-  SetStreamOrderDirty();
-
-  ResetVisitedStreamState();
-
-  StreamSetForAudioContext(aStream->AudioContextId(), streamSet);
-
-  MoveStreams(aOperation, streamSet);
-  MOZ_ASSERT(!streamSet.getFirst(),
-      "Streams should be removed from the list after having been moved.");
+
+  SuspendOrResumeStreams(aOperation, aStreams);
 
   // If we have suspended the last AudioContext, and we don't have other
   // streams that have audio, this graph will automatically switch to a
   // SystemCallbackDriver, because it can't find a MediaStream that has an audio
   // track. When resuming, force switching to an AudioCallbackDriver (if we're
   // not already switching). It would have happened at the next iteration
   // anyways, but doing this now save some time.
   if (aOperation == AudioContextOperation::Resume) {
@@ -3299,21 +2943,22 @@ MediaStreamGraphImpl::ApplyAudioContextO
       if (CurrentDriver()->Switching()) {
         MOZ_ASSERT(CurrentDriver()->NextDriver()->AsAudioCallbackDriver());
         driver = CurrentDriver()->NextDriver()->AsAudioCallbackDriver();
       } else {
         driver = new AudioCallbackDriver(this);
         mMixer.AddCallback(driver);
         CurrentDriver()->SwitchAtNextIteration(driver);
       }
-      driver->EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation);
+      driver->EnqueueStreamAndPromiseForOperation(aDestinationStream,
+          aPromise, aOperation);
     } else {
       // We are resuming a context, but we are already using an
       // AudioCallbackDriver, we can resolve the promise now.
-      AudioContextOperationCompleted(aStream, aPromise, aOperation);
+      AudioContextOperationCompleted(aDestinationStream, aPromise, aOperation);
     }
   }
   // Close, suspend: check if we are going to switch to a
   // SystemAudioCallbackDriver, and pass the promise to the AudioCallbackDriver
   // if that's the case, so it can notify the content.
   // This is the same logic as in UpdateStreamOrder, but it's simpler to have it
   // here as well so we don't have to store the Promise(s) on the Graph.
   if (aOperation != AudioContextOperation::Resume) {
@@ -3325,75 +2970,84 @@ MediaStreamGraphImpl::ApplyAudioContextO
       }
       for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer(), MediaSegment::AUDIO);
           !tracks.IsEnded(); tracks.Next()) {
         audioTrackPresent = true;
       }
     }
     if (!audioTrackPresent && CurrentDriver()->AsAudioCallbackDriver()) {
       CurrentDriver()->AsAudioCallbackDriver()->
-        EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation);
+        EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise,
+                                            aOperation);
 
       SystemClockDriver* driver;
       if (CurrentDriver()->NextDriver()) {
         MOZ_ASSERT(!CurrentDriver()->NextDriver()->AsAudioCallbackDriver());
       } else {
         driver = new SystemClockDriver(this);
         mMixer.RemoveCallback(CurrentDriver()->AsAudioCallbackDriver());
         CurrentDriver()->SwitchAtNextIteration(driver);
       }
       // We are closing or suspending an AudioContext, but we just got resumed.
       // Queue the operation on the next driver so that the ordering is
       // preserved.
     } else if (!audioTrackPresent && CurrentDriver()->Switching()) {
       MOZ_ASSERT(CurrentDriver()->NextDriver()->AsAudioCallbackDriver());
       CurrentDriver()->NextDriver()->AsAudioCallbackDriver()->
-        EnqueueStreamAndPromiseForOperation(aStream, aPromise, aOperation);
+        EnqueueStreamAndPromiseForOperation(aDestinationStream, aPromise,
+                                            aOperation);
     } else {
       // We are closing or suspending an AudioContext, but something else is
       // using the audio stream, we can resolve the promise now.
-      AudioContextOperationCompleted(aStream, aPromise, aOperation);
+      AudioContextOperationCompleted(aDestinationStream, aPromise, aOperation);
     }
   }
 }
 
 void
-MediaStreamGraph::ApplyAudioContextOperation(AudioNodeStream* aNodeStream,
+MediaStreamGraph::ApplyAudioContextOperation(MediaStream* aDestinationStream,
+                                             const nsTArray<MediaStream*>& aStreams,
                                              AudioContextOperation aOperation,
                                              void* aPromise)
 {
   class AudioContextOperationControlMessage : public ControlMessage
   {
   public:
-    AudioContextOperationControlMessage(AudioNodeStream* aStream,
+    AudioContextOperationControlMessage(MediaStream* aDestinationStream,
+                                        const nsTArray<MediaStream*>& aStreams,
                                         AudioContextOperation aOperation,
                                         void* aPromise)
-      : ControlMessage(aStream)
+      : ControlMessage(aDestinationStream)
+      , mStreams(aStreams)
       , mAudioContextOperation(aOperation)
       , mPromise(aPromise)
     {
     }
     virtual void Run()
     {
-      mStream->GraphImpl()->ApplyAudioContextOperationImpl(
-        mStream->AsAudioNodeStream(), mAudioContextOperation, mPromise);
+      mStream->GraphImpl()->ApplyAudioContextOperationImpl(mStream,
+        mStreams, mAudioContextOperation, mPromise);
     }
     virtual void RunDuringShutdown()
     {
       MOZ_ASSERT(false, "We should be reviving the graph?");
     }
 
   private:
+    // We don't need strong references here for the same reason ControlMessage
+    // doesn't.
+    nsTArray<MediaStream*> mStreams;
     AudioContextOperation mAudioContextOperation;
     void* mPromise;
   };
 
   MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this);
   graphImpl->AppendMessage(
-    new AudioContextOperationControlMessage(aNodeStream, aOperation, aPromise));
+    new AudioContextOperationControlMessage(aDestinationStream, aStreams,
+                                            aOperation, aPromise));
 }
 
 bool
 MediaStreamGraph::IsNonRealtime() const
 {
   const MediaStreamGraphImpl* impl = static_cast<const MediaStreamGraphImpl*>(this);
   MediaStreamGraphImpl* graph;
 
@@ -3475,15 +3129,15 @@ MediaStreamGraph::ConnectToCaptureStream
 already_AddRefed<MediaInputPort>
 MediaStreamGraphImpl::ConnectToCaptureStream(uint64_t aWindowId,
                                              MediaStream* aMediaStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   for (uint32_t i = 0; i < mWindowCaptureStreams.Length(); i++) {
     if (mWindowCaptureStreams[i].mWindowId == aWindowId) {
       ProcessedMediaStream* sink = mWindowCaptureStreams[i].mCaptureStreamSink;
-      return sink->AllocateInputPort(aMediaStream, 0);
+      return sink->AllocateInputPort(aMediaStream);
     }
   }
   return nullptr;
 }
 
 } // namespace mozilla
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -12,17 +12,16 @@
 
 #include "mozilla/dom/AudioChannelBinding.h"
 
 #include "AudioSegment.h"
 #include "AudioStream.h"
 #include "nsTArray.h"
 #include "nsIRunnable.h"
 #include "StreamBuffer.h"
-#include "TimeVarying.h"
 #include "VideoFrameContainer.h"
 #include "VideoSegment.h"
 #include "MainThreadUtils.h"
 #include "nsAutoRef.h"
 #include <speex/speex_resampler.h>
 #include "DOMMediaStream.h"
 #include "AudioContext.h"
 
@@ -97,28 +96,16 @@ class MediaStreamGraph;
 class MediaStreamListener {
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~MediaStreamListener() {}
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStreamListener)
 
-  enum Consumption {
-    CONSUMED,
-    NOT_CONSUMED
-  };
-
-  /**
-   * Notify that the stream is hooked up and we'd like to start or stop receiving
-   * data on it. Only fires on SourceMediaStreams.
-   * The initial state is assumed to be NOT_CONSUMED.
-   */
-  virtual void NotifyConsumptionChanged(MediaStreamGraph* aGraph, Consumption aConsuming) {}
-
   /**
    * When a SourceMediaStream has pulling enabled, and the MediaStreamGraph
    * control loop is ready to pull, this gets called. A NotifyPull implementation
    * is allowed to call the SourceMediaStream methods that alter track
    * data. It is not allowed to make other MediaStream API calls, including
    * calls to add or remove MediaStreamListeners. It is not allowed to block
    * for any length of time.
    * aDesiredTime is the stream time we would like to get data up to. Data
@@ -314,17 +301,16 @@ class CameraPreviewMediaStream;
  * to handle this.
  */
 class MediaStream : public mozilla::LinkedListElement<MediaStream>
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream)
 
   explicit MediaStream(DOMMediaStream* aWrapper);
-  virtual dom::AudioContext::AudioContextId AudioContextId() const { return 0; }
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~MediaStream()
   {
     MOZ_COUNT_DTOR(MediaStream);
     NS_ASSERTION(mMainThreadDestroyed, "Should have been destroyed already");
     NS_ASSERTION(mMainThreadListeners.IsEmpty(),
@@ -358,21 +344,23 @@ public:
   virtual void AddAudioOutput(void* aKey);
   virtual void SetAudioOutputVolume(void* aKey, float aVolume);
   virtual void RemoveAudioOutput(void* aKey);
   // Since a stream can be played multiple ways, we need to be able to
   // play to multiple VideoFrameContainers.
   // Only the first enabled video track is played.
   virtual void AddVideoOutput(VideoFrameContainer* aContainer);
   virtual void RemoveVideoOutput(VideoFrameContainer* aContainer);
-  // Explicitly block. Useful for example if a media element is pausing
-  // and we need to stop its stream emitting its buffered data.
-  virtual void ChangeExplicitBlockerCount(int32_t aDelta);
-  void BlockStreamIfNeeded();
-  void UnblockStreamIfNeeded();
+  // Explicitly suspend. Useful for example if a media element is pausing
+  // and we need to stop its stream emitting its buffered data. As soon as the
+  // Suspend message reaches the graph, the stream stops processing. It
+  // ignores its inputs and produces silence/no video until Resumed. Its
+  // current time does not advance.
+  virtual void Suspend();
+  virtual void Resume();
   // Events will be dispatched by calling methods of aListener.
   virtual void AddListener(MediaStreamListener* aListener);
   virtual void RemoveListener(MediaStreamListener* aListener);
   // A disabled track has video replaced by black, and audio replaced by
   // silence.
   void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
 
   // Finish event will be notified by calling methods of aListener. It is the
@@ -461,50 +449,20 @@ public:
   }
   void RemoveVideoOutputImpl(VideoFrameContainer* aContainer)
   {
     // Ensure that any frames currently queued for playback by the compositor
     // are removed.
     aContainer->ClearFutureFrames();
     mVideoOutputs.RemoveElement(aContainer);
   }
-  void ChangeExplicitBlockerCountImpl(GraphTime aTime, int32_t aDelta)
-  {
-    mExplicitBlockerCount.SetAtAndAfter(aTime, mExplicitBlockerCount.GetAt(aTime) + aDelta);
-  }
-  void BlockStreamIfNeededImpl(GraphTime aTime)
-  {
-    bool blocked = mExplicitBlockerCount.GetAt(aTime) > 0;
-    if (blocked) {
-      return;
-    }
-    ChangeExplicitBlockerCountImpl(aTime, 1);
-  }
-  void UnblockStreamIfNeededImpl(GraphTime aTime)
-  {
-    bool blocked = mExplicitBlockerCount.GetAt(aTime) > 0;
-    if (!blocked) {
-      return;
-    }
-    ChangeExplicitBlockerCountImpl(aTime, -1);
-  }
   void AddListenerImpl(already_AddRefed<MediaStreamListener> aListener);
   void RemoveListenerImpl(MediaStreamListener* aListener);
   void RemoveAllListenersImpl();
   virtual void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled);
-  /**
-   * Returns true when this stream requires the contents of its inputs even if
-   * its own outputs are not being consumed. This is used to signal inputs to
-   * this stream that they are being consumed; when they're not being consumed,
-   * we make some optimizations.
-   */
-  virtual bool IsIntrinsicallyConsumed() const
-  {
-    return !mAudioOutputs.IsEmpty() || !mVideoOutputs.IsEmpty();
-  }
 
   void AddConsumer(MediaInputPort* aPort)
   {
     mConsumers.AppendElement(aPort);
   }
   void RemoveConsumer(MediaInputPort* aPort)
   {
     mConsumers.RemoveElement(aPort);
@@ -538,29 +496,32 @@ public:
   {
     return RateConvertTicksRoundDown(mBuffer.GraphRate(), aRate, aTicks);
   }
   /**
    * Convert graph time to stream time. aTime must be <= mStateComputedTime
    * to ensure we know exactly how much time this stream will be blocked during
    * the interval.
    */
+  StreamTime GraphTimeToStreamTimeWithBlocking(GraphTime aTime);
+  /**
+   * Convert graph time to stream time. This assumes there is no blocking time
+   * to take account of, which is always true except between a stream
+   * having its blocking time calculated in UpdateGraph and its blocking time
+   * taken account of in UpdateCurrentTimeForStreams.
+   */
   StreamTime GraphTimeToStreamTime(GraphTime aTime);
   /**
-   * Convert graph time to stream time. aTime can be > mStateComputedTime,
-   * in which case we optimistically assume the stream will not be blocked
-   * after mStateComputedTime.
-   */
-  StreamTime GraphTimeToStreamTimeOptimistic(GraphTime aTime);
-  /**
-   * Convert stream time to graph time. The result can be > mStateComputedTime,
-   * in which case we did the conversion optimistically assuming the stream
-   * will not be blocked after mStateComputedTime.
+   * Convert stream time to graph time. This assumes there is no blocking time
+   * to take account of, which is always true except between a stream
+   * having its blocking time calculated in UpdateGraph and its blocking time
+   * taken account of in UpdateCurrentTimeForStreams.
    */
   GraphTime StreamTimeToGraphTime(StreamTime aTime);
+
   bool IsFinishedOnGraphThread() { return mFinished; }
   void FinishOnGraphThread();
 
   bool HasCurrentData() { return mHasCurrentData; }
 
   StreamBuffer::Track* EnsureTrack(TrackID aTrack);
 
   virtual void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr);
@@ -578,22 +539,28 @@ public:
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
   void SetAudioChannelType(dom::AudioChannel aType) { mAudioChannelType = aType; }
   dom::AudioChannel AudioChannelType() const { return mAudioChannelType; }
 
+  bool IsSuspended() { return mSuspendedCount > 0; }
+  void IncrementSuspendCount() { ++mSuspendedCount; }
+  void DecrementSuspendCount()
+  {
+    NS_ASSERTION(mSuspendedCount > 0, "Suspend count underrun");
+    --mSuspendedCount;
+  }
+
 protected:
   void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime)
   {
     mBufferStartTime += aBlockedTime;
-    mExplicitBlockerCount.AdvanceCurrentTime(aCurrentTime);
-
     mBuffer.ForgetUpTo(aCurrentTime - mBufferStartTime);
   }
 
   void NotifyMainThreadListeners()
   {
     NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
 
     for (int32_t i = mMainThreadListeners.Length() - 1; i >= 0; --i) {
@@ -631,30 +598,25 @@ protected:
     void* mKey;
     float mVolume;
   };
   nsTArray<AudioOutput> mAudioOutputs;
   nsTArray<nsRefPtr<VideoFrameContainer> > mVideoOutputs;
   // We record the last played video frame to avoid playing the frame again
   // with a different frame id.
   VideoFrame mLastPlayedVideoFrame;
-  // The number of times this stream has been explicitly blocked by the control
-  // API, minus the number of times it has been explicitly unblocked.
-  TimeVarying<GraphTime,uint32_t,0> mExplicitBlockerCount;
   nsTArray<nsRefPtr<MediaStreamListener> > mListeners;
   nsTArray<MainThreadMediaStreamListener*> mMainThreadListeners;
   nsTArray<TrackID> mDisabledTrackIDs;
 
-  // Precomputed blocking status (over GraphTime).
-  // This is only valid between the graph's mCurrentTime and
-  // mStateComputedTime. The stream is considered to have
-  // not been blocked before mCurrentTime (its mBufferStartTime is increased
-  // as necessary to account for that time instead) --- this avoids us having to
-  // record the entire history of the stream's blocking-ness in mBlocked.
-  TimeVarying<GraphTime,bool,5> mBlocked;
+  // GraphTime at which this stream starts blocking.
+  // This is only valid up to mStateComputedTime. The stream is considered to
+  // have not been blocked before mCurrentTime (its mBufferStartTime is increased
+  // as necessary to account for that time instead).
+  GraphTime mStartBlocking;
 
   // MediaInputPorts to which this is connected
   nsTArray<MediaInputPort*> mConsumers;
 
   // Where audio output is going. There is one AudioOutputStream per
   // audio track.
   struct AudioOutputStream
   {
@@ -666,16 +628,22 @@ protected:
     MediaTime mBlockedAudioTime;
     // Last tick written to the audio output.
     StreamTime mLastTickWritten;
     TrackID mTrackID;
   };
   nsTArray<AudioOutputStream> mAudioOutputStreams;
 
   /**
+   * Number of outstanding suspend operations on this stream. Stream is
+   * suspended when this is > 0.
+   */
+  int32_t mSuspendedCount;
+
+  /**
    * When true, this means the stream will be finished once all
    * buffered data has been consumed.
    */
   bool mFinished;
   /**
    * When true, mFinished is true and we've played all the data in this stream
    * and fired NotifyFinished notifications.
    */
@@ -692,26 +660,16 @@ protected:
    * unblock it until there's more data.
    */
   bool mHasCurrentData;
   /**
    * True if mHasCurrentData is true and we've notified listeners.
    */
   bool mNotifiedHasCurrentData;
 
-  // True if the stream is being consumed (i.e. has track data being played,
-  // or is feeding into some stream that is being consumed).
-  bool mIsConsumed;
-  // Temporary data for computing blocking status of streams
-  // True if we've added this stream to the set of streams we're computing
-  // blocking for.
-  bool mInBlockingSet;
-  // True if this stream should be blocked in this phase.
-  bool mBlockInThisPhase;
-
   // This state is only used on the main thread.
   DOMMediaStream* mWrapper;
   // Main-thread views of state
   StreamTime mMainThreadCurrentTime;
   bool mMainThreadFinished;
   bool mFinishedNotificationSent;
   bool mMainThreadDestroyed;
 
@@ -727,17 +685,16 @@ protected:
  * Audio and video can be written on any thread, but you probably want to
  * always write from the same thread to avoid unexpected interleavings.
  */
 class SourceMediaStream : public MediaStream
 {
 public:
   explicit SourceMediaStream(DOMMediaStream* aWrapper) :
     MediaStream(aWrapper),
-    mLastConsumptionState(MediaStreamListener::NOT_CONSUMED),
     mMutex("mozilla::media::SourceMediaStream"),
     mUpdateKnownTracksTime(0),
     mPullEnabled(false),
     mUpdateFinished(false),
     mNeedsMixing(false)
   {}
 
   virtual SourceMediaStream* AsSourceStream() override { return this; }
@@ -853,27 +810,16 @@ public:
   }
 
   /**
    * End all tracks and Finish() this stream.  Used to voluntarily revoke access
    * to a LocalMediaStream.
    */
   void EndAllTrackAndFinish();
 
-  /**
-   * Note: Only call from Media Graph thread (eg NotifyPull)
-   *
-   * Returns amount of time (data) that is currently buffered in the track,
-   * assuming playout via PlayAudio or via a TrackUnion - note that
-   * NotifyQueuedTrackChanges() on a SourceMediaStream will occur without
-   * any "extra" buffering, but NotifyQueued TrackChanges() on a TrackUnion
-   * will be buffered.
-   */
-  StreamTime GetBufferedTicks(TrackID aID);
-
   void RegisterForAudioMixing();
 
   // XXX need a Reset API
 
   friend class MediaStreamGraphImpl;
 
 protected:
   struct ThreadAndRunnable {
@@ -935,19 +881,16 @@ protected:
    * Notify direct consumers of new data to one of the stream tracks.
    * The data doesn't have to be resampled (though it may be).  This is called
    * from AppendToTrack on the thread providing the data, and will call
    * the Listeners on this thread.
    */
   void NotifyDirectConsumers(TrackData *aTrack,
                              MediaSegment *aSegment);
 
-  // Media stream graph thread only
-  MediaStreamListener::Consumption mLastConsumptionState;
-
   // This must be acquired *before* MediaStreamGraphImpl's lock, if they are
   // held together.
   Mutex mMutex;
   // protected by mMutex
   StreamTime mUpdateKnownTracksTime;
   nsTArray<TrackData> mUpdateTracks;
   nsTArray<TrackData> mPendingTracks;
   nsTArray<nsRefPtr<MediaStreamDirectListener> > mDirectListeners;
@@ -973,51 +916,35 @@ protected:
  * the Destroy message is processed on the graph manager thread we disconnect
  * the port and drop the graph's reference, destroying the object.
  */
 class MediaInputPort final
 {
 private:
   // Do not call this constructor directly. Instead call aDest->AllocateInputPort.
   MediaInputPort(MediaStream* aSource, ProcessedMediaStream* aDest,
-                 uint32_t aFlags, uint16_t aInputNumber,
-                 uint16_t aOutputNumber)
+                 uint16_t aInputNumber, uint16_t aOutputNumber)
     : mSource(aSource)
     , mDest(aDest)
-    , mFlags(aFlags)
     , mInputNumber(aInputNumber)
     , mOutputNumber(aOutputNumber)
     , mGraph(nullptr)
   {
     MOZ_COUNT_CTOR(MediaInputPort);
   }
 
   // Private destructor, to discourage deletion outside of Release():
   ~MediaInputPort()
   {
     MOZ_COUNT_DTOR(MediaInputPort);
   }
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaInputPort)
 
-  /**
-   * The FLAG_BLOCK_INPUT and FLAG_BLOCK_OUTPUT flags can be used to control
-   * exactly how the blocking statuses of the input and output streams affect
-   * each other.
-   */
-  enum {
-    // When set, blocking on the output stream forces blocking on the input
-    // stream.
-    FLAG_BLOCK_INPUT = 0x01,
-    // When set, blocking on the input stream forces blocking on the output
-    // stream.
-    FLAG_BLOCK_OUTPUT = 0x02
-  };
-
   // Called on graph manager thread
   // Do not call these from outside MediaStreamGraph.cpp!
   void Init();
   // Called during message processing to trigger removal of this stream.
   void Disconnect();
 
   // Control API
   /**
@@ -1071,17 +998,16 @@ public:
 
 private:
   friend class MediaStreamGraphImpl;
   friend class MediaStream;
   friend class ProcessedMediaStream;
   // Never modified after Init()
   MediaStream* mSource;
   ProcessedMediaStream* mDest;
-  uint32_t mFlags;
   // The input and output numbers are optional, and are currently only used by
   // Web Audio.
   const uint16_t mInputNumber;
   const uint16_t mOutputNumber;
 
   // Our media stream graph
   MediaStreamGraphImpl* mGraph;
 };
@@ -1099,17 +1025,16 @@ public:
   {}
 
   // Control API.
   /**
    * Allocates a new input port attached to source aStream.
    * This stream can be removed by calling MediaInputPort::Remove().
    */
   already_AddRefed<MediaInputPort> AllocateInputPort(MediaStream* aStream,
-                                                     uint32_t aFlags = 0,
                                                      uint16_t aInputNumber = 0,
                                                      uint16_t aOutputNumber = 0);
   /**
    * Force this stream into the finished state.
    */
   void Finish();
   /**
    * Set the autofinish flag on this stream (defaults to false). When this flag
@@ -1248,35 +1173,41 @@ public:
    * particular tracks of each input stream.
    */
   ProcessedMediaStream* CreateTrackUnionStream(DOMMediaStream* aWrapper);
   /**
    * Create a stream that will mix all its audio input.
    */
   ProcessedMediaStream* CreateAudioCaptureStream(DOMMediaStream* aWrapper);
 
+  enum {
+    ADD_STREAM_SUSPENDED = 0x01
+  };
   /**
    * Add a new stream to the graph.  Main thread.
    */
-  void AddStream(MediaStream* aStream);
+  void AddStream(MediaStream* aStream, uint32_t aFlags = 0);
 
   /* From the main thread, ask the MSG to send back an event when the graph
    * thread is running, and audio is being processed. */
   void NotifyWhenGraphStarted(AudioNodeStream* aNodeStream);
   /* From the main thread, suspend, resume or close an AudioContext.
-   * aNodeStream is the stream of the DestinationNode of the AudioContext.
+   * aStreams are the streams of all the AudioNodes of the AudioContext that
+   * need to be suspended or resumed. This can be empty if this is a second
+   * consecutive suspend call and all the nodes are already suspended.
    *
    * This can possibly pause the graph thread, releasing system resources, if
    * all streams have been suspended/closed.
    *
    * When the operation is complete, aPromise is resolved.
    */
-  void ApplyAudioContextOperation(AudioNodeStream* aNodeStream,
+  void ApplyAudioContextOperation(MediaStream* aDestinationStream,
+                                  const nsTArray<MediaStream*>& aStreams,
                                   dom::AudioContextOperation aState,
-                                  void * aPromise);
+                                  void* aPromise);
 
   bool IsNonRealtime() const;
   /**
    * Start processing non-realtime for a specific number of ticks.
    */
   void StartNonRealtimeProcessing(uint32_t aTicksToProcess);
 
   /**
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -162,22 +162,27 @@ public:
       if (!(mDetectedNotRunning &&
             mLifecycleState > LIFECYCLE_RUNNING &&
             NS_IsMainThread())) {
         mMonitor.AssertCurrentThreadOwns();
       }
     }
 #endif
   }
-  /*
-   * This does the actual iteration: Message processing, MediaStream ordering,
-   * blocking computation and processing.
+
+  void MaybeProduceMemoryReport();
+
+  /**
+   * Returns true if this MediaStreamGraph should keep running
    */
-  void DoIteration();
+  bool UpdateMainThreadState();
 
+  /**
+   * Returns true if this MediaStreamGraph should keep running
+   */
   bool OneIteration(GraphTime aStateEnd);
 
   bool Running() const
   {
     mMonitor.AssertCurrentThreadOwns();
     return mLifecycleState == LIFECYCLE_RUNNING;
   }
 
@@ -211,199 +216,133 @@ public:
   /**
    * If we are rendering in non-realtime mode, we don't want to send messages to
    * the main thread at each iteration for performance reasons. We instead
    * notify the main thread at the same rate
    */
   bool ShouldUpdateMainThread();
   // The following methods are the various stages of RunThread processing.
   /**
-   * Advance all stream state to the new current time.
+   * Advance all stream state to mStateComputedTime.
    */
-  void UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime,
-                                   GraphTime aNextCurrentTime);
+  void UpdateCurrentTimeForStreams(GraphTime aPrevCurrentTime);
   /**
    * Process graph message for this iteration, update stream processing order,
    * and recompute stream blocking until aEndBlockingDecisions.
    */
   void UpdateGraph(GraphTime aEndBlockingDecisions);
 
   void SwapMessageQueues()
   {
     mMonitor.AssertCurrentThreadOwns();
     mFrontMessageQueue.SwapElements(mBackMessageQueue);
   }
   /**
-   * Do all the processing and play the audio and video, ffrom aFrom to aTo.
+   * Do all the processing and play the audio and video, from
+   * mProcessedTime to mStateComputedTime.
    */
-  void Process(GraphTime aFrom, GraphTime aTo);
+  void Process();
   /**
    * Update the consumption state of aStream to reflect whether its data
    * is needed or not.
    */
   void UpdateConsumptionState(SourceMediaStream* aStream);
   /**
    * Extract any state updates pending in aStream, and apply them.
    */
   void ExtractPendingInput(SourceMediaStream* aStream,
                            GraphTime aDesiredUpToTime,
                            bool* aEnsureNextIteration);
   /**
    * Update "have enough data" flags in aStream.
    */
   void UpdateBufferSufficiencyState(SourceMediaStream* aStream);
-  /**
-   * Mark aStream and all its inputs (recursively) as consumed.
-   */
-  static void MarkConsumed(MediaStream* aStream);
-
-  /**
-   * Given the Id of an AudioContext, return the set of all MediaStreams that
-   * are part of this context.
-   */
-  void StreamSetForAudioContext(dom::AudioContext::AudioContextId aAudioContextId,
-                                mozilla::LinkedList<MediaStream>& aStreamSet);
 
   /**
    * Called when a suspend/resume/close operation has been completed, on the
    * graph thread.
    */
   void AudioContextOperationCompleted(MediaStream* aStream,
                                       void* aPromise,
                                       dom::AudioContextOperation aOperation);
 
   /**
    * Apply and AudioContext operation (suspend/resume/closed), on the graph
    * thread.
    */
-  void ApplyAudioContextOperationImpl(AudioNodeStream* aStream,
+  void ApplyAudioContextOperationImpl(MediaStream* aDestinationStream,
+                                      const nsTArray<MediaStream*>& aStreams,
                                       dom::AudioContextOperation aOperation,
                                       void* aPromise);
 
+  /**
+   * Increment suspend count on aStream and move it to mSuspendedStreams if
+   * necessary.
+   */
+  void IncrementSuspendCount(MediaStream* aStream);
+  /**
+   * Increment suspend count on aStream and move it to mStreams if
+   * necessary.
+   */
+  void DecrementSuspendCount(MediaStream* aStream);
+
   /*
    * Move streams from the mStreams to mSuspendedStream if suspending/closing an
    * AudioContext, or the inverse when resuming an AudioContext.
    */
-  void MoveStreams(dom::AudioContextOperation aAudioContextOperation,
-                   mozilla::LinkedList<MediaStream>& aStreamSet);
-
-  /*
-   * Reset some state about the streams before suspending them, or resuming
-   * them.
-   */
-  void ResetVisitedStreamState();
-
-  /*
-   * True if a stream is suspended, that is, is not in mStreams, but in
-   * mSuspendedStream.
-   */
-  bool StreamSuspended(MediaStream* aStream);
+  void SuspendOrResumeStreams(dom::AudioContextOperation aAudioContextOperation,
+                              const nsTArray<MediaStream*>& aStreamSet);
 
   /**
    * Sort mStreams so that every stream not in a cycle is after any streams
    * it depends on, and every stream in a cycle is marked as being in a cycle.
    * Also sets mIsConsumed on every stream.
    */
   void UpdateStreamOrder();
-  /**
-   * Compute the blocking states of streams from mStateComputedTime
-   * until the desired future time aEndBlockingDecisions.
-   * Updates mStateComputedTime and sets MediaStream::mBlocked
-   * for all streams.
-   */
-  void RecomputeBlocking(GraphTime aEndBlockingDecisions);
 
-  // The following methods are used to help RecomputeBlocking.
-  /**
-   * If aStream isn't already in aStreams, add it and recursively call
-   * AddBlockingRelatedStreamsToSet on all the streams whose blocking
-   * status could depend on or affect the state of aStream.
-   */
-  void AddBlockingRelatedStreamsToSet(nsTArray<MediaStream*>* aStreams,
-                                      MediaStream* aStream);
-  /**
-   * Mark a stream blocked at time aTime. If this results in decisions that need
-   * to be revisited at some point in the future, *aEnd will be reduced to the
-   * first time in the future to recompute those decisions.
-   */
-  void MarkStreamBlocking(MediaStream* aStream);
-  /**
-   * Recompute blocking for the streams in aStreams for the interval starting at aTime.
-   * If this results in decisions that need to be revisited at some point
-   * in the future, *aEnd will be reduced to the first time in the future to
-   * recompute those decisions.
-   */
-  void RecomputeBlockingAt(const nsTArray<MediaStream*>& aStreams,
-                           GraphTime aTime, GraphTime aEndBlockingDecisions,
-                           GraphTime* aEnd);
   /**
    * Returns smallest value of t such that t is a multiple of
    * WEBAUDIO_BLOCK_SIZE and t > aTime.
    */
   GraphTime RoundUpToNextAudioBlock(GraphTime aTime);
   /**
-   * Produce data for all streams >= aStreamIndex for the given time interval.
+   * Produce data for all streams >= aStreamIndex for the current time interval.
    * Advances block by block, each iteration producing data for all streams
    * for a single block.
    * This is called whenever we have an AudioNodeStream in the graph.
    */
   void ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
-                                         TrackRate aSampleRate,
-                                         GraphTime aFrom,
-                                         GraphTime aTo);
+                                         TrackRate aSampleRate);
   /**
-   * Returns true if aStream will underrun at aTime for its own playback.
-   * aEndBlockingDecisions is when we plan to stop making blocking decisions.
-   * *aEnd will be reduced to the first time in the future to recompute these
-   * decisions.
+   * If aStream will underrun between aTime, and aEndBlockingDecisions, returns
+   * the time at which the underrun will start. Otherwise return
+   * aEndBlockingDecisions.
    */
-  bool WillUnderrun(MediaStream* aStream, GraphTime aTime,
-                    GraphTime aEndBlockingDecisions, GraphTime* aEnd);
+  GraphTime WillUnderrun(MediaStream* aStream, GraphTime aEndBlockingDecisions);
 
   /**
    * Given a graph time aTime, convert it to a stream time taking into
    * account the time during which aStream is scheduled to be blocked.
    */
-  StreamTime GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime);
-  /**
-   * Given a graph time aTime, convert it to a stream time taking into
-   * account the time during which aStream is scheduled to be blocked, and
-   * when we don't know whether it's blocked or not, we assume it's not blocked.
-   */
-  StreamTime GraphTimeToStreamTimeOptimistic(MediaStream* aStream, GraphTime aTime);
-  enum
-  {
-    INCLUDE_TRAILING_BLOCKED_INTERVAL = 0x01
-  };
+  StreamTime GraphTimeToStreamTimeWithBlocking(MediaStream* aStream, GraphTime aTime);
 
   /**
-   * Given a stream time aTime, convert it to a graph time taking into
-   * account the time during which aStream is scheduled to be blocked.
-   * aTime must be <= mStateComputedTime since blocking decisions
-   * are only known up to that point.
-   * If aTime is exactly at the start of a blocked interval, then the blocked
-   * interval is included in the time returned if and only if
-   * aFlags includes INCLUDE_TRAILING_BLOCKED_INTERVAL.
-   */
-  GraphTime StreamTimeToGraphTime(MediaStream* aStream, StreamTime aTime,
-                                  uint32_t aFlags = 0);
-  /**
    * Call NotifyHaveCurrentData on aStream's listeners.
    */
   void NotifyHasCurrentData(MediaStream* aStream);
   /**
    * If aStream needs an audio stream but doesn't have one, create it.
    * If aStream doesn't need an audio stream but has one, destroy it.
    */
-  void CreateOrDestroyAudioStreams(GraphTime aAudioOutputStartTime, MediaStream* aStream);
+  void CreateOrDestroyAudioStreams(MediaStream* aStream);
   /**
    * Queue audio (mix of stream audio and silence for blocked intervals)
    * to the audio output stream. Returns the number of frames played.
    */
-  StreamTime PlayAudio(MediaStream* aStream, GraphTime aFrom, GraphTime aTo);
+  StreamTime PlayAudio(MediaStream* aStream);
   /**
    * Set the correct current video frame for stream aStream.
    */
   void PlayVideo(MediaStream* aStream);
   /**
    * No more data will be forthcoming for aStream. The stream will end
    * at the current buffer end point. The StreamBuffer's tracks must be
    * explicitly set to finished by the caller.
@@ -601,16 +540,20 @@ public:
   /**
    * This stores MediaStreams that are part of suspended AudioContexts.
    * mStreams and mSuspendStream are disjoint sets: a stream is either suspended
    * or not suspended. Suspended streams are not ordered in UpdateStreamOrder,
    * and are therefore not doing any processing.
    */
   nsTArray<MediaStream*> mSuspendedStreams;
   /**
+   * Suspended AudioContext IDs
+   */
+  nsTHashtable<nsUint64HashKey> mSuspendedContexts;
+  /**
    * Streams from mFirstCycleBreaker to the end of mStreams produce output
    * before they receive input.  They correspond to DelayNodes that are in
    * cycles.
    */
   uint32_t mFirstCycleBreaker;
   /**
    * Blocking decisions have been computed up to this time.
    * Between each iteration, this is the same as mProcessedTime.
@@ -725,23 +668,16 @@ public:
    */
   bool mForceShutDown;
   /**
    * True when we have posted an event to the main thread to run
    * RunInStableState() and the event hasn't run yet.
    */
   bool mPostedRunInStableStateEvent;
 
-  /**
-   * Used to flush any accumulated data when the output streams
-   * may have stalled (on Mac after an output device change)
-   */
-  bool mFlushSourcesNow;
-  bool mFlushSourcesOnNextIteration;
-
   // Main thread only
 
   /**
    * Messages posted by the current event loop task. These are forwarded to
    * the media graph thread during RunInStableState. We can't forward them
    * immediately because we want all messages between stable states to be
    * processed as an atomic batch.
    */
@@ -781,20 +717,16 @@ public:
   nsRefPtr<AudioOutputObserver> mFarendObserverRef;
 #endif
 
   uint32_t AudioChannel() const { return mAudioChannel; }
 
 private:
   virtual ~MediaStreamGraphImpl();
 
-  void StreamNotifyOutput(MediaStream* aStream);
-
-  void StreamReadyToFinish(MediaStream* aStream);
-
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
   /**
    * Used to signal that a memory report has been requested.
    */
   Monitor mMemoryReportMonitor;
   /**
    * This class uses manual memory management, and all pointers to it are raw
--- a/dom/media/MediaTimer.cpp
+++ b/dom/media/MediaTimer.cpp
@@ -169,14 +169,16 @@ MediaTimer::ArmTimer(const TimeStamp& aT
   MOZ_DIAGNOSTIC_ASSERT(!TimerIsArmed());
   MOZ_DIAGNOSTIC_ASSERT(aTarget > aNow);
 
   // XPCOM timer resolution is in milliseconds. It's important to never resolve
   // a timer when mTarget might compare < now (even if very close), so round up.
   unsigned long delay = std::ceil((aTarget - aNow).ToMilliseconds());
   TIMER_LOG("MediaTimer::ArmTimer delay=%lu", delay);
   mCurrentTimerTarget = aTarget;
-  nsresult rv = mTimer->InitWithFuncCallback(&TimerCallback, this, delay, nsITimer::TYPE_ONE_SHOT);
+  nsresult rv = mTimer->InitWithNamedFuncCallback(&TimerCallback, this, delay,
+                                                  nsITimer::TYPE_ONE_SHOT,
+                                                  "MediaTimer::TimerCallback");
   MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
   (void) rv;
 }
 
 } // namespace mozilla
--- a/dom/media/StreamBuffer.h
+++ b/dom/media/StreamBuffer.h
@@ -56,17 +56,16 @@ public:
   /**
    * Every track has a start time --- when it started in the StreamBuffer.
    * It has an end flag; when false, no end point is known; when true,
    * the track ends when the data we have for the track runs out.
    * Tracks have a unique ID assigned at creation. This allows us to identify
    * the same track across StreamBuffers. A StreamBuffer should never have
    * two tracks with the same ID (even if they don't overlap in time).
    * TODO Tracks can also be enabled and disabled over time.
-   * TODO Add TimeVarying<StreamTime,bool> mEnabled.
    * Takes ownership of aSegment.
    */
   class Track final
   {
     Track(TrackID aID, StreamTime aStart, MediaSegment* aSegment)
       : mStart(aStart),
         mSegment(aSegment),
         mID(aID),
deleted file mode 100644
--- a/dom/media/TimeVarying.h
+++ /dev/null
@@ -1,237 +0,0 @@
-/* -*- 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_TIMEVARYING_H_
-#define MOZILLA_TIMEVARYING_H_
-
-#include "nsTArray.h"
-
-namespace mozilla {
-
-/**
- * This is just for CTOR/DTOR tracking. We can't put this in
- * TimeVarying directly because the different template instances have
- * different sizes and that confuses things.
- */
-class TimeVaryingBase {
-protected:
-  TimeVaryingBase()
-  {
-    MOZ_COUNT_CTOR(TimeVaryingBase);
-  }
-  ~TimeVaryingBase()
-  {
-    MOZ_COUNT_DTOR(TimeVaryingBase);
-  }
-};
-
-/**
- * Objects of this class represent values that can change over time ---
- * a mathematical function of time.
- * Time is the type of time values, T is the value that changes over time.
- * There are a finite set of "change times"; at each change time, the function
- * instantly changes to a new value. ReservedChanges should be set to the
- * expected number of change events that the object is likely to contain.
- * This value should be 0 for all consumers unless you know that a higher value
- * would be a benefit.
- * There is also a "current time" which must always advance (not go backward).
- * The function is constant for all times less than the current time.
- * When the current time is advanced, the value of the function at the new
- * current time replaces the values for all previous times.
- *
- * The implementation records a mCurrent (the value at the current time)
- * and an array of "change times" (greater than the current time) and the
- * new value for each change time. This is a simple but dumb implementation.
- * We maintain the invariant that each change entry in the array must have
- * a different value to the value in the previous change entry (or, for
- * the first change entry, mCurrent).
- */
-template <typename Time, typename T, uint32_t ReservedChanges>
-class TimeVarying : public TimeVaryingBase {
-public:
-  explicit TimeVarying(const T& aInitial) : mCurrent(aInitial) {}
-  /**
-   * This constructor can only be called if mCurrent has a no-argument
-   * constructor.
-   */
-  TimeVarying() : mCurrent() {}
-
-  /**
-   * Sets the value for all times >= aTime to aValue.
-   */
-  void SetAtAndAfter(Time aTime, const T& aValue)
-  {
-    for (int32_t i = mChanges.Length() - 1; i >= 0; --i) {
-      NS_ASSERTION(i == int32_t(mChanges.Length() - 1),
-                   "Always considering last element of array");
-      if (aTime > mChanges[i].mTime) {
-        if (mChanges[i].mValue != aValue) {
-          mChanges.AppendElement(Entry(aTime, aValue));
-        }
-        return;
-      }
-      if (aTime == mChanges[i].mTime) {
-        if ((i > 0 ? mChanges[i - 1].mValue : mCurrent) == aValue) {
-          mChanges.RemoveElementAt(i);
-          return;
-        }
-        mChanges[i].mValue = aValue;
-        return;
-      }
-      mChanges.RemoveElementAt(i);
-    }
-    if (mCurrent == aValue) {
-      return;
-    }
-    mChanges.InsertElementAt(0, Entry(aTime, aValue));
-  }
-  /**
-   * Returns the final value of the function. If aTime is non-null,
-   * sets aTime to the time at which the function changes to that final value.
-   * If there are no changes after the current time, returns INT64_MIN in aTime.
-   */
-  const T& GetLast(Time* aTime = nullptr) const
-  {
-    if (mChanges.IsEmpty()) {
-      if (aTime) {
-        *aTime = INT64_MIN;
-      }
-      return mCurrent;
-    }
-    if (aTime) {
-      *aTime = mChanges[mChanges.Length() - 1].mTime;
-    }
-    return mChanges[mChanges.Length() - 1].mValue;
-  }
-  /**
-   * Returns the value of the function just before time aTime.
-   */
-  const T& GetBefore(Time aTime) const
-  {
-    if (mChanges.IsEmpty() || aTime <= mChanges[0].mTime) {
-      return mCurrent;
-    }
-    int32_t changesLength = mChanges.Length();
-    if (mChanges[changesLength - 1].mTime < aTime) {
-      return mChanges[changesLength - 1].mValue;
-    }
-    for (uint32_t i = 1; ; ++i) {
-      if (aTime <= mChanges[i].mTime) {
-        NS_ASSERTION(mChanges[i].mValue != mChanges[i - 1].mValue,
-                     "Only changed values appear in array");
-        return mChanges[i - 1].mValue;
-      }
-    }
-  }
-  /**
-   * Returns the value of the function at time aTime.
-   * If aEnd is non-null, sets *aEnd to the time at which the function will
-   * change from the returned value to a new value, or INT64_MAX if that
-   * never happens.
-   * If aStart is non-null, sets *aStart to the time at which the function
-   * changed to the returned value, or INT64_MIN if that happened at or
-   * before the current time.
-   *
-   * Currently uses a linear search, but could use a binary search.
-   */
-  const T& GetAt(Time aTime, Time* aEnd = nullptr, Time* aStart = nullptr) const
-  {
-    if (mChanges.IsEmpty() || aTime < mChanges[0].mTime) {
-      if (aStart) {
-        *aStart = INT64_MIN;
-      }
-      if (aEnd) {
-        *aEnd = mChanges.IsEmpty() ? INT64_MAX : mChanges[0].mTime;
-      }
-      return mCurrent;
-    }
-    int32_t changesLength = mChanges.Length();
-    if (mChanges[changesLength - 1].mTime <= aTime) {
-      if (aEnd) {
-        *aEnd = INT64_MAX;
-      }
-      if (aStart) {
-        *aStart = mChanges[changesLength - 1].mTime;
-      }
-      return mChanges[changesLength - 1].mValue;
-    }
-
-    for (uint32_t i = 1; ; ++i) {
-      if (aTime < mChanges[i].mTime) {
-        if (aEnd) {
-          *aEnd = mChanges[i].mTime;
-        }
-        if (aStart) {
-          *aStart = mChanges[i - 1].mTime;
-        }
-        NS_ASSERTION(mChanges[i].mValue != mChanges[i - 1].mValue,
-                     "Only changed values appear in array");
-        return mChanges[i - 1].mValue;
-      }
-    }
-  }
-  /**
-   * Advance the current time to aTime.
-   */
-  void AdvanceCurrentTime(Time aTime)
-  {
-    for (uint32_t i = 0; i < mChanges.Length(); ++i) {
-      if (aTime < mChanges[i].mTime) {
-        mChanges.RemoveElementsAt(0, i);
-        return;
-      }
-      mCurrent = mChanges[i].mValue;
-    }
-    mChanges.Clear();
-  }
-  /**
-   * Make all currently pending changes happen aDelta later than their
-   * current change times.
-   */
-  void InsertTimeAtStart(Time aDelta)
-  {
-    for (uint32_t i = 0; i < mChanges.Length(); ++i) {
-      mChanges[i].mTime += aDelta;
-    }
-  }
-
-  /**
-   * Replace the values of this function at aTimeOffset and later with the
-   * values of aOther taken from zero, so if aOther is V at time T >= 0
-   * then this function will be V at time T + aTimeOffset. aOther's current
-   * time must be >= 0.
-   */
-  void Append(const TimeVarying& aOther, Time aTimeOffset)
-  {
-    NS_ASSERTION(aOther.mChanges.IsEmpty() || aOther.mChanges[0].mTime >= 0,
-                 "Negative time not allowed here");
-    NS_ASSERTION(&aOther != this, "Can't self-append");
-    SetAtAndAfter(aTimeOffset, aOther.mCurrent);
-    for (uint32_t i = 0; i < aOther.mChanges.Length(); ++i) {
-      const Entry& e = aOther.mChanges[i];
-      SetAtAndAfter(aTimeOffset + e.mTime, e.mValue);
-    }
-  }
-
-  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
-  {
-    return mChanges.ShallowSizeOfExcludingThis(aMallocSizeOf);
-  }
-
-private:
-  struct Entry {
-    Entry(Time aTime, const T& aValue) : mTime(aTime), mValue(aValue) {}
-
-    // The time at which the value changes to mValue
-    Time mTime;
-    T mValue;
-  };
-  nsAutoTArray<Entry, ReservedChanges> mChanges;
-  T mCurrent;
-};
-
-} // namespace mozilla
-
-#endif /* MOZILLA_TIMEVARYING_H_ */
--- a/dom/media/TrackUnionStream.cpp
+++ b/dom/media/TrackUnionStream.cpp
@@ -133,17 +133,17 @@ TrackUnionStream::TrackUnionStream(DOMMe
       }
     }
     if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
       // All streams have finished and won't add any more tracks, and
       // all our tracks have actually finished and been removed from our map,
       // so we're finished now.
       FinishOnGraphThread();
     } else {
-      mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
+      mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTimeWithBlocking(aTo));
     }
     if (allHaveCurrentData) {
       // We can make progress if we're not blocked
       mHasCurrentData = true;
     }
   }
 
   // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
@@ -178,17 +178,17 @@ TrackUnionStream::TrackUnionStream(DOMMe
           break;
         }
       }
     }
 
     // Round up the track start time so the track, if anything, starts a
     // little later than the true time. This means we'll have enough
     // samples in our input stream to go just beyond the destination time.
-    StreamTime outputStart = GraphTimeToStreamTime(aFrom);
+    StreamTime outputStart = GraphTimeToStreamTimeWithBlocking(aFrom);
 
     nsAutoPtr<MediaSegment> segment;
     segment = aTrack->GetSegment()->CreateEmptyClone();
     for (uint32_t j = 0; j < mListeners.Length(); ++j) {
       MediaStreamListener* l = mListeners[j];
       l->NotifyQueuedTrackChanges(Graph(), id, outputStart,
                                   MediaStreamListener::TRACK_EVENT_CREATED,
                                   *segment);
@@ -239,17 +239,17 @@ TrackUnionStream::TrackUnionStream(DOMMe
     MediaSegment* segment = map->mSegment;
     MediaStream* source = map->mInputPort->GetSource();
 
     GraphTime next;
     *aOutputTrackFinished = false;
     for (GraphTime t = aFrom; t < aTo; t = next) {
       MediaInputPort::InputInterval interval = map->mInputPort->GetNextInputInterval(t);
       interval.mEnd = std::min(interval.mEnd, aTo);
-      StreamTime inputEnd = source->GraphTimeToStreamTime(interval.mEnd);
+      StreamTime inputEnd = source->GraphTimeToStreamTimeWithBlocking(interval.mEnd);
       StreamTime inputTrackEndPoint = STREAM_TIME_MAX;
 
       if (aInputTrack->IsEnded() &&
           aInputTrack->GetEnd() <= inputEnd) {
         inputTrackEndPoint = aInputTrack->GetEnd();
         *aOutputTrackFinished = true;
       }
 
@@ -264,22 +264,22 @@ TrackUnionStream::TrackUnionStream(DOMMe
       if (interval.mInputIsBlocked) {
         // Maybe the input track ended?
         segment->AppendNullData(ticks);
         STREAM_LOG(LogLevel::Verbose, ("TrackUnionStream %p appending %lld ticks of null data to track %d",
                    this, (long long)ticks, outputTrack->GetID()));
       } else if (InMutedCycle()) {
         segment->AppendNullData(ticks);
       } else {
-        if (GraphImpl()->StreamSuspended(source)) {
+        if (source->IsSuspended()) {
           segment->AppendNullData(aTo - aFrom);
         } else {
-          MOZ_ASSERT(outputTrack->GetEnd() == GraphTimeToStreamTime(interval.mStart),
+          MOZ_ASSERT(outputTrack->GetEnd() == GraphTimeToStreamTimeWithBlocking(interval.mStart),
                      "Samples missing");
-          StreamTime inputStart = source->GraphTimeToStreamTime(interval.mStart);
+          StreamTime inputStart = source->GraphTimeToStreamTimeWithBlocking(interval.mStart);
           segment->AppendSlice(*aInputTrack->GetSegment(),
                                std::min(inputTrackEndPoint, inputStart),
                                std::min(inputTrackEndPoint, inputEnd));
         }
       }
       ApplyTrackDisabling(outputTrack->GetID(), segment);
       for (uint32_t j = 0; j < mListeners.Length(); ++j) {
         MediaStreamListener* l = mListeners[j];
--- a/dom/media/mediasink/DecodedStream.cpp
+++ b/dom/media/mediasink/DecodedStream.cpp
@@ -30,17 +30,18 @@ public:
   {
     mFinishPromise = Move(aPromise);
   }
 
   void NotifyOutput(MediaStreamGraph* aGraph, GraphTime aCurrentTime) override
   {
     MutexAutoLock lock(mMutex);
     if (mStream) {
-      mLastOutputTime = mStream->StreamTimeToMicroseconds(mStream->GraphTimeToStreamTime(aCurrentTime));
+      mLastOutputTime = mStream->StreamTimeToMicroseconds(
+          mStream->GraphTimeToStreamTime(aCurrentTime));
     }
   }
 
   void NotifyEvent(MediaStreamGraph* aGraph, MediaStreamGraphEvent event) override
   {
     if (event == EVENT_FINISHED) {
       nsCOMPtr<nsIRunnable> event =
         NS_NewRunnableMethod(this, &DecodedStreamGraphListener::DoNotifyFinished);
@@ -81,24 +82,31 @@ private:
   nsRefPtr<MediaStream> mStream;
   int64_t mLastOutputTime; // microseconds
   bool mStreamFinishedOnMainThread;
   // Main thread only.
   MozPromiseHolder<GenericPromise> mFinishPromise;
 };
 
 static void
-UpdateStreamBlocking(MediaStream* aStream, bool aBlocking)
+UpdateStreamSuspended(MediaStream* aStream, bool aBlocking)
 {
-  int32_t delta = aBlocking ? 1 : -1;
   if (NS_IsMainThread()) {
-    aStream->ChangeExplicitBlockerCount(delta);
+    if (aBlocking) {
+      aStream->Suspend();
+    } else {
+      aStream->Resume();
+    }
   } else {
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<int32_t>(
-      aStream, &MediaStream::ChangeExplicitBlockerCount, delta);
+    nsCOMPtr<nsIRunnable> r;
+    if (aBlocking) {
+      r = NS_NewRunnableMethod(aStream, &MediaStream::Suspend);
+    } else {
+      r = NS_NewRunnableMethod(aStream, &MediaStream::Resume);
+    }
     AbstractThread::MainThread()->Dispatch(r.forget());
   }
 }
 
 /*
  * All MediaStream-related data is protected by the decoder's monitor.
  * We have at most one DecodedStreamDaata per MediaDecoder. Its stream
  * is used as the input for each ProcessedMediaStream created by calls to
@@ -184,17 +192,17 @@ DecodedStreamData::GetPosition() const
   return mListener->GetLastOutputTime();
 }
 
 void
 DecodedStreamData::SetPlaying(bool aPlaying)
 {
   if (mPlaying != aPlaying) {
     mPlaying = aPlaying;
-    UpdateStreamBlocking(mStream, !mPlaying);
+    UpdateStreamSuspended(mStream, !mPlaying);
   }
 }
 
 OutputStreamData::~OutputStreamData()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Break the connection to the input stream if necessary.
   if (mPort) {
@@ -211,20 +219,17 @@ OutputStreamData::Init(OutputStreamManag
 
 void
 OutputStreamData::Connect(MediaStream* aStream)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mPort, "Already connected?");
   MOZ_ASSERT(!mStream->IsDestroyed(), "Can't connect a destroyed stream.");
 
-  mPort = mStream->AllocateInputPort(aStream, 0);
-  // Unblock the output stream now. The input stream is responsible for
-  // controlling blocking from now on.
-  mStream->ChangeExplicitBlockerCount(-1);
+  mPort = mStream->AllocateInputPort(aStream);
 }
 
 bool
 OutputStreamData::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // During cycle collection, DOMMediaStream can be destroyed and send
@@ -234,19 +239,16 @@ OutputStreamData::Disconnect()
     return false;
   }
 
   // Disconnect the existing port if necessary.
   if (mPort) {
     mPort->Destroy();
     mPort = nullptr;
   }
-  // Block the stream again. It will be unlocked when connecting
-  // to the input stream.
-  mStream->ChangeExplicitBlockerCount(1);
   return true;
 }
 
 MediaStreamGraph*
 OutputStreamData::Graph() const
 {
   return mStream->Graph();
 }
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -139,17 +139,16 @@ EXPORTS += [
     'MP3FrameParser.h',
     'nsIDocumentActivity.h',
     'RtspMediaResource.h',
     'SelfRef.h',
     'SharedBuffer.h',
     'StreamBuffer.h',
     'ThreadPoolCOMListener.h',
     'TimeUnits.h',
-    'TimeVarying.h',
     'TrackUnionStream.h',
     'VideoFrameContainer.h',
     'VideoSegment.h',
     'VideoUtils.h',
     'VorbisUtils.h',
     'XiphExtradata.h',
 ]
 
--- a/dom/media/webaudio/AnalyserNode.cpp
+++ b/dom/media/webaudio/AnalyserNode.cpp
@@ -81,17 +81,17 @@ AnalyserNode::AnalyserNode(AudioContext*
               1,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mAnalysisBlock(2048)
   , mMinDecibels(-100.)
   , mMaxDecibels(-30.)
   , mSmoothingTimeConstant(.8)
 {
-  mStream = AudioNodeStream::Create(aContext->Graph(),
+  mStream = AudioNodeStream::Create(aContext,
                                     new AnalyserNodeEngine(this),
                                     AudioNodeStream::NO_STREAM_FLAGS);
 
   // Enough chunks must be recorded to handle the case of fftSize being
   // increased to maximum immediately before getFloatTimeDomainData() is
   // called, for example.
   unused << mChunks.SetLength(CHUNK_COUNT, fallible);
 
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -570,17 +570,17 @@ AudioBufferSourceNode::AudioBufferSource
   , mLoopEnd(0.0)
   // mOffset and mDuration are initialized in Start().
   , mPlaybackRate(new AudioParam(this, SendPlaybackRateToStream, 1.0f, "playbackRate"))
   , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
   , mLoop(false)
   , mStartCalled(false)
 {
   AudioBufferSourceNodeEngine* engine = new AudioBufferSourceNodeEngine(this, aContext->Destination());
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NEED_MAIN_THREAD_FINISHED);
   engine->SetSourceStream(mStream);
   mStream->AddMainThreadListener(this);
 }
 
 AudioBufferSourceNode::~AudioBufferSourceNode()
 {
 }
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -93,21 +93,21 @@ AudioContext::AudioContext(nsPIDOMWindow
                            uint32_t aNumberOfChannels,
                            uint32_t aLength,
                            float aSampleRate)
   : DOMEventTargetHelper(aWindow)
   , mId(gAudioContextId++)
   , mSampleRate(GetSampleRateForAudioContext(aIsOffline, aSampleRate))
   , mAudioContextState(AudioContextState::Suspended)
   , mNumberOfChannels(aNumberOfChannels)
-  , mNodeCount(0)
   , mIsOffline(aIsOffline)
   , mIsStarted(!aIsOffline)
   , mIsShutDown(false)
   , mCloseCalled(false)
+  , mSuspendCalled(false)
 {
   bool mute = aWindow->AddAudioContext(this);
 
   // Note: AudioDestinationNode needs an AudioContext that must already be
   // bound to the window.
   mDestination = new AudioDestinationNode(this, aIsOffline, aChannel,
                                           aNumberOfChannels, aLength, aSampleRate);
 
@@ -689,21 +689,16 @@ AudioContext::Shutdown()
   mActiveNodes.Clear();
 
   // For offline contexts, we can destroy the MediaStreamGraph at this point.
   if (mIsOffline && mDestination) {
     mDestination->OfflineShutdown();
   }
 }
 
-AudioContextState AudioContext::State() const
-{
-  return mAudioContextState;
-}
-
 StateChangeTask::StateChangeTask(AudioContext* aAudioContext,
                                  void* aPromise,
                                  AudioContextState aNewState)
   : mAudioContext(aAudioContext)
   , mPromise(aPromise)
   , mAudioNodeStream(nullptr)
   , mNewState(aNewState)
 {
@@ -825,16 +820,29 @@ AudioContext::OnStateChanged(void* aProm
     nsRefPtr<OnStateChangeTask> onStateChangeTask =
       new OnStateChangeTask(this);
     NS_DispatchToMainThread(onStateChangeTask);
   }
 
   mAudioContextState = aNewState;
 }
 
+nsTArray<MediaStream*>
+AudioContext::GetAllStreams() const
+{
+  nsTArray<MediaStream*> streams;
+  for (auto iter = mAllNodes.ConstIter(); !iter.Done(); iter.Next()) {
+    MediaStream* s = iter.Get()->GetKey()->GetStream();
+    if (s) {
+      streams.AppendElement(s);
+    }
+  }
+  return streams;
+}
+
 already_AddRefed<Promise>
 AudioContext::Suspend(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   nsRefPtr<Promise> promise;
   promise = Promise::Create(parentObject, aRv);
   if (aRv.Failed()) {
     return nullptr;
@@ -853,19 +861,31 @@ AudioContext::Suspend(ErrorResult& aRv)
   if (mAudioContextState == AudioContextState::Suspended) {
     promise->MaybeResolve(JS::UndefinedHandleValue);
     return promise.forget();
   }
 
   Destination()->Suspend();
 
   mPromiseGripArray.AppendElement(promise);
+
+  nsTArray<MediaStream*> streams;
+  // If mSuspendCalled is true then we already suspended all our streams,
+  // so don't suspend them again (since suspend(); suspend(); resume(); should
+  // cancel both suspends). But we still need to do ApplyAudioContextOperation
+  // to ensure our new promise is resolved.
+  if (!mSuspendCalled) {
+    streams = GetAllStreams();
+  }
   Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
+                                      streams,
                                       AudioContextOperation::Suspend, promise);
 
+  mSuspendCalled = true;
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 AudioContext::Resume(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   nsRefPtr<Promise> promise;
@@ -887,20 +907,31 @@ AudioContext::Resume(ErrorResult& aRv)
 
   if (mAudioContextState == AudioContextState::Running) {
     promise->MaybeResolve(JS::UndefinedHandleValue);
     return promise.forget();
   }
 
   Destination()->Resume();
 
+  nsTArray<MediaStream*> streams;
+  // If mSuspendCalled is false then we already resumed all our streams,
+  // so don't resume them again (since suspend(); resume(); resume(); should
+  // be OK). But we still need to do ApplyAudioContextOperation
+  // to ensure our new promise is resolved.
+  if (mSuspendCalled) {
+    streams = GetAllStreams();
+  }
   mPromiseGripArray.AppendElement(promise);
   Graph()->ApplyAudioContextOperation(DestinationStream()->AsAudioNodeStream(),
+                                      streams,
                                       AudioContextOperation::Resume, promise);
 
+  mSuspendCalled = false;
+
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 AudioContext::Close(ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   nsRefPtr<Promise> promise;
@@ -914,46 +945,63 @@ AudioContext::Close(ErrorResult& aRv)
     return promise.forget();
   }
 
   if (mAudioContextState == AudioContextState::Closed) {
     promise->MaybeResolve(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
-  mCloseCalled = true;
-
   if (Destination()) {
     Destination()->DestroyAudioChannelAgent();
   }
 
   mPromiseGripArray.AppendElement(promise);
 
   // This can be called when freeing a document, and the streams are dead at
   // this point, so we need extra null-checks.
   MediaStream* ds = DestinationStream();
   if (ds) {
-    Graph()->ApplyAudioContextOperation(ds->AsAudioNodeStream(),
+    nsTArray<MediaStream*> streams;
+    // If mSuspendCalled or mCloseCalled are true then we already suspended
+    // all our streams, so don't suspend them again. But we still need to do
+    // ApplyAudioContextOperation to ensure our new promise is resolved.
+    if (!mSuspendCalled && !mCloseCalled) {
+      streams = GetAllStreams();
+    }
+    Graph()->ApplyAudioContextOperation(ds->AsAudioNodeStream(), streams,
                                         AudioContextOperation::Close, promise);
   }
+  mCloseCalled = true;
+
   return promise.forget();
 }
 
 void
-AudioContext::UpdateNodeCount(int32_t aDelta)
+AudioContext::RegisterNode(AudioNode* aNode)
 {
-  bool firstNode = mNodeCount == 0;
-  mNodeCount += aDelta;
-  MOZ_ASSERT(mNodeCount >= 0);
+  MOZ_ASSERT(!mAllNodes.Contains(aNode));
+  mAllNodes.PutEntry(aNode);
   // mDestinationNode may be null when we're destroying nodes unlinked by CC.
   // Skipping unnecessary calls after shutdown avoids RunInStableState events
   // getting stuck in CycleCollectedJSRuntime during final cycle collection
   // (bug 1200514).
-  if (!firstNode && mDestination && !mIsShutDown) {
-    mDestination->SetIsOnlyNodeForContext(mNodeCount == 1);
+  if (mDestination && !mIsShutDown) {
+    mDestination->SetIsOnlyNodeForContext(mAllNodes.Count() == 1);
+  }
+}
+
+void
+AudioContext::UnregisterNode(AudioNode* aNode)
+{
+  MOZ_ASSERT(mAllNodes.Contains(aNode));
+  mAllNodes.RemoveEntry(aNode);
+  // mDestinationNode may be null when we're destroying nodes unlinked by CC
+  if (mDestination) {
+    mDestination->SetIsOnlyNodeForContext(mAllNodes.Count() == 1);
   }
 }
 
 JSObject*
 AudioContext::GetGlobalJSObject() const
 {
   nsCOMPtr<nsIGlobalObject> parentObject = do_QueryInterface(GetParentObject());
   if (!parentObject) {
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -170,26 +170,24 @@ public:
     return mDestination;
   }
 
   float SampleRate() const
   {
     return mSampleRate;
   }
 
-  AudioContextId Id() const
-  {
-    return mId;
-  }
+  bool ShouldSuspendNewStream() const { return mSuspendCalled; }
 
   double CurrentTime() const;
 
   AudioListener* Listener();
 
-  AudioContextState State() const;
+  AudioContextState State() const { return mAudioContextState; }
+
   // Those three methods return a promise to content, that is resolved when an
   // (possibly long) operation is completed on the MSG (and possibly other)
   // thread(s). To avoid having to match the calls and asychronous result when
   // the operation is completed, we keep a reference to the promises on the main
   // thread, and then send the promises pointers down the MSG thread, as a void*
   // (to make it very clear that the pointer is to merely be treated as an ID).
   // When back on the main thread, we can resolve or reject the promise, by
   // casting it back to a `Promise*` while asserting we're back on the main
@@ -298,17 +296,18 @@ public:
   void Unmute() const;
 
   JSObject* GetGlobalJSObject() const;
 
   AudioChannel MozAudioChannelType() const;
 
   AudioChannel TestAudioChannelInAudioNodeStream();
 
-  void UpdateNodeCount(int32_t aDelta);
+  void RegisterNode(AudioNode* aNode);
+  void UnregisterNode(AudioNode* aNode);
 
   double DOMTimeToStreamTime(double aTime) const
   {
     return aTime - ExtraCurrentTime();
   }
 
   double StreamTimeToDOMTime(double aTime) const
   {
@@ -338,16 +337,18 @@ private:
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
                             nsISupports* aData, bool aAnonymize) override;
 
   friend struct ::mozilla::WebAudioDecodeJob;
 
   bool CheckClosed(ErrorResult& aRv);
 
+  nsTArray<MediaStream*> GetAllStreams() const;
+
 private:
   // Each AudioContext has an id, that is passed down the MediaStreams that
   // back the AudioNodes, so we can easily compute the set of all the
   // MediaStreams for a given context, on the MediasStreamGraph side.
   const AudioContextId mId;
   // Note that it's important for mSampleRate to be initialized before
   // mDestination, as mDestination's constructor needs to access it!
   const float mSampleRate;
@@ -356,30 +357,32 @@ private:
   nsRefPtr<AudioListener> mListener;
   nsTArray<nsRefPtr<WebAudioDecodeJob> > mDecodeJobs;
   // This array is used to keep the suspend/resume/close promises alive until
   // they are resolved, so we can safely pass them accross threads.
   nsTArray<nsRefPtr<Promise>> mPromiseGripArray;
   // See RegisterActiveNode.  These will keep the AudioContext alive while it
   // is rendering and the window remains alive.
   nsTHashtable<nsRefPtrHashKey<AudioNode> > mActiveNodes;
+  // Raw (non-owning) references to all AudioNodes for this AudioContext.
+  nsTHashtable<nsPtrHashKey<AudioNode> > mAllNodes;
   // Hashsets containing all the PannerNodes, to compute the doppler shift.
   // These are weak pointers.
   nsTHashtable<nsPtrHashKey<PannerNode> > mPannerNodes;
   // Cache to avoid recomputing basic waveforms all the time.
   nsRefPtr<BasicWaveFormCache> mBasicWaveFormCache;
   // Number of channels passed in the OfflineAudioContext ctor.
   uint32_t mNumberOfChannels;
-  // Number of nodes that currently exist for this AudioContext
-  int32_t mNodeCount;
   bool mIsOffline;
   bool mIsStarted;
   bool mIsShutDown;
   // Close has been called, reject suspend and resume call.
   bool mCloseCalled;
+  // Suspend has been called with no following resume.
+  bool mSuspendCalled;
 };
 
 static const dom::AudioContext::AudioContextId NO_AUDIO_CONTEXT = 0;
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -327,17 +327,17 @@ AudioDestinationNode::AudioDestinationNo
                             new OfflineDestinationNodeEngine(this, aNumberOfChannels,
                                                              aLength, aSampleRate) :
                             static_cast<AudioNodeEngine*>(new DestinationNodeEngine(this));
 
   AudioNodeStream::Flags flags =
     AudioNodeStream::NEED_MAIN_THREAD_CURRENT_TIME |
     AudioNodeStream::NEED_MAIN_THREAD_FINISHED |
     AudioNodeStream::EXTERNAL_OUTPUT;
-  mStream = AudioNodeStream::Create(graph, engine, flags);
+  mStream = AudioNodeStream::Create(aContext, engine, flags, graph);
   mStream->AddMainThreadListener(this);
   mStream->AddAudioOutput(&gWebAudioOutputKey);
 
   if (!aIsOffline) {
     graph->NotifyWhenGraphStarted(mStream);
   }
 
   if (aChannel != AudioChannel::Normal) {
@@ -675,27 +675,27 @@ AudioDestinationNode::SetIsOnlyNodeForCo
     // Don't block the destination stream for offline AudioContexts, since
     // we expect the zero data produced when there are no other nodes to
     // show up in its result buffer. Also, we would get confused by adding
     // ExtraCurrentTime before StartRendering has even been called.
     return;
   }
 
   if (aIsOnlyNode) {
-    mStream->ChangeExplicitBlockerCount(1);
+    mStream->Suspend();
     mStartedBlockingDueToBeingOnlyNode = TimeStamp::Now();
     // Don't do an update of mExtraCurrentTimeSinceLastStartedBlocking until the next stable state.
     mExtraCurrentTimeUpdatedSinceLastStableState = true;
     ScheduleStableStateNotification();
   } else {
     // Force update of mExtraCurrentTimeSinceLastStartedBlocking if necessary
     ExtraCurrentTime();
     mExtraCurrentTime += mExtraCurrentTimeSinceLastStartedBlocking;
     mExtraCurrentTimeSinceLastStartedBlocking = 0;
-    mStream->ChangeExplicitBlockerCount(-1);
+    mStream->Resume();
     mStartedBlockingDueToBeingOnlyNode = TimeStamp();
   }
 }
 
 void
 AudioDestinationNode::InputMuted(bool aMuted)
 {
   MOZ_ASSERT(Context() && !Context()->IsOffline());
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -18,17 +18,17 @@ namespace dom {
 static const uint32_t INVALID_PORT = 0xffffffff;
 static uint32_t gId = 0;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper)
   tmp->DisconnectFromGraph();
   if (tmp->mContext) {
-    tmp->mContext->UpdateNodeCount(-1);
+    tmp->mContext->UnregisterNode(tmp);
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
@@ -67,30 +67,30 @@ AudioNode::AudioNode(AudioContext* aCont
   , mId(gId++)
   , mPassThrough(false)
 #ifdef DEBUG
   , mDemiseNotified(false)
 #endif
 {
   MOZ_ASSERT(aContext);
   DOMEventTargetHelper::BindToOwner(aContext->GetParentObject());
-  aContext->UpdateNodeCount(1);
+  aContext->RegisterNode(this);
 }
 
 AudioNode::~AudioNode()
 {
   MOZ_ASSERT(mInputNodes.IsEmpty());
   MOZ_ASSERT(mOutputNodes.IsEmpty());
   MOZ_ASSERT(mOutputParams.IsEmpty());
 #ifdef DEBUG
   MOZ_ASSERT(mDemiseNotified,
              "The webaudio-node-demise notification must have been sent");
 #endif
   if (mContext) {
-    mContext->UpdateNodeCount(-1);
+    mContext->UnregisterNode(this);
   }
 }
 
 size_t
 AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   // Not owned:
   // - mContext
@@ -220,19 +220,18 @@ AudioNode::Connect(AudioNode& aDestinati
   input->mInputPort = aInput;
   input->mOutputPort = aOutput;
   AudioNodeStream* destinationStream = aDestination.mStream;
   if (mStream && destinationStream) {
     // Connect streams in the MediaStreamGraph
     MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number");
     MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
     input->mStreamPort = destinationStream->
-      AllocateInputPort(mStream, 0,
-                            static_cast<uint16_t>(aInput),
-                            static_cast<uint16_t>(aOutput));
+      AllocateInputPort(mStream, static_cast<uint16_t>(aInput),
+                        static_cast<uint16_t>(aOutput));
   }
   aDestination.NotifyInputsChanged();
 
   // This connection may have connected a panner and a source.
   Context()->UpdatePannerSource();
 }
 
 void
@@ -263,17 +262,17 @@ AudioNode::Connect(AudioParam& aDestinat
 
   MediaStream* stream = aDestination.Stream();
   MOZ_ASSERT(stream->AsProcessedStream());
   ProcessedMediaStream* ps = static_cast<ProcessedMediaStream*>(stream);
   if (mStream) {
     // Setup our stream as an input to the AudioParam's stream
     MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
     input->mStreamPort =
-      ps->AllocateInputPort(mStream, 0, 0, static_cast<uint16_t>(aOutput));
+      ps->AllocateInputPort(mStream, 0, static_cast<uint16_t>(aOutput));
   }
 }
 
 void
 AudioNode::SendDoubleParameterToStream(uint32_t aIndex, double aValue)
 {
   MOZ_ASSERT(mStream, "How come we don't have a stream here?");
   mStream->SetDoubleParameter(aIndex, aValue);
--- a/dom/media/webaudio/AudioNode.h
+++ b/dom/media/webaudio/AudioNode.h
@@ -172,17 +172,17 @@ public:
     // The index of the input port this node feeds into.
     // This is not used for connections to AudioParams.
     uint32_t mInputPort;
     // The index of the output port this node comes out of.
     uint32_t mOutputPort;
   };
 
   // Returns the stream, if any.
-  AudioNodeStream* GetStream() { return mStream; }
+  AudioNodeStream* GetStream() const { return mStream; }
 
   const nsTArray<InputNode>& InputNodes() const
   {
     return mInputNodes;
   }
   const nsTArray<nsRefPtr<AudioNode> >& OutputNodes() const
   {
     return mOutputNodes;
--- a/dom/media/webaudio/AudioNodeExternalInputStream.cpp
+++ b/dom/media/webaudio/AudioNodeExternalInputStream.cpp
@@ -7,38 +7,39 @@
 #include "AudioNodeExternalInputStream.h"
 #include "AudioChannelFormat.h"
 #include "mozilla/dom/MediaStreamAudioSourceNode.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
-AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate, uint32_t aContextId)
-  : AudioNodeStream(aEngine, NO_STREAM_FLAGS, aSampleRate, aContextId)
+AudioNodeExternalInputStream::AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate)
+  : AudioNodeStream(aEngine, NO_STREAM_FLAGS, aSampleRate)
 {
   MOZ_COUNT_CTOR(AudioNodeExternalInputStream);
 }
 
 AudioNodeExternalInputStream::~AudioNodeExternalInputStream()
 {
   MOZ_COUNT_DTOR(AudioNodeExternalInputStream);
 }
 
 /* static */ already_AddRefed<AudioNodeExternalInputStream>
 AudioNodeExternalInputStream::Create(MediaStreamGraph* aGraph,
                                      AudioNodeEngine* aEngine)
 {
+  AudioContext* ctx = aEngine->NodeMainThread()->Context();
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aGraph->GraphRate() == aEngine->NodeMainThread()->Context()->SampleRate());
+  MOZ_ASSERT(aGraph->GraphRate() == ctx->SampleRate());
 
   nsRefPtr<AudioNodeExternalInputStream> stream =
-    new AudioNodeExternalInputStream(aEngine, aGraph->GraphRate(),
-                                     aEngine->NodeMainThread()->Context()->Id());
-  aGraph->AddStream(stream);
+    new AudioNodeExternalInputStream(aEngine, aGraph->GraphRate());
+  aGraph->AddStream(stream,
+    ctx->ShouldSuspendNewStream() ? MediaStreamGraph::ADD_STREAM_SUSPENDED : 0);
   return stream.forget();
 }
 
 /**
  * Copies the data in aInput to aOffsetInBlock within aBlock.
  * aBlock must have been allocated with AllocateInputBlock and have a channel
  * count that's a superset of the channels in aInput.
  */
@@ -148,23 +149,27 @@ AudioNodeExternalInputStream::ProcessInp
     GraphTime next;
     for (GraphTime t = aFrom; t < aTo; t = next) {
       MediaInputPort::InputInterval interval = mInputs[0]->GetNextInputInterval(t);
       interval.mEnd = std::min(interval.mEnd, aTo);
       if (interval.mStart >= interval.mEnd)
         break;
       next = interval.mEnd;
 
+      // We know this stream does not block during the processing interval ---
+      // we're not finished, we don't underrun, and we're not suspended.
       StreamTime outputStart = GraphTimeToStreamTime(interval.mStart);
       StreamTime outputEnd = GraphTimeToStreamTime(interval.mEnd);
       StreamTime ticks = outputEnd - outputStart;
 
       if (interval.mInputIsBlocked) {
         segment.AppendNullData(ticks);
       } else {
+        // The input stream is not blocked in this interval, so no need to call
+        // GraphTimeToStreamTimeWithBlocking.
         StreamTime inputStart =
           std::min(inputSegment.GetDuration(),
                    source->GraphTimeToStreamTime(interval.mStart));
         StreamTime inputEnd =
           std::min(inputSegment.GetDuration(),
                    source->GraphTimeToStreamTime(interval.mEnd));
 
         segment.AppendSlice(inputSegment, inputStart, inputEnd);
--- a/dom/media/webaudio/AudioNodeExternalInputStream.h
+++ b/dom/media/webaudio/AudioNodeExternalInputStream.h
@@ -20,18 +20,17 @@ namespace mozilla {
  */
 class AudioNodeExternalInputStream final : public AudioNodeStream
 {
 public:
   static already_AddRefed<AudioNodeExternalInputStream>
   Create(MediaStreamGraph* aGraph, AudioNodeEngine* aEngine);
 
 protected:
-  AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate,
-                               uint32_t aContextId);
+  AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate);
   ~AudioNodeExternalInputStream();
 
 public:
   virtual void ProcessInput(GraphTime aFrom, GraphTime aTo,
                             uint32_t aFlags) override;
 
 private:
   /**
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -6,38 +6,37 @@
 #include "AudioNodeStream.h"
 
 #include "MediaStreamGraphImpl.h"
 #include "AudioNodeEngine.h"
 #include "ThreeDPoint.h"
 #include "AudioChannelFormat.h"
 #include "AudioParamTimeline.h"
 #include "AudioContext.h"
+#include "nsMathUtils.h"
 
 using namespace mozilla::dom;
 
 namespace mozilla {
 
 /**
  * An AudioNodeStream produces a single audio track with ID
  * AUDIO_TRACK. This track has rate AudioContext::sIdealAudioRate
  * for regular audio contexts, and the rate requested by the web content
  * for offline audio contexts.
  * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
  * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
  */
 
 AudioNodeStream::AudioNodeStream(AudioNodeEngine* aEngine,
                                  Flags aFlags,
-                                 TrackRate aSampleRate,
-                                 AudioContext::AudioContextId aContextId)
+                                 TrackRate aSampleRate)
   : ProcessedMediaStream(nullptr),
     mEngine(aEngine),
     mSampleRate(aSampleRate),
-    mAudioContextId(aContextId),
     mFlags(aFlags),
     mNumberOfInputChannels(2),
     mMarkAsFinishedAfterThisBlock(false),
     mAudioParamStream(false),
     mPassThrough(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mChannelCountMode = ChannelCountMode::Max;
@@ -59,36 +58,35 @@ AudioNodeStream::DestroyImpl()
   // These are graph thread objects, so clean up on graph thread.
   mInputChunks.Clear();
   mLastChunks.Clear();
 
   ProcessedMediaStream::DestroyImpl();
 }
 
 /* static */ already_AddRefed<AudioNodeStream>
-AudioNodeStream::Create(MediaStreamGraph* aGraph, AudioNodeEngine* aEngine,
-                        Flags aFlags)
+AudioNodeStream::Create(AudioContext* aCtx, AudioNodeEngine* aEngine,
+                        Flags aFlags, MediaStreamGraph* aGraph)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // MediaRecorders use an AudioNodeStream, but no AudioNode
   AudioNode* node = aEngine->NodeMainThread();
-  MOZ_ASSERT(!node || aGraph->GraphRate() == node->Context()->SampleRate());
+  MediaStreamGraph* graph = aGraph ? aGraph : aCtx->Graph();
+  MOZ_ASSERT(graph->GraphRate() == aCtx->SampleRate());
 
-  dom::AudioContext::AudioContextId contextIdForStream = node ? node->Context()->Id() :
-                                                                NO_AUDIO_CONTEXT;
   nsRefPtr<AudioNodeStream> stream =
-    new AudioNodeStream(aEngine, aFlags, aGraph->GraphRate(),
-                        contextIdForStream);
-  if (aEngine->HasNode()) {
-    stream->SetChannelMixingParametersImpl(aEngine->NodeMainThread()->ChannelCount(),
-                                           aEngine->NodeMainThread()->ChannelCountModeValue(),
-                                           aEngine->NodeMainThread()->ChannelInterpretationValue());
+    new AudioNodeStream(aEngine, aFlags, graph->GraphRate());
+  if (node) {
+    stream->SetChannelMixingParametersImpl(node->ChannelCount(),
+                                           node->ChannelCountModeValue(),
+                                           node->ChannelInterpretationValue());
   }
-  aGraph->AddStream(stream);
+  graph->AddStream(stream,
+    aCtx->ShouldSuspendNewStream() ? MediaStreamGraph::ADD_STREAM_SUSPENDED : 0);
   return stream.forget();
 }
 
 size_t
 AudioNodeStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = 0;
 
@@ -517,22 +515,17 @@ AudioNodeStream::ProcessInput(GraphTime 
     EnsureTrack(AUDIO_TRACK);
   }
   // No more tracks will be coming
   mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX);
 
   uint16_t outputCount = mLastChunks.Length();
   MOZ_ASSERT(outputCount == std::max(uint16_t(1), mEngine->OutputCount()));
 
-  // Consider this stream blocked if it has already finished output. Normally
-  // mBlocked would reflect this, but due to rounding errors our audio track may
-  // appear to extend slightly beyond aFrom, so we might not be blocked yet.
-  bool blocked = mFinished || mBlocked.GetAt(aFrom);
-  // If the stream has finished at this time, it will be blocked.
-  if (blocked || InMutedCycle()) {
+  if (mFinished || InMutedCycle()) {
     mInputChunks.Clear();
     for (uint16_t i = 0; i < outputCount; ++i) {
       mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
   } else {
     // We need to generate at least one input
     uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount());
     mInputChunks.SetLength(maxInputs);
@@ -560,18 +553,18 @@ AudioNodeStream::ProcessInput(GraphTime 
 
     if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) {
       for (uint32_t i = 0; i < outputCount; ++i) {
         mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
       }
     }
   }
 
-  if (!blocked) {
-    // Don't output anything while blocked
+  if (!mFinished) {
+    // Don't output anything while finished
     AdvanceOutputSegment();
     if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) {
       // This stream was finished the last time that we looked at it, and all
       // of the depending streams have finished their output as well, so now
       // it's time to mark this stream as finished.
       FinishOutput();
     }
   }
@@ -581,22 +574,17 @@ void
 AudioNodeStream::ProduceOutputBeforeInput(GraphTime aFrom)
 {
   MOZ_ASSERT(mEngine->AsDelayNodeEngine());
   MOZ_ASSERT(mEngine->OutputCount() == 1,
              "DelayNodeEngine output count should be 1");
   MOZ_ASSERT(!InMutedCycle(), "DelayNodes should break cycles");
   MOZ_ASSERT(mLastChunks.Length() == 1);
 
-  // Consider this stream blocked if it has already finished output. Normally
-  // mBlocked would reflect this, but due to rounding errors our audio track may
-  // appear to extend slightly beyond aFrom, so we might not be blocked yet.
-  bool blocked = mFinished || mBlocked.GetAt(aFrom);
-  // If the stream has finished at this time, it will be blocked.
-  if (blocked) {
+  if (mFinished) {
     mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
   } else {
     mEngine->ProduceBlockBeforeInput(&mLastChunks[0]);
     NS_ASSERTION(mLastChunks[0].GetDuration() == WEBAUDIO_BLOCK_SIZE,
                  "Invalid WebAudio chunk size");
     if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) {
       mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
     }
@@ -663,39 +651,37 @@ AudioNodeStream::FractionalTicksFromDest
   double destinationFractionalTicks = destinationSeconds * SampleRate();
   MOZ_ASSERT(destinationFractionalTicks < STREAM_TIME_MAX);
   StreamTime destinationStreamTime = destinationFractionalTicks; // round down
   // MediaTime does not have the resolution of double
   double offset = destinationFractionalTicks - destinationStreamTime;
 
   GraphTime graphTime =
     aDestination->StreamTimeToGraphTime(destinationStreamTime);
-  StreamTime thisStreamTime = GraphTimeToStreamTimeOptimistic(graphTime);
+  StreamTime thisStreamTime = GraphTimeToStreamTime(graphTime);
   double thisFractionalTicks = thisStreamTime + offset;
-  MOZ_ASSERT(thisFractionalTicks >= 0.0);
   return thisFractionalTicks;
 }
 
 StreamTime
 AudioNodeStream::TicksFromDestinationTime(MediaStream* aDestination,
                                           double aSeconds)
 {
   AudioNodeStream* destination = aDestination->AsAudioNodeStream();
   MOZ_ASSERT(destination);
 
   double thisSeconds =
     FractionalTicksFromDestinationTime(destination, aSeconds);
-  // Round to nearest
-  StreamTime ticks = thisSeconds + 0.5;
-  return ticks;
+  return NS_round(thisSeconds);
 }
 
 double
 AudioNodeStream::DestinationTimeFromTicks(AudioNodeStream* aDestination,
                                           StreamTime aPosition)
 {
   MOZ_ASSERT(SampleRate() == aDestination->SampleRate());
+
   GraphTime graphTime = StreamTimeToGraphTime(aPosition);
-  StreamTime destinationTime = aDestination->GraphTimeToStreamTimeOptimistic(graphTime);
+  StreamTime destinationTime = aDestination->GraphTimeToStreamTime(graphTime);
   return StreamTimeToSeconds(destinationTime);
 }
 
 } // namespace mozilla
--- a/dom/media/webaudio/AudioNodeStream.h
+++ b/dom/media/webaudio/AudioNodeStream.h
@@ -52,28 +52,31 @@ public:
     // Internal AudioNodeStreams can only pass their output to another
     // AudioNode, whereas external AudioNodeStreams can pass their output
     // to other ProcessedMediaStreams or hardware audio output.
     EXTERNAL_OUTPUT = 1U << 2,
   };
   /**
    * Create a stream that will process audio for an AudioNode.
    * Takes ownership of aEngine.
+   * If aGraph is non-null, use that as the MediaStreamGraph, otherwise use
+   * aCtx's graph. aGraph is only non-null when called for AudioDestinationNode
+   * since the context's graph hasn't been set up in that case.
    */
   static already_AddRefed<AudioNodeStream>
-  Create(MediaStreamGraph* aGraph, AudioNodeEngine* aEngine, Flags aKind);
+  Create(AudioContext* aCtx, AudioNodeEngine* aEngine, Flags aKind,
+         MediaStreamGraph* aGraph = nullptr);
 
 protected:
   /**
    * Transfers ownership of aEngine to the new AudioNodeStream.
    */
   AudioNodeStream(AudioNodeEngine* aEngine,
                   Flags aFlags,
-                  TrackRate aSampleRate,
-                  AudioContext::AudioContextId aContextId);
+                  TrackRate aSampleRate);
 
   ~AudioNodeStream();
 
 public:
   // Control API
   /**
    * Sets a parameter that's a time relative to some stream's played time.
    * This time is converted to a time relative to this stream when it's set.
@@ -127,25 +130,20 @@ public:
   {
     return mLastChunks;
   }
   virtual bool MainThreadNeedsUpdates() const override
   {
     return ((mFlags & NEED_MAIN_THREAD_FINISHED) && mFinished) ||
       (mFlags & NEED_MAIN_THREAD_CURRENT_TIME);
   }
-  virtual bool IsIntrinsicallyConsumed() const override
-  {
-    return true;
-  }
 
   // Any thread
   AudioNodeEngine* Engine() { return mEngine; }
   TrackRate SampleRate() const { return mSampleRate; }
-  AudioContext::AudioContextId AudioContextId() const override { return mAudioContextId; }
 
   /**
    * Convert a time in seconds on the destination stream to ticks
    * on this stream, including fractional position between ticks.
    */
   double FractionalTicksFromDestinationTime(AudioNodeStream* aDestination,
                                             double aSeconds);
   /**
@@ -187,19 +185,16 @@ protected:
   nsAutoPtr<AudioNodeEngine> mEngine;
   // The mixed input blocks are kept from iteration to iteration to avoid
   // reallocating channel data arrays and any buffers for mixing.
   OutputChunks mInputChunks;
   // The last block produced by this node.
   OutputChunks mLastChunks;
   // The stream's sampling rate
   const TrackRate mSampleRate;
-  // This is necessary to be able to find all the nodes for a given
-  // AudioContext. It is set on the main thread, in the constructor.
-  const AudioContext::AudioContextId mAudioContextId;
   // Whether this is an internal or external stream
   const Flags mFlags;
   // The number of input channels that this stream requires. 0 means don't care.
   uint32_t mNumberOfInputChannels;
   // The mixing modes
   ChannelCountMode mChannelCountMode;
   ChannelInterpretation mChannelInterpretation;
   // Whether the stream should be marked as finished as soon
--- a/dom/media/webaudio/AudioParam.cpp
+++ b/dom/media/webaudio/AudioParam.cpp
@@ -95,31 +95,31 @@ MediaStream*
 AudioParam::Stream()
 {
   if (mStream) {
     return mStream;
   }
 
   AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
   nsRefPtr<AudioNodeStream> stream =
-    AudioNodeStream::Create(mNode->Context()->Graph(), engine,
+    AudioNodeStream::Create(mNode->Context(), engine,
                             AudioNodeStream::NO_STREAM_FLAGS);
 
   // Force the input to have only one channel, and make it down-mix using
   // the speaker rules if needed.
   stream->SetChannelMixingParametersImpl(1, ChannelCountMode::Explicit, ChannelInterpretation::Speakers);
   // Mark as an AudioParam helper stream
   stream->SetAudioParamHelperStream();
 
   mStream = stream.forget();
 
   // Setup the AudioParam's stream as an input to the owner AudioNode's stream
   AudioNodeStream* nodeStream = mNode->GetStream();
   if (nodeStream) {
-    mNodeStreamPort = nodeStream->AllocateInputPort(mStream, 0);
+    mNodeStreamPort = nodeStream->AllocateInputPort(mStream);
   }
 
   // Let the MSG's copy of AudioParamTimeline know about the change in the stream
   mCallback(mNode);
 
   return mStream;
 }
 
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -245,17 +245,17 @@ BiquadFilterNode::BiquadFilterNode(Audio
               ChannelInterpretation::Speakers)
   , mType(BiquadFilterType::Lowpass)
   , mFrequency(new AudioParam(this, SendFrequencyToStream, 350.f, "frequency"))
   , mDetune(new AudioParam(this, SendDetuneToStream, 0.f, "detune"))
   , mQ(new AudioParam(this, SendQToStream, 1.f, "Q"))
   , mGain(new AudioParam(this, SendGainToStream, 0.f, "gain"))
 {
   BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination());
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
   engine->SetSourceStream(mStream);
 }
 
 BiquadFilterNode::~BiquadFilterNode()
 {
 }
 
--- a/dom/media/webaudio/ChannelMergerNode.cpp
+++ b/dom/media/webaudio/ChannelMergerNode.cpp
@@ -68,17 +68,17 @@ public:
 ChannelMergerNode::ChannelMergerNode(AudioContext* aContext,
                                      uint16_t aInputCount)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mInputCount(aInputCount)
 {
-  mStream = AudioNodeStream::Create(aContext->Graph(),
+  mStream = AudioNodeStream::Create(aContext,
                                     new ChannelMergerNodeEngine(this),
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 ChannelMergerNode::~ChannelMergerNode()
 {
 }
 
--- a/dom/media/webaudio/ChannelSplitterNode.cpp
+++ b/dom/media/webaudio/ChannelSplitterNode.cpp
@@ -55,17 +55,17 @@ public:
 ChannelSplitterNode::ChannelSplitterNode(AudioContext* aContext,
                                          uint16_t aOutputCount)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mOutputCount(aOutputCount)
 {
-  mStream = AudioNodeStream::Create(aContext->Graph(),
+  mStream = AudioNodeStream::Create(aContext,
                                     new ChannelSplitterNodeEngine(this),
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 ChannelSplitterNode::~ChannelSplitterNode()
 {
 }
 
--- a/dom/media/webaudio/ConvolverNode.cpp
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -186,17 +186,17 @@ private:
 ConvolverNode::ConvolverNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Clamped_max,
               ChannelInterpretation::Speakers)
   , mNormalize(true)
 {
   ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 ConvolverNode::~ConvolverNode()
 {
 }
 
 size_t
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -193,17 +193,17 @@ DelayNode::DelayNode(AudioContext* aCont
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mDelay(new AudioParam(this, SendDelayToStream, 0.0f, "delayTime"))
 {
   DelayNodeEngine* engine =
     new DelayNodeEngine(this, aContext->Destination(),
                         aContext->SampleRate() * aMaxDelay);
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
   engine->SetSourceStream(mStream);
 }
 
 DelayNode::~DelayNode()
 {
 }
 
--- a/dom/media/webaudio/DynamicsCompressorNode.cpp
+++ b/dom/media/webaudio/DynamicsCompressorNode.cpp
@@ -198,17 +198,17 @@ DynamicsCompressorNode::DynamicsCompress
   , mThreshold(new AudioParam(this, SendThresholdToStream, -24.f, "threshold"))
   , mKnee(new AudioParam(this, SendKneeToStream, 30.f, "knee"))
   , mRatio(new AudioParam(this, SendRatioToStream, 12.f, "ratio"))
   , mReduction(0)
   , mAttack(new AudioParam(this, SendAttackToStream, 0.003f, "attack"))
   , mRelease(new AudioParam(this, SendReleaseToStream, 0.25f, "release"))
 {
   DynamicsCompressorNodeEngine* engine = new DynamicsCompressorNodeEngine(this, aContext->Destination());
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
   engine->SetSourceStream(mStream);
 }
 
 DynamicsCompressorNode::~DynamicsCompressorNode()
 {
 }
 
--- a/dom/media/webaudio/GainNode.cpp
+++ b/dom/media/webaudio/GainNode.cpp
@@ -123,17 +123,17 @@ public:
 GainNode::GainNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mGain(new AudioParam(this, SendGainToStream, 1.0f, "gain"))
 {
   GainNodeEngine* engine = new GainNodeEngine(this, aContext->Destination());
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
   engine->SetSourceStream(mStream);
 }
 
 GainNode::~GainNode()
 {
 }
 
--- a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -34,17 +34,17 @@ MediaStreamAudioDestinationNode::MediaSt
                                                       aContext->Graph()))
 {
   // Ensure an audio track with the correct ID is exposed to JS
   mDOMStream->CreateDOMTrack(AudioNodeStream::AUDIO_TRACK, MediaSegment::AUDIO);
 
   ProcessedMediaStream* outputStream = mDOMStream->GetStream()->AsProcessedStream();
   MOZ_ASSERT(!!outputStream);
   AudioNodeEngine* engine = new AudioNodeEngine(this);
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::EXTERNAL_OUTPUT);
   mPort = outputStream->AllocateInputPort(mStream);
 
   nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc();
   if (doc) {
     mDOMStream->CombineWithPrincipal(doc->NodePrincipal());
   }
 }
--- a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -36,17 +36,17 @@ MediaStreamAudioSourceNode::MediaStreamA
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers),
     mInputStream(aMediaStream)
 {
   AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
   mStream = AudioNodeExternalInputStream::Create(aContext->Graph(), engine);
   ProcessedMediaStream* outputStream = static_cast<ProcessedMediaStream*>(mStream.get());
-  mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream(), 0);
+  mInputPort = outputStream->AllocateInputPort(aMediaStream->GetStream());
   mInputStream->AddConsumerToKeepAlive(static_cast<nsIDOMEventTarget*>(this));
 
   PrincipalChanged(mInputStream); // trigger enabling/disabling of the connector
   mInputStream->AddPrincipalChangeObserver(this);
 }
 
 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
 {
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -379,17 +379,17 @@ OscillatorNode::OscillatorNode(AudioCont
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(OscillatorType::Sine)
   , mFrequency(new AudioParam(this, SendFrequencyToStream, 440.0f, "frequency"))
   , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
   , mStartCalled(false)
 {
   OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination());
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NEED_MAIN_THREAD_FINISHED);
   engine->SetSourceStream(mStream);
   mStream->AddMainThreadListener(this);
 }
 
 OscillatorNode::~OscillatorNode()
 {
 }
--- a/dom/media/webaudio/PannerNode.cpp
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -235,17 +235,17 @@ PannerNode::PannerNode(AudioContext* aCo
   , mVelocity()
   , mRefDistance(1.)
   , mMaxDistance(10000.)
   , mRolloffFactor(1.)
   , mConeInnerAngle(360.)
   , mConeOuterAngle(360.)
   , mConeOuterGain(0.)
 {
-  mStream = AudioNodeStream::Create(aContext->Graph(),
+  mStream = AudioNodeStream::Create(aContext,
                                     new PannerNodeEngine(this),
                                     AudioNodeStream::NO_STREAM_FLAGS);
   // We should register once we have set up our stream and engine.
   Context()->Listener()->RegisterPannerNode(this);
 }
 
 PannerNode::~PannerNode()
 {
--- a/dom/media/webaudio/ScriptProcessorNode.cpp
+++ b/dom/media/webaudio/ScriptProcessorNode.cpp
@@ -496,17 +496,17 @@ ScriptProcessorNode::ScriptProcessorNode
   , mNumberOfOutputChannels(aNumberOfOutputChannels)
 {
   MOZ_ASSERT(BufferSize() % WEBAUDIO_BLOCK_SIZE == 0, "Invalid buffer size");
   ScriptProcessorNodeEngine* engine =
     new ScriptProcessorNodeEngine(this,
                                   aContext->Destination(),
                                   BufferSize(),
                                   aNumberOfInputChannels);
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
   engine->SetSourceStream(mStream);
 }
 
 ScriptProcessorNode::~ScriptProcessorNode()
 {
 }
 
--- a/dom/media/webaudio/StereoPannerNode.cpp
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -176,17 +176,17 @@ public:
 StereoPannerNode::StereoPannerNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Clamped_max,
               ChannelInterpretation::Speakers)
   , mPan(new AudioParam(this, SendPanToStream, 0.f, "pan"))
 {
   StereoPannerNodeEngine* engine = new StereoPannerNodeEngine(this, aContext->Destination());
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
   engine->SetSourceStream(mStream);
 }
 
 StereoPannerNode::~StereoPannerNode()
 {
 }
 
--- a/dom/media/webaudio/WaveShaperNode.cpp
+++ b/dom/media/webaudio/WaveShaperNode.cpp
@@ -283,17 +283,17 @@ WaveShaperNode::WaveShaperNode(AudioCont
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mCurve(nullptr)
   , mType(OverSampleType::None)
 {
   mozilla::HoldJSObjects(this);
 
   WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
-  mStream = AudioNodeStream::Create(aContext->Graph(), engine,
+  mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 WaveShaperNode::~WaveShaperNode()
 {
   ClearCurve();
 }
 
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -558,17 +558,17 @@ nsSpeechTask::Pause()
   MOZ_ASSERT(XRE_IsParentProcess());
 
   if (mCallback) {
     DebugOnly<nsresult> rv = mCallback->OnPause();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to call onPause() callback");
   }
 
   if (mStream) {
-    mStream->ChangeExplicitBlockerCount(1);
+    mStream->Suspend();
   }
 
   if (!mInited) {
     mPrePaused = true;
   }
 
   if (!mIndirectAudio) {
     DispatchPauseImpl(GetCurrentTime(), GetCurrentCharOffset());
@@ -581,17 +581,17 @@ nsSpeechTask::Resume()
   MOZ_ASSERT(XRE_IsParentProcess());
 
   if (mCallback) {
     DebugOnly<nsresult> rv = mCallback->OnResume();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to call onResume() callback");
   }
 
   if (mStream) {
-    mStream->ChangeExplicitBlockerCount(-1);
+    mStream->Resume();
   }
 
   if (mPrePaused) {
     mPrePaused = false;
     nsSynthVoiceRegistry::GetInstance()->ResumeQueue();
   }
 
   if (!mIndirectAudio) {
@@ -607,33 +607,33 @@ nsSpeechTask::Cancel()
   LOG(LogLevel::Debug, ("nsSpeechTask::Cancel"));
 
   if (mCallback) {
     DebugOnly<nsresult> rv = mCallback->OnCancel();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Unable to call onCancel() callback");
   }
 
   if (mStream) {
-    mStream->ChangeExplicitBlockerCount(1);
+    mStream->Suspend();
   }
 
   if (!mInited) {
     mPreCanceled = true;
   }
 
   if (!mIndirectAudio) {
     DispatchEndInner(GetCurrentTime(), GetCurrentCharOffset());
   }
 }
 
 void
 nsSpeechTask::ForceEnd()
 {
   if (mStream) {
-    mStream->ChangeExplicitBlockerCount(1);
+    mStream->Suspend();
   }
 
   if (!mInited) {
     mPreCanceled = true;
   }
 
   DispatchEndInner(GetCurrentTime(), GetCurrentCharOffset());
 }
--- a/dom/plugins/base/nsJSNPRuntime.h
+++ b/dom/plugins/base/nsJSNPRuntime.h
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsJSNPRuntime_h_
 #define nsJSNPRuntime_h_
 
 #include "nscore.h"
 #include "npapi.h"
 #include "npruntime.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 
 class nsJSNPRuntime
 {
 public:
   static void OnPluginDestroy(NPP npp);
   static void OnPluginDestroyPending(NPP npp);
 };
 
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -286,17 +286,17 @@ nsMixedContentBlocker::AsyncOnChannelRed
     // If an addon creates a channel, they may not set loadinfo. If that
     // channel redirects from one page to another page, we would get caught
     // in this code path. Hence, we have to return NS_OK. Once we have more
     // confidence that all channels have loadinfo, we can change this to
     // a failure. See bug 1077201.
     return NS_OK;
   }
 
-  uint32_t contentPolicyType = loadInfo->GetContentPolicyType();
+  nsContentPolicyType contentPolicyType = loadInfo->InternalContentPolicyType();
   nsCOMPtr<nsIPrincipal> requestingPrincipal = loadInfo->LoadingPrincipal();
 
   // Since we are calling shouldLoad() directly on redirects, we don't go through the code
   // in nsContentPolicyUtils::NS_CheckContentLoadPolicy(). Hence, we have to
   // duplicate parts of it here.
   nsCOMPtr<nsIURI> requestingLocation;
   if (requestingPrincipal) {
     // We check to see if the loadingPrincipal is systemPrincipal and return
@@ -305,17 +305,17 @@ nsMixedContentBlocker::AsyncOnChannelRed
       return NS_OK;
     }
     // We set the requestingLocation from the RequestingPrincipal.
     rv = requestingPrincipal->GetURI(getter_AddRefs(requestingLocation));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   int16_t decision = REJECT_REQUEST;
-  rv = ShouldLoad(nsContentUtils::InternalContentPolicyTypeToExternal(contentPolicyType),
+  rv = ShouldLoad(nsContentUtils::InternalContentPolicyTypeToExternalOrScript(contentPolicyType),
                   newUri,
                   requestingLocation,
                   loadInfo->LoadingNode(),
                   EmptyCString(),       // aMimeGuess
                   nullptr,              // aExtra
                   requestingPrincipal,
                   &decision);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -373,19 +373,27 @@ nsMixedContentBlocker::ShouldLoad(bool a
                                   nsIPrincipal* aRequestPrincipal,
                                   int16_t* aDecision)
 {
   // Asserting that we are on the main thread here and hence do not have to lock
   // and unlock sBlockMixedScript and sBlockMixedDisplay before reading/writing
   // to them.
   MOZ_ASSERT(NS_IsMainThread());
 
-  MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+  MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternalOrScript(aContentType),
              "We should only see external content policy types here.");
 
+  // The content policy type that we receive may be an internal type for
+  // scripts.  Let's remember if we have seen a worker type, and reset it to the
+  // external type in all cases right now.
+  bool isWorkerType = aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+                      aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+                      aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
+  aContentType = nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
+
   // Assume active (high risk) content and blocked by default
   MixedContentTypes classification = eMixedScript;
   // Make decision to block/reject by default
   *aDecision = REJECT_REQUEST;
 
   // Notes on non-obvious decisions:
   //
   // TYPE_DTD: A DTD can contain entity definitions that expand to scripts.
@@ -620,16 +628,33 @@ nsMixedContentBlocker::ShouldLoad(bool a
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
   if (!parentIsHttps) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
+  // Disallow mixed content loads for workers, shared workers and service
+  // workers.
+  if (isWorkerType) {
+    // For workers, we can assume that we're mixed content at this point, since
+    // the parent is https, and the protocol associated with aContentLocation
+    // doesn't map to the secure URI flags checked above.  Assert this for
+    // sanity's sake
+#ifdef DEBUG
+    bool isHttpsScheme = false;
+    rv = aContentLocation->SchemeIs("https", &isHttpsScheme);
+    NS_ENSURE_SUCCESS(rv, rv);
+    MOZ_ASSERT(!isHttpsScheme);
+#endif
+    *aDecision = REJECT_REQUEST;
+    return NS_OK;
+  }
+
   // Determine if the rootDoc is https and if the user decided to allow Mixed Content
   nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(aRequestingContext);
   NS_ENSURE_TRUE(docShell, NS_OK);
 
   // The page might have set the CSP directive 'upgrade-insecure-requests'. In such
   // a case allow the http: load to succeed with the promise that the channel will
   // get upgraded to https before fetching any data from the netwerk.
   // Please see: nsHttpChannel::Connect()
--- a/dom/smil/nsSMILCompositor.h
+++ b/dom/smil/nsSMILCompositor.h
@@ -8,17 +8,17 @@
 #define NS_SMILCOMPOSITOR_H_
 
 #include "mozilla/Move.h"
 #include "nsTHashtable.h"
 #include "nsString.h"
 #include "nsSMILAnimationFunction.h"
 #include "nsSMILTargetIdentifier.h"
 #include "nsSMILCompositorTable.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 
 //----------------------------------------------------------------------
 // nsSMILCompositor
 //
 // Performs the composition of the animation sandwich by combining the results
 // of a series animation functions according to the rules of SMIL composition
 // including prioritising animations.
 
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -985,21 +985,21 @@ var interfaceNamesInGlobalScope =
     "ScreenOrientation",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ScriptProcessorNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ScrollAreaEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Selection",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ServiceWorker", b2g: false, releaseAndroid: false},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ServiceWorkerContainer", b2g: false, releaseAndroid: false},
-// IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "ServiceWorkerRegistration", b2g: false, releaseAndroid: false},
+    {name: "ServiceWorker", b2g: false, nightlyAndroid: true, android: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "ServiceWorkerContainer", b2g: false, nightlyAndroid: true, android: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "ServiceWorkerRegistration", b2g: false, nightlyAndroid: true, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SettingsLock",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SettingsManager",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ShadowRoot", // Bogus, but the test harness forces it on.  See bug 1159768.
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SharedWorker",
@@ -1482,25 +1482,25 @@ function createInterfaceMap(isXBLScope) 
   function addInterfaces(interfaces)
   {
     for (var entry of interfaces) {
       if (typeof(entry) === "string") {
         interfaceMap[entry] = true;
       } else {
         ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
         if ((entry.nightly === !isNightly) ||
+            (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
             (entry.xbl === !isXBLScope) ||
             (entry.desktop === !isDesktop) ||
             (entry.b2g === !isB2G) ||
             (entry.windows === !isWindows) ||
             (entry.mac === !isMac) ||
             (entry.linux === !isLinux) ||
-            (entry.android === !isAndroid) ||
+            (entry.android === !isAndroid && !entry.nightlyAndroid) ||
             (entry.release === !isRelease) ||
-            (entry.releaseAndroid === !(isAndroid && isRelease)) ||
             (entry.permission && !hasPermission(entry.permission)) ||
             entry.disabled) {
           interfaceMap[entry.name] = false;
         } else {
           interfaceMap[entry.name] = true;
         }
       }
     }
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -115,31 +115,28 @@ ChannelFromScriptURL(nsIPrincipal* princ
   nsCOMPtr<nsIURI> uri;
   rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
                                                  aScriptURL, parentDoc,
                                                  baseURI);
   if (NS_FAILED(rv)) {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
-  // If we're part of a document then check the content load policy.
-  if (parentDoc) {
-    int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-    rv = NS_CheckContentLoadPolicy(aContentPolicyType, uri,
-                                   principal, parentDoc,
-                                   NS_LITERAL_CSTRING("text/javascript"),
-                                   nullptr, &shouldLoad,
-                                   nsContentUtils::GetContentPolicy(),
-                                   secMan);
-    if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
-      if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
-        return rv = NS_ERROR_CONTENT_BLOCKED;
-      }
-      return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+  rv = NS_CheckContentLoadPolicy(aContentPolicyType, uri,
+                                 principal, parentDoc,
+                                 NS_LITERAL_CSTRING("text/javascript"),
+                                 nullptr, &shouldLoad,
+                                 nsContentUtils::GetContentPolicy(),
+                                 secMan);
+  if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+    if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
+      return rv = NS_ERROR_CONTENT_BLOCKED;
     }
+    return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
   }
 
   if (aWorkerScriptType == DebuggerScript) {
     bool isChrome = false;
     NS_ENSURE_SUCCESS(uri->SchemeIs("chrome", &isChrome),
                       NS_ERROR_DOM_SECURITY_ERR);
 
     bool isResource = false;
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1183,18 +1183,19 @@ public:
     nsRefPtr<TimerThreadEventTarget> target =
       new TimerThreadEventTarget(mWorkerPrivate, runnable);
 
     if (NS_FAILED(timer->SetTarget(target))) {
       JS_ReportError(aCx, "Failed to set timer's target!");
       return false;
     }
 
-    if (NS_FAILED(timer->InitWithFuncCallback(DummyCallback, nullptr, aDelayMS,
-                                              nsITimer::TYPE_ONE_SHOT))) {
+    if (NS_FAILED(timer->InitWithNamedFuncCallback(
+          DummyCallback, nullptr, aDelayMS, nsITimer::TYPE_ONE_SHOT,
+          "dom::workers::DummyCallback(1)"))) {
       JS_ReportError(aCx, "Failed to start timer!");
       return false;
     }
 
     mTimer.swap(timer);
     return true;
   }
 
@@ -4531,19 +4532,19 @@ WorkerPrivate::SetGCTimerMode(GCTimerMod
   }
   else {
     target = mIdleGCTimerTarget;
     delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
     type = nsITimer::TYPE_ONE_SHOT;
   }
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->SetTarget(target)));
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mGCTimer->InitWithFuncCallback(DummyCallback,
-                                                              nullptr, delay,
-                                                              type)));
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mGCTimer->InitWithNamedFuncCallback(DummyCallback, nullptr, delay, type,
+                                        "dom::workers::DummyCallback(2)")));
 
   if (aMode == PeriodicTimer) {
     LOG(("Worker %p scheduled periodic GC timer\n", this));
     mPeriodicGCTimerRunning = true;
   }
   else {
     LOG(("Worker %p scheduled idle GC timer\n", this));
     mIdleGCTimerRunning = true;
@@ -5940,18 +5941,19 @@ WorkerPrivate::RescheduleTimeoutTimer(JS
   AssertIsOnWorkerThread();
   NS_ASSERTION(!mTimeouts.IsEmpty(), "Should have some timeouts!");
   NS_ASSERTION(mTimer, "Should have a timer!");
 
   double delta =
     (mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
   uint32_t delay = delta > 0 ? std::min(delta, double(UINT32_MAX)) : 0;
 
-  nsresult rv = mTimer->InitWithFuncCallback(DummyCallback, nullptr, delay,
-                                             nsITimer::TYPE_ONE_SHOT);
+  nsresult rv = mTimer->InitWithNamedFuncCallback(
+    DummyCallback, nullptr, delay, nsITimer::TYPE_ONE_SHOT,
+    "dom::workers::DummyCallback(3)");
   if (NS_FAILED(rv)) {
     JS_ReportError(aCx, "Failed to start timer!");
     return false;
   }
 
   return true;
 }
 
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -727,17 +727,17 @@ public:
   ContentPolicyType(WorkerType aWorkerType)
   {
     switch (aWorkerType) {
     case WorkerTypeDedicated:
       return nsIContentPolicy::TYPE_INTERNAL_WORKER;
     case WorkerTypeShared:
       return nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER;
     case WorkerTypeService:
-      return nsIContentPolicy::TYPE_SCRIPT;
+      return nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
     default:
       MOZ_ASSERT_UNREACHABLE("Invalid worker type");
       return nsIContentPolicy::TYPE_INVALID;
     }
   }
 
   const nsCString&
   SharedWorkerName() const
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -169,17 +169,17 @@ var interfaceNamesInGlobalScope =
     { name: "PushManager", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushSubscription", b2g: false, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    { name: "ServiceWorkerRegistration", b2g: false, releaseAndroid: false },
+    { name: "ServiceWorkerRegistration", b2g: false, nightlyAndroid: true, android: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TextDecoder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "TextEncoder",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "XMLHttpRequest",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "XMLHttpRequestEventTarget",
@@ -214,21 +214,21 @@ function createInterfaceMap(permissionMa
   function addInterfaces(interfaces)
   {
     for (var entry of interfaces) {
       if (typeof(entry) === "string") {
         interfaceMap[entry] = true;
       } else {
         ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
         if ((entry.nightly === !isNightly) ||
+            (entry.nightlyAndroid === !(isAndroid && isNightly) && isAndroid) ||
             (entry.desktop === !isDesktop) ||
-            (entry.android === !isAndroid) ||
+            (entry.android === !isAndroid && !entry.nightlyAndroid) ||
             (entry.b2g === !isB2G) ||
             (entry.release === !isRelease) ||
-            (entry.releaseAndroid === !(isAndroid && isRelease)) ||
             (entry.permission && !permissionMap[entry.permission]) ||
             entry.disabled) {
           interfaceMap[entry.name] = false;
         } else {
           interfaceMap[entry.name] = true;
         }
       }
     }
--- a/dom/xul/templates/nsContentSupportMap.h
+++ b/dom/xul/templates/nsContentSupportMap.h
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 nsContentSupportMap_h__
 #define nsContentSupportMap_h__
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsTemplateMatch.h"
 
 /**
  * The nsContentSupportMap maintains a mapping from a "resource element"
  * in the content tree to the nsTemplateMatch that was used to instantiate it. This
  * is necessary to allow the XUL content to be built lazily. Specifically,
  * when building "resumes" on a partially-built content element, the builder
  * will walk upwards in the content tree to find the first element with an
--- a/dom/xul/templates/nsRuleNetwork.h
+++ b/dom/xul/templates/nsRuleNetwork.h
@@ -29,17 +29,17 @@
 #define nsRuleNetwork_h__
 
 #include "mozilla/Attributes.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIAtom.h"
 #include "nsIDOMNode.h"
 #include "plhash.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsIRDFNode.h"
 
 class nsXULTemplateResultSetRDF;
 
 //----------------------------------------------------------------------
 
 /**
  * A memory element that supports an instantiation. A memory element holds a
--- a/dom/xul/templates/nsTemplateMap.h
+++ b/dom/xul/templates/nsTemplateMap.h
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 nsTemplateMap_h__
 #define nsTemplateMap_h__
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsXULElement.h"
 
 class nsTemplateMap {
 protected:
     struct Entry : public PLDHashEntryHdr {
         nsIContent*     mContent;
         nsIContent*     mTemplate;
     };
--- a/dom/xul/templates/nsTreeRows.h
+++ b/dom/xul/templates/nsTreeRows.h
@@ -3,17 +3,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/. */
 
 #ifndef nsTreeRows_h__
 #define nsTreeRows_h__
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsIXULTemplateResult.h"
 #include "nsTemplateMatch.h"
 #include "nsIRDFResource.h"
 
 
 /**
  * This class maintains the state of the XUL tree builder's
  * rows. It maps a row number to the nsTemplateMatch object that
--- a/dom/xul/templates/nsXULContentBuilder.cpp
+++ b/dom/xul/templates/nsXULContentBuilder.cpp
@@ -29,17 +29,17 @@
 #include "nsContentCreatorFunctions.h"
 #include "nsContentUtils.h"
 #include "nsAttrName.h"
 #include "nsNodeUtils.h"
 #include "mozAutoDocUpdate.h"
 #include "nsTextNode.h"
 #include "mozilla/dom/Element.h"
 
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "rdf.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
 //
 // Return values for EnsureElementHasGenericChild()
--- a/dom/xul/templates/nsXULTemplateBuilder.cpp
+++ b/dom/xul/templates/nsXULTemplateBuilder.cpp
@@ -52,17 +52,17 @@
 #include "nsTArray.h"
 #include "nsXPIDLString.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsGkAtoms.h"
 #include "nsXULElement.h"
 #include "jsapi.h"
 #include "mozilla/Logging.h"
 #include "rdf.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "plhash.h"
 #include "nsDOMClassInfoID.h"
 #include "nsPIDOMWindow.h"
 #include "nsIConsoleService.h" 
 #include "nsNetUtil.h"
 #include "nsXULTemplateBuilder.h"
 #include "nsXULTemplateQueryProcessorRDF.h"
 #include "nsXULTemplateQueryProcessorXML.h"
--- a/embedding/components/commandhandler/nsCommandParams.h
+++ b/embedding/components/commandhandler/nsCommandParams.h
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsCommandParams_h__
 #define nsCommandParams_h__
 
 #include "nsString.h"
 #include "nsICommandParams.h"
 #include "nsCOMPtr.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 
 class nsCommandParams : public nsICommandParams
 {
 public:
   nsCommandParams();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSICOMMANDPARAMS
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -2631,22 +2631,22 @@ nsPermissionManager::ImportDefaults()
   nsCOMPtr<nsIURI> defaultsURI;
   nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewChannel(getter_AddRefs(channel),
                      defaultsURI,
                      nsContentUtils::GetSystemPrincipal(),
-                     nsILoadInfo::SEC_NORMAL,
+                     nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                      nsIContentPolicy::TYPE_OTHER);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIInputStream> inputStream;
-  rv = channel->Open(getter_AddRefs(inputStream));
+  rv = channel->Open2(getter_AddRefs(inputStream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = _DoImport(inputStream, nullptr);
   inputStream->Close();
   return rv;
 }
 
 // _DoImport reads the specified stream and adds the parsed elements.  If
--- a/extensions/permissions/nsContentBlocker.cpp
+++ b/extensions/permissions/nsContentBlocker.cpp
@@ -52,17 +52,18 @@ static const char *kTypeString[] = {
                                     "", // TYPE_INTERNAL_EMBED
                                     "", // TYPE_INTERNAL_OBJECT
                                     "", // TYPE_INTERNAL_FRAME
                                     "", // TYPE_INTERNAL_IFRAME
                                     "", // TYPE_INTERNAL_AUDIO
                                     "", // TYPE_INTERNAL_VIDEO
                                     "", // TYPE_INTERNAL_TRACK
                                     "", // TYPE_INTERNAL_XMLHTTPREQUEST
-                                    ""  // TYPE_INTERNAL_EVENTSOURCE
+                                    "", // TYPE_INTERNAL_EVENTSOURCE
+                                    "", // TYPE_INTERNAL_SERVICE_WORKER
 };
 
 #define NUMBER_OF_TYPES MOZ_ARRAY_LENGTH(kTypeString)
 uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES];
 
 NS_IMPL_ISUPPORTS(nsContentBlocker, 
                   nsIContentPolicy,
                   nsIObserver,
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -693,22 +693,25 @@ public:
   /**
    * Similar to PreTranslate, but applies a scale instead of a translation.
    */
   Matrix4x4 &PreScale(Float aX, Float aY, Float aZ)
   {
     _11 *= aX;
     _12 *= aX;
     _13 *= aX;
+    _14 *= aX;
     _21 *= aY;
     _22 *= aY;
     _23 *= aY;
+    _24 *= aY;
     _31 *= aZ;
     _32 *= aZ;
     _33 *= aZ;
+    _34 *= aZ;
 
     return *this;
   }
 
   /**
    * Similar to PostTranslate, but applies a scale instead of a translation.
    */
   Matrix4x4 &PostScale(Float aScaleX, Float aScaleY, Float aScaleZ)
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -23,16 +23,17 @@
 #include "mozilla/EventStateManager.h"  // for WheelPrefs
 #include "nsDebug.h"                    // for NS_WARNING
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsThreadUtils.h"              // for NS_IsMainThread
 #include "mozilla/gfx/Logging.h"        // for gfx::TreeLog
 #include "UnitTransforms.h"             // for ViewAs
 #include "gfxPrefs.h"                   // for gfxPrefs
 #include "OverscrollHandoffState.h"     // for OverscrollHandoffState
+#include "TaskThrottler.h"              // for TaskThrottler
 #include "TreeTraversal.h"              // for generic tree traveral algorithms
 #include "LayersLogging.h"              // for Stringify
 #include "Units.h"                      // for ParentlayerPixel
 
 #define ENABLE_APZCTM_LOGGING 0
 // #define ENABLE_APZCTM_LOGGING 1
 
 #if ENABLE_APZCTM_LOGGING
@@ -102,21 +103,28 @@ APZCTreeManager::APZCTreeManager()
   mApzcTreeLog.ConditionOnPrefFunction(gfxPrefs::APZPrintTree);
 }
 
 APZCTreeManager::~APZCTreeManager()
 {
 }
 
 AsyncPanZoomController*
-APZCTreeManager::MakeAPZCInstance(uint64_t aLayersId,
-                                  GeckoContentController* aController)
+APZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
+                                 GeckoContentController* aController,
+                                 TaskThrottler* aPaintThrottler)
 {
   return new AsyncPanZoomController(aLayersId, this, mInputQueue,
-    aController, AsyncPanZoomController::USE_GESTURE_DETECTOR);
+    aController, aPaintThrottler, AsyncPanZoomController::USE_GESTURE_DETECTOR);
+}
+
+TimeStamp
+APZCTreeManager::GetFrameTime()
+{
+  return TimeStamp::Now();
 }
 
 void
 APZCTreeManager::SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                          const nsTArray<TouchBehaviorFlags> &aValues)
 {
   mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
 }
@@ -408,17 +416,27 @@ APZCTreeManager::PrepareNodeForLayer(con
     }
 
     // The APZC we get off the layer may have been destroyed previously if the
     // layer was inactive or omitted from the layer tree for whatever reason
     // from a layers update. If it later comes back it will have a reference to
     // a destroyed APZC and so we need to throw that out and make a new one.
     bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
     if (newApzc) {
-      apzc = MakeAPZCInstance(aLayersId, state->mController);
+      // Look up the paint throttler for this layers id, or create it if
+      // this is the first APZC for this layers id.
+      auto throttlerInsertResult = mPaintThrottlerMap.insert(
+          std::make_pair(aLayersId, nsRefPtr<TaskThrottler>()));
+      if (throttlerInsertResult.second) {
+        throttlerInsertResult.first->second = new TaskThrottler(
+            GetFrameTime(), TimeDuration::FromMilliseconds(500));
+      }
+
+      apzc = NewAPZCInstance(aLayersId, state->mController,
+                             throttlerInsertResult.first->second);
       apzc->SetCompositorParent(aState.mCompositor);
       if (state->mCrossProcessParent != nullptr) {
         apzc->ShareFrameMetricsAcrossProcesses();
       }
       MOZ_ASSERT(node == nullptr);
       node = new HitTestingTreeNode(apzc, true, aLayersId);
     } else {
       // If we are re-using a node for this layer clear the tree pointers
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -44,16 +44,17 @@ class Layer;
 class AsyncPanZoomController;
 class CompositorParent;
 class OverscrollHandoffChain;
 struct OverscrollHandoffState;
 class LayerMetricsWrapper;
 class InputQueue;
 class GeckoContentController;
 class HitTestingTreeNode;
+class TaskThrottler;
 
 /**
  * ****************** NOTE ON LOCK ORDERING IN APZ **************************
  *
  * There are two kinds of locks used by APZ: APZCTreeManager::mTreeLock
  * ("the tree lock") and AsyncPanZoomController::mMonitor ("APZC locks").
  *
  * To avoid deadlock, we impose a lock ordering between these locks, which is:
@@ -386,19 +387,23 @@ public:
    * Build the chain of APZCs that will handle overscroll for a pan starting at |aInitialTarget|.
    */
   nsRefPtr<const OverscrollHandoffChain> BuildOverscrollHandoffChain(const nsRefPtr<AsyncPanZoomController>& aInitialTarget);
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~APZCTreeManager();
 
-  // Hook for gtests subclass
-  virtual AsyncPanZoomController* MakeAPZCInstance(uint64_t aLayersId,
-                                                   GeckoContentController* aController);
+  // Protected hooks for gtests subclass
+  virtual AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId,
+                                                  GeckoContentController* aController,
+                                                  TaskThrottler* aPaintThrottler);
+public:
+  // Public hooks for gtests subclass
+  virtual TimeStamp GetFrameTime();
 
 public:
   /* Some helper functions to find an APZC given some identifying input. These functions
      lock the tree of APZCs while they find the right one, and then return an addref'd
      pointer to it. This allows caller code to just use the target APZC without worrying
      about it going away. These are public for testing code and generally should not be
      used by other production code.
   */
@@ -501,17 +506,20 @@ private:
    * is considered part of the APZC tree management state.
    * Finally, the lock needs to be held when accessing mZoomConstraints.
    * IMPORTANT: See the note about lock ordering at the top of this file. */
   mutable mozilla::Monitor mTreeLock;
   nsRefPtr<HitTestingTreeNode> mRootNode;
   /* Holds the zoom constraints for scrollable layers, as determined by the
    * the main-thread gecko code. */
   std::map<ScrollableLayerGuid, ZoomConstraints> mZoomConstraints;
-
+  /* Stores a paint throttler for each layers id. There is one for each layers
+   * id to ensure that one child process painting slowly doesn't hold up
+   * another. */
+  std::map<uint64_t, nsRefPtr<TaskThrottler>> mPaintThrottlerMap;
   /* This tracks the APZC that should receive all inputs for the current input event block.
    * This allows touch points to move outside the thing they started on, but still have the
    * touch events delivered to the same initial APZC. This will only ever be touched on the
    * input delivery thread, and so does not require locking.
    */
   nsRefPtr<AsyncPanZoomController> mApzcForInputBlock;
   /* The hit result for the current input event block; this should always be in
    * sync with mApzcForInputBlock.
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -383,17 +383,18 @@ static inline void LogRendertraceRect(co
 }
 
 // Counter used to give each APZC a unique id
 static uint32_t sAsyncPanZoomControllerCount = 0;
 
 TimeStamp
 AsyncPanZoomController::GetFrameTime() const
 {
-  return TimeStamp::Now();
+  APZCTreeManager* treeManagerLocal = GetApzcTreeManager();
+  return treeManagerLocal ? treeManagerLocal->GetFrameTime() : TimeStamp::Now();
 }
 
 class MOZ_STACK_CLASS StateChangeNotificationBlocker {
 public:
   explicit StateChangeNotificationBlocker(AsyncPanZoomController* aApzc)
     : mApzc(aApzc)
   {
     ReentrantMonitorAutoEnter lock(mApzc->mMonitor);
@@ -802,36 +803,38 @@ AsyncPanZoomController::InitializeGlobal
                      gfxPrefs::APZCurveFunctionY2()));
   ClearOnShutdown(&gVelocityCurveFunction);
 }
 
 AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
                                                APZCTreeManager* aTreeManager,
                                                const nsRefPtr<InputQueue>& aInputQueue,
                                                GeckoContentController* aGeckoContentController,
+                                               TaskThrottler* aPaintThrottler,
                                                GestureBehavior aGestures)
   :  mLayersId(aLayersId),
-     mPaintThrottler(GetFrameTime(), TimeDuration::FromMilliseconds(500)),
+     mPaintThrottler(aPaintThrottler),
      mGeckoContentController(aGeckoContentController),
      mRefPtrMonitor("RefPtrMonitor"),
+     // mTreeManager must be initialized before GetFrameTime() is called
+     mTreeManager(aTreeManager),
      mSharingFrameMetricsAcrossProcesses(false),
      mMonitor("AsyncPanZoomController"),
      mX(this),
      mY(this),
      mPanDirRestricted(false),
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
      mLastAsyncScrollTime(GetFrameTime()),
      mLastAsyncScrollOffset(0, 0),
      mCurrentAsyncScrollOffset(0, 0),
      mAsyncScrollTimeoutTask(nullptr),
      mState(NOTHING),
      mNotificationBlockers(0),
      mInputQueue(aInputQueue),
-     mTreeManager(aTreeManager),
      mAPZCId(sAsyncPanZoomControllerCount++),
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false)
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
     mGestureEventListener = new GestureEventListener(this);
   }
 }
@@ -2480,17 +2483,17 @@ void AsyncPanZoomController::ScheduleCom
   if (mCompositorParent) {
     mCompositorParent->ScheduleRenderOnCompositorThread();
   }
 }
 
 void AsyncPanZoomController::ScheduleCompositeAndMaybeRepaint() {
   ScheduleComposite();
 
-  TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
+  TimeDuration timePaintDelta = mPaintThrottler->TimeSinceLastRequest(GetFrameTime());
   if (timePaintDelta.ToMilliseconds() > gfxPrefs::APZPanRepaintInterval()) {
     RequestContentRepaint();
   }
 }
 
 void AsyncPanZoomController::FlushRepaintForOverscrollHandoff() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   RequestContentRepaint();
@@ -2501,28 +2504,28 @@ void AsyncPanZoomController::FlushRepain
   APZC_LOG("%p flushing repaint for new input block\n", this);
 
   ReentrantMonitorAutoEnter lock(mMonitor);
   // We need to send a new repaint request unthrottled, but that
   // will obsolete any pending repaint request in the paint throttler.
   // Therefore we should clear out the pending task and restore the
   // state of mLastPaintRequestMetrics to what it was before the
   // pending task was queued.
-  mPaintThrottler.CancelPendingTask();
+  mPaintThrottler->CancelPendingTask();
   mLastPaintRequestMetrics = mLastDispatchedPaintMetrics;
 
   RequestContentRepaint(mFrameMetrics, false /* not throttled */);
   UpdateSharedCompositorFrameMetrics();
 }
 
 void AsyncPanZoomController::FlushRepaintIfPending() {
   // Just tell the paint throttler to send the pending repaint request if
   // there is one.
   ReentrantMonitorAutoEnter lock(mMonitor);
-  mPaintThrottler.TaskComplete(GetFrameTime());
+  mPaintThrottler->TaskComplete(GetFrameTime());
 }
 
 bool AsyncPanZoomController::SnapBackIfOverscrolled() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   // It's possible that we're already in the middle of an overscroll
   // animation - if so, don't start a new one.
   if (IsOverscrolled() && mState != OVERSCROLL_ANIMATION) {
     APZC_LOG("%p is overscrolled, starting snap-back\n", this);
@@ -2555,17 +2558,17 @@ int32_t AsyncPanZoomController::GetLastT
 void AsyncPanZoomController::RequestContentRepaint() {
   RequestContentRepaint(mFrameMetrics);
 }
 
 void AsyncPanZoomController::RequestContentRepaint(FrameMetrics& aFrameMetrics, bool aThrottled) {
   aFrameMetrics.SetDisplayPortMargins(
     CalculatePendingDisplayPort(aFrameMetrics,
                                 GetVelocityVector(),
-                                mPaintThrottler.AverageDuration().ToSeconds()));
+                                mPaintThrottler->AverageDuration().ToSeconds()));
   aFrameMetrics.SetUseDisplayPortMargins();
 
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   ScreenMargin marginDelta = (mLastPaintRequestMetrics.GetDisplayPortMargins()
                            - aFrameMetrics.GetDisplayPortMargins());
   if (fabsf(marginDelta.left) < EPSILON &&
       fabsf(marginDelta.top) < EPSILON &&
@@ -2578,17 +2581,17 @@ void AsyncPanZoomController::RequestCont
       aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
       fabsf(aFrameMetrics.GetViewport().width - mLastPaintRequestMetrics.GetViewport().width) < EPSILON &&
       fabsf(aFrameMetrics.GetViewport().height - mLastPaintRequestMetrics.GetViewport().height) < EPSILON) {
     return;
   }
 
   SendAsyncScrollEvent();
   if (aThrottled) {
-    mPaintThrottler.PostTask(
+    mPaintThrottler->PostTask(
       FROM_HERE,
       UniquePtr<CancelableTask>(NewRunnableMethod(this,
                         &AsyncPanZoomController::DispatchRepaintRequest,
                         aFrameMetrics)),
       GetFrameTime());
   } else {
     DispatchRepaintRequest(aFrameMetrics);
   }
@@ -2649,17 +2652,17 @@ bool AsyncPanZoomController::UpdateAnima
   }
   TimeDuration sampleTimeDelta = aSampleTime - mLastSampleTime;
   mLastSampleTime = aSampleTime;
 
   if (mAnimation) {
     bool continueAnimation = mAnimation->Sample(mFrameMetrics, sampleTimeDelta);
     *aOutDeferredTasks = mAnimation->TakeDeferredTasks();
     if (continueAnimation) {
-      if (mPaintThrottler.TimeSinceLastRequest(aSampleTime) >
+      if (mPaintThrottler->TimeSinceLastRequest(aSampleTime) >
           mAnimation->mRepaintInterval) {
         RequestContentRepaint();
       }
     } else {
       mAnimation = nullptr;
       SetState(NOTHING);
       SendAsyncScrollEvent();
       RequestContentRepaint();
@@ -2839,17 +2842,24 @@ Matrix4x4 AsyncPanZoomController::GetCur
 Matrix4x4 AsyncPanZoomController::GetTransformToLastDispatchedPaint() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   LayerPoint scrollChange =
     (mLastContentPaintMetrics.GetScrollOffset() - mLastDispatchedPaintMetrics.GetScrollOffset())
     * mLastContentPaintMetrics.GetDevPixelsPerCSSPixel()
     * mLastContentPaintMetrics.GetCumulativeResolution();
 
-  gfxSize zoomChange = mLastContentPaintMetrics.GetZoom() / mLastDispatchedPaintMetrics.GetZoom();
+  // We're interested in the async zoom change. Factor out the content scale
+  // that may change when dragging the window to a monitor with a different
+  // content scale.
+  LayoutDeviceToParentLayerScale2D lastContentZoom =
+    mLastContentPaintMetrics.GetZoom() / mLastContentPaintMetrics.GetDevPixelsPerCSSPixel();
+  LayoutDeviceToParentLayerScale2D lastDispatchedZoom =
+    mLastDispatchedPaintMetrics.GetZoom() / mLastDispatchedPaintMetrics.GetDevPixelsPerCSSPixel();
+  gfxSize zoomChange = lastContentZoom / lastDispatchedZoom;
 
   return Matrix4x4::Translation(scrollChange.x, scrollChange.y, 0).
            PostScale(zoomChange.width, zoomChange.height, 1);
 }
 
 bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
@@ -2883,17 +2893,17 @@ void AsyncPanZoomController::NotifyLayer
   LogRendertraceRect(GetGuid(), "page", "brown", aLayerMetrics.GetScrollableRect());
   LogRendertraceRect(GetGuid(), "painted displayport", "lightgreen",
     aLayerMetrics.GetDisplayPort() + aLayerMetrics.GetScrollOffset());
   if (!aLayerMetrics.GetCriticalDisplayPort().IsEmpty()) {
     LogRendertraceRect(GetGuid(), "painted critical displayport", "darkgreen",
       aLayerMetrics.GetCriticalDisplayPort() + aLayerMetrics.GetScrollOffset());
   }
 
-  mPaintThrottler.TaskComplete(GetFrameTime());
+  mPaintThrottler->TaskComplete(GetFrameTime());
   bool needContentRepaint = false;
   bool viewportUpdated = false;
   if (FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().width, mFrameMetrics.GetCompositionBounds().width) &&
       FuzzyEqualsAdditive(aLayerMetrics.GetCompositionBounds().height, mFrameMetrics.GetCompositionBounds().height)) {
     // Remote content has sync'd up to the composition geometry
     // change, so we can accept the viewport it's calculated.
     if (mFrameMetrics.GetViewport().width != aLayerMetrics.GetViewport().width ||
         mFrameMetrics.GetViewport().height != aLayerMetrics.GetViewport().height) {
@@ -2911,18 +2921,18 @@ void AsyncPanZoomController::NotifyLayer
         && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
 
   bool smoothScrollRequested = aLayerMetrics.GetDoSmoothScroll()
        && (aLayerMetrics.GetScrollGeneration() != mFrameMetrics.GetScrollGeneration());
 
   if (aIsFirstPaint || isDefault) {
     // Initialize our internal state to something sane when the content
     // that was just painted is something we knew nothing about previously
-    mPaintThrottler.ClearHistory();
-    mPaintThrottler.SetMaxDurations(gfxPrefs::APZNumPaintDurationSamples());
+    mPaintThrottler->ClearHistory();
+    mPaintThrottler->SetMaxDurations(gfxPrefs::APZNumPaintDurationSamples());
 
     CancelAnimation();
 
     mFrameMetrics = aLayerMetrics;
     mLastDispatchedPaintMetrics = aLayerMetrics;
     ShareCompositorFrameMetrics();
 
     if (mFrameMetrics.GetDisplayPortMargins() != ScreenMargin()) {
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -98,16 +98,17 @@ public:
    *       distance, but it's the closest thing we currently have.
    */
   static ScreenCoord GetTouchStartTolerance();
 
   AsyncPanZoomController(uint64_t aLayersId,
                          APZCTreeManager* aTreeManager,
                          const nsRefPtr<InputQueue>& aInputQueue,
                          GeckoContentController* aController,
+                         TaskThrottler* aPaintThrottler,
                          GestureBehavior aGestures = DEFAULT_GESTURES);
 
   // --------------------------------------------------------------------------
   // These methods must only be called on the gecko thread.
   //
 
   /**
    * Read the various prefs and do any global initialization for all APZC instances.
@@ -379,24 +380,21 @@ public:
 
   // Return whether or not there is room to scroll this APZC
   // in the given direction.
   bool CanScroll(Layer::ScrollDirection aDirection) const;
 
   void NotifyMozMouseScrollEvent(const nsString& aString) const;
 
 protected:
-  // These functions are protected virtual so test code can override them.
+  // Protected destructor, to discourage deletion outside of Release():
+  virtual ~AsyncPanZoomController();
 
   // Returns the cached current frame time.
-  virtual TimeStamp GetFrameTime() const;
-
-protected:
-  // Protected destructor, to discourage deletion outside of Release():
-  virtual ~AsyncPanZoomController();
+  TimeStamp GetFrameTime() const;
 
   /**
    * Helper method for touches beginning. Sets everything up for panning and any
    * multitouch gestures.
    */
   nsEventStatus OnTouchStart(const MultiTouchInput& aEvent);
 
   /**
@@ -646,25 +644,32 @@ protected:
   void OnTouchEndOrCancel();
 
   // This is called by OverscrollAnimation to notify us when the overscroll
   // animation is ending.
   void OverscrollAnimationEnding();
 
   uint64_t mLayersId;
   nsRefPtr<CompositorParent> mCompositorParent;
-  TaskThrottler mPaintThrottler;
+  nsRefPtr<TaskThrottler> mPaintThrottler;
 
   /* Access to the following two fields is protected by the mRefPtrMonitor,
      since they are accessed on the UI thread but can be cleared on the
      compositor thread. */
   nsRefPtr<GeckoContentController> mGeckoContentController;
   nsRefPtr<GestureEventListener> mGestureEventListener;
   mutable Monitor mRefPtrMonitor;
 
+  // This is a raw pointer to avoid introducing a reference cycle between
+  // AsyncPanZoomController and APZCTreeManager. Since these objects don't
+  // live on the main thread, we can't use the cycle collector with them.
+  // The APZCTreeManager owns the lifetime of the APZCs, so nulling this
+  // pointer out in Destroy() will prevent accessing deleted memory.
+  Atomic<APZCTreeManager*> mTreeManager;
+
   /* Utility functions that return a addrefed pointer to the corresponding fields. */
   already_AddRefed<GeckoContentController> GetGeckoContentController() const;
   already_AddRefed<GestureEventListener> GetGestureEventListener() const;
 
   // If we are sharing our frame metrics with content across processes
   bool mSharingFrameMetricsAcrossProcesses;
   /* Utility function to get the Compositor with which we share the FrameMetrics.
      This function is only callable from the compositor thread. */
@@ -923,22 +928,18 @@ public:
   }
 
   bool IsRootContent() const {
     ReentrantMonitorAutoEnter lock(mMonitor);
     return mFrameMetrics.IsRootContent();
   }
 
 private:
-  // This is a raw pointer to avoid introducing a reference cycle between
-  // AsyncPanZoomController and APZCTreeManager. Since these objects don't
-  // live on the main thread, we can't use the cycle collector with them.
-  // The APZCTreeManager owns the lifetime of the APZCs, so nulling this
-  // pointer out in Destroy() will prevent accessing deleted memory.
-  Atomic<APZCTreeManager*> mTreeManager;
+  // |mTreeManager| belongs in this section but it's declaration is a bit
+  // further above due to initialization-order constraints.
 
   nsRefPtr<AsyncPanZoomController> mParent;
 
 
   /* ===================================================================
    * The functions and members in this section are used for scrolling,
    * including handing off scroll to another APZC, and overscrolling.
    */
--- a/gfx/layers/apz/src/TaskThrottler.cpp
+++ b/gfx/layers/apz/src/TaskThrottler.cpp
@@ -1,40 +1,64 @@
 /* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8; -*- */
 /* vim: set sw=2 sts=2 ts=8 et 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/. */
 
 #include "TaskThrottler.h"
 
+#include "mozilla/layers/APZThreadUtils.h"  // for NewTimerCallback
+#include "nsComponentManagerUtils.h"        // for do_CreateInstance
+#include "nsITimer.h"
+
 namespace mozilla {
 namespace layers {
 
 TaskThrottler::TaskThrottler(const TimeStamp& aTimeStamp, const TimeDuration& aMaxWait)
   : mOutstanding(false)
   , mQueuedTask(nullptr)
   , mStartTime(aTimeStamp)
   , mMaxWait(aMaxWait)
   , mMean(1)
+  , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
 { }
 
+TaskThrottler::~TaskThrottler()
+{
+  mTimer->Cancel();
+}
+
 void
 TaskThrottler::PostTask(const tracked_objects::Location& aLocation,
                         UniquePtr<CancelableTask> aTask, const TimeStamp& aTimeStamp)
 {
   aTask->SetBirthPlace(aLocation);
 
   if (mOutstanding) {
     if (mQueuedTask) {
       mQueuedTask->Cancel();
       mQueuedTask = nullptr;
+      mTimer->Cancel();
     }
     if (TimeSinceLastRequest(aTimeStamp) < mMaxWait) {
       mQueuedTask = Move(aTask);
+      // Make sure the queued task is sent after mMaxWait time elapses,
+      // even if we don't get a TaskComplete() until then.
+      TimeDuration timeout = mMaxWait - TimeSinceLastRequest(aTimeStamp);
+      TimeStamp timeoutTime = mStartTime + mMaxWait;
+      nsRefPtr<TaskThrottler> refPtrThis = this;
+      mTimer->InitWithCallback(NewTimerCallback(
+          [refPtrThis, timeoutTime]()
+          {
+            if (refPtrThis->mQueuedTask) {
+              refPtrThis->RunQueuedTask(timeoutTime);
+            }
+          }),
+          timeout.ToMilliseconds(), nsITimer::TYPE_ONE_SHOT);
       return;
     }
     // we've been waiting for more than the max-wait limit, so just fall through
     // and send the new task already.
   }
 
   mStartTime = aTimeStamp;
   aTask->Run();
@@ -46,30 +70,39 @@ TaskThrottler::TaskComplete(const TimeSt
 {
   if (!mOutstanding) {
     return;
   }
 
   mMean.insert(aTimeStamp - mStartTime);
 
   if (mQueuedTask) {
-    mStartTime = aTimeStamp;
-    mQueuedTask->Run();
-    mQueuedTask = nullptr;
+    RunQueuedTask(aTimeStamp);
+    mTimer->Cancel();
   } else {
     mOutstanding = false;
   }
 }
 
 void
+TaskThrottler::RunQueuedTask(const TimeStamp& aTimeStamp)
+{
+  mStartTime = aTimeStamp;
+  mQueuedTask->Run();
+  mQueuedTask = nullptr;
+
+}
+
+void
 TaskThrottler::CancelPendingTask()
 {
   if (mQueuedTask) {
     mQueuedTask->Cancel();
     mQueuedTask = nullptr;
+    mTimer->Cancel();
   }
 }
 
 TimeDuration
 TaskThrottler::TimeSinceLastRequest(const TimeStamp& aTimeStamp)
 {
   return aTimeStamp - mStartTime;
 }
--- a/gfx/layers/apz/src/TaskThrottler.h
+++ b/gfx/layers/apz/src/TaskThrottler.h
@@ -8,18 +8,22 @@
 #define mozilla_dom_TaskThrottler_h
 
 #include <stdint.h>                     // for uint32_t
 #include "base/task.h"                  // for CancelableTask
 #include "mozilla/TimeStamp.h"          // for TimeDuration, TimeStamp
 #include "mozilla/RollingMean.h"        // for RollingMean
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "mozilla/UniquePtr.h"          // for UniquePtr
+#include "nsCOMPtr.h"                   // for nsCOMPtr
+#include "nsISupportsImpl.h"            // for NS_INLINE_DECL_THREADSAFE_REFCOUNTING
 #include "nsTArray.h"                   // for nsTArray
 
+class nsITimer;
+
 namespace tracked_objects {
 class Location;
 } // namespace tracked_objects
 
 namespace mozilla {
 namespace layers {
 
 /** The TaskThrottler prevents update event overruns. It is used in cases where
@@ -40,16 +44,18 @@ namespace layers {
  * process is painting you might get several updates from the UI thread but when
  * the paint is complete you want to send the most recent.
  */
 
 class TaskThrottler {
 public:
   TaskThrottler(const TimeStamp& aTimeStamp, const TimeDuration& aMaxWait);
 
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskThrottler)
+
   /** Post a task to be run as soon as there are no outstanding tasks, or
    * post it immediately if it has been more than the max-wait time since
    * the last task was posted.
    *
    * @param aLocation Use the macro FROM_HERE
    * @param aTask     Ownership of this object is transferred to TaskThrottler
    *                  which will delete it when it is either run or becomes
    *                  obsolete or the TaskThrottler is destructed.
@@ -102,14 +108,18 @@ public:
   }
 
 private:
   bool mOutstanding;
   UniquePtr<CancelableTask> mQueuedTask;
   TimeStamp mStartTime;
   TimeDuration mMaxWait;
   RollingMean<TimeDuration, TimeDuration> mMean;
+  nsCOMPtr<nsITimer> mTimer;
+
+  ~TaskThrottler();
+  void RunQueuedTask(const TimeStamp& aTimeStamp);
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_dom_TaskThrottler_h
--- a/gfx/layers/apz/util/APZThreadUtils.cpp
+++ b/gfx/layers/apz/util/APZThreadUtils.cpp
@@ -76,10 +76,12 @@ APZThreadUtils::RunOnControllerThread(Ta
     aTask->Run();
     delete aTask;
   } else {
     sControllerThread->PostTask(FROM_HERE, aTask);
   }
 #endif
 }
 
+NS_IMPL_ISUPPORTS(GenericTimerCallbackBase, nsITimerCallback)
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/util/APZThreadUtils.h
+++ b/gfx/layers/apz/util/APZThreadUtils.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_APZThreadUtils_h
 #define mozilla_layers_APZThreadUtils_h
 
 #include "base/message_loop.h"
+#include "nsITimer.h"
 
 class Task;
 
 namespace mozilla {
 namespace layers {
 
 class APZThreadUtils
 {
@@ -46,12 +47,52 @@ public:
   /**
    * Run the given task on the APZ "controller thread" for this platform. If
    * this function is called from the controller thread itself then the task is
    * run immediately without getting queued.
    */
   static void RunOnControllerThread(Task* aTask);
 };
 
+// A base class for GenericTimerCallback<Function>.
+// This is necessary because NS_IMPL_ISUPPORTS doesn't work for a class
+// template.
+class GenericTimerCallbackBase : public nsITimerCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+protected:
+  virtual ~GenericTimerCallbackBase() {}
+};
+
+// An nsITimerCallback implementation that can be used with any function
+// object that's callable with no arguments.
+template <typename Function>
+class GenericTimerCallback final : public GenericTimerCallbackBase
+{
+public:
+  explicit GenericTimerCallback(const Function& aFunction) : mFunction(aFunction) {}
+
+  NS_IMETHODIMP Notify(nsITimer*) override
+  {
+    mFunction();
+    return NS_OK;
+  }
+private:
+  Function mFunction;
+};
+
+// Convenience function for constructing a GenericTimerCallback.
+// Returns a raw pointer, suitable for passing directly as an argument to
+// nsITimer::InitWithCallback(). The intention is to enable the following
+// terse inline usage:
+//    timer->InitWithCallback(NewTimerCallback([](){ ... }), delay);
+template <typename Function>
+GenericTimerCallback<Function>* NewTimerCallback(const Function& aFunction)
+{
+  return new GenericTimerCallback<Function>(aFunction);
+}
+
 } // namespace layers
 } // namespace mozilla
 
 #endif /* mozilla_layers_APZThreadUtils_h */
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -437,17 +437,17 @@ gfxShmSharedReadLock::GetReadCount() {
   }
   ShmReadLockInfo* info = GetShmReadLockInfoPtr();
   return info->readCount;
 }
 
 class TileExpiry final : public nsExpirationTracker<TileClient, 3>
 {
   public:
-    TileExpiry() : nsExpirationTracker<TileClient, 3>(1000) {}
+    TileExpiry() : nsExpirationTracker<TileClient, 3>(1000, "TileExpiry") {}
 
     static void AddTile(TileClient* aTile)
     {
       if (!sTileExpiry) {
         sTileExpiry = MakeUnique<TileExpiry>();
       }
 
       sTileExpiry->AddObject(aTile);
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -142,32 +142,43 @@ private:
   // The following array is sorted by timestamp (tasks are inserted in order by
   // timestamp).
   nsTArray<std::pair<Task*, TimeStamp>> mTaskQueue;
   TimeStamp mTime;
 };
 
 class TestAPZCTreeManager : public APZCTreeManager {
 public:
-  TestAPZCTreeManager() {}
+  explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {}
 
   nsRefPtr<InputQueue> GetInputQueue() const {
     return mInputQueue;
   }
 
 protected:
-  AsyncPanZoomController* MakeAPZCInstance(uint64_t aLayersId, GeckoContentController* aController) override;
+  AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId,
+                                          GeckoContentController* aController,
+                                          TaskThrottler* aPaintThrottler) override;
+
+  TimeStamp GetFrameTime() override {
+    return mcc->Time();
+  }
+
+private:
+  nsRefPtr<MockContentControllerDelayed> mcc;
 };
 
 class TestAsyncPanZoomController : public AsyncPanZoomController {
 public:
   TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc,
                              TestAPZCTreeManager* aTreeManager,
+                             TaskThrottler* aPaintThrottler,
                              GestureBehavior aBehavior = DEFAULT_GESTURES)
-    : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(), aMcc, aBehavior)
+    : AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(),
+        aMcc, aPaintThrottler, aBehavior)
     , mWaitForMainThread(false)
     , mcc(aMcc)
   {}
 
   nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) {
     // This is a function whose signature matches exactly the ReceiveInputEvent
     // on APZCTreeManager. This allows us to templates for functions like
     // TouchDown, TouchUp, etc so that we can reuse the code for dispatching
@@ -234,35 +245,33 @@ public:
       aOutTransform, aScrollOffset);
     return ret;
   }
 
   void SetWaitForMainThread() {
     mWaitForMainThread = true;
   }
 
-  TimeStamp GetFrameTime() const {
-    return mcc->Time();
-  }
-
   static TimeStamp GetStartupTime() {
     static TimeStamp sStartupTime = TimeStamp::Now();
     return sStartupTime;
   }
 
 private:
   bool mWaitForMainThread;
   MockContentControllerDelayed* mcc;
 };
 
 AsyncPanZoomController*
-TestAPZCTreeManager::MakeAPZCInstance(uint64_t aLayersId, GeckoContentController* aController)
+TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
+                                     GeckoContentController* aController,
+                                     TaskThrottler* aPaintThrottler)
 {
   MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController);
-  return new TestAsyncPanZoomController(aLayersId, mcc, this,
+  return new TestAsyncPanZoomController(aLayersId, mcc, this, aPaintThrottler,
       AsyncPanZoomController::USE_GESTURE_DETECTOR);
 }
 
 static FrameMetrics
 TestFrameMetrics()
 {
   FrameMetrics fm;
 
@@ -284,18 +293,19 @@ public:
 protected:
   virtual void SetUp()
   {
     gfxPrefs::GetSingleton();
     APZThreadUtils::SetThreadAssertionsEnabled(false);
     APZThreadUtils::SetControllerThread(MessageLoop::current());
 
     mcc = new NiceMock<MockContentControllerDelayed>();
-    tm = new TestAPZCTreeManager();
-    apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior);
+    mPaintThrottler = new TaskThrottler(mcc->Time(), TimeDuration::FromMilliseconds(500));
+    tm = new TestAPZCTreeManager(mcc);
+    apzc = new TestAsyncPanZoomController(0, mcc, tm, mPaintThrottler, mGestureBehavior);
     apzc->SetFrameMetrics(TestFrameMetrics());
   }
 
   /**
    * Get the APZC's scroll range in CSS pixels.
    */
   CSSRect GetScrollRange() const
   {
@@ -368,16 +378,17 @@ protected:
     EXPECT_TRUE(recoveredFromOverscroll);
     apzc->AssertStateIsReset();
   }
 
   void TestOverscroll();
 
   AsyncPanZoomController::GestureBehavior mGestureBehavior;
   nsRefPtr<MockContentControllerDelayed> mcc;
+  nsRefPtr<TaskThrottler> mPaintThrottler;
   nsRefPtr<TestAPZCTreeManager> tm;
   nsRefPtr<TestAsyncPanZoomController> apzc;
 };
 
 class APZCGestureDetectorTester : public APZCBasicTester {
 public:
   APZCGestureDetectorTester()
     : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
@@ -962,17 +973,17 @@ TEST_F(APZCBasicTester, ComplexTransform
   // CSS transforms, the two layers are the same size in screen
   // pixels.
   //
   // The screen itself is 24x24 in screen pixels (therefore 4x4 in
   // CSS pixels). The displayport is 1 extra CSS pixel on all
   // sides.
 
   nsRefPtr<TestAsyncPanZoomController> childApzc =
-      new TestAsyncPanZoomController(0, mcc, tm);
+      new TestAsyncPanZoomController(0, mcc, tm, mPaintThrottler);
 
   const char* layerTreeSyntax = "c(c)";
   // LayerID                     0 1
   nsIntRegion layerVisibleRegion[] = {
     nsIntRegion(IntRect(0, 0, 300, 300)),
     nsIntRegion(IntRect(0, 0, 150, 300)),
   };
   Matrix4x4 transforms[] = {
@@ -1865,17 +1876,17 @@ TEST_F(APZCGestureDetectorTester, TapFol
 class APZCTreeManagerTester : public ::testing::Test {
 protected:
   virtual void SetUp() {
     gfxPrefs::GetSingleton();
     APZThreadUtils::SetThreadAssertionsEnabled(false);
     APZThreadUtils::SetControllerThread(MessageLoop::current());
 
     mcc = new NiceMock<MockContentControllerDelayed>();
-    manager = new TestAPZCTreeManager();
+    manager = new TestAPZCTreeManager(mcc);
   }
 
   virtual void TearDown() {
     while (mcc->RunThroughDelayedTasks());
     manager->ClearTree();
   }
 
   /**
@@ -3231,33 +3242,33 @@ private:
   TaskRunMetrics& mMetrics;
 };
 
 class APZTaskThrottlerTester : public ::testing::Test {
 public:
   APZTaskThrottlerTester()
   {
     now = TimeStamp::Now();
-    throttler = MakeUnique<TaskThrottler>(now, TimeDuration::FromMilliseconds(100));
+    throttler = new TaskThrottler(now, TimeDuration::FromMilliseconds(100));
   }
 
 protected:
   TimeStamp Advance(int aMillis = 5)
   {
     now = now + TimeDuration::FromMilliseconds(aMillis);
     return now;
   }
 
   UniquePtr<CancelableTask> NewTask()
   {
     return MakeUnique<MockTask>(metrics);
   }
 
   TimeStamp now;
-  UniquePtr<TaskThrottler> throttler;
+  nsRefPtr<TaskThrottler> throttler;
   TaskRunMetrics metrics;
 };
 
 TEST_F(APZTaskThrottlerTester, BasicTest) {
   // Check that posting the first task runs right away
   throttler->PostTask(FROM_HERE, NewTask(), Advance());         // task 1
   EXPECT_EQ(1, metrics.GetAndClearRunCount());
 
--- a/gfx/tests/gtest/gfxWordCacheTest.cpp
+++ b/gfx/tests/gtest/gfxWordCacheTest.cpp
@@ -31,17 +31,19 @@ static FrameTextRunCache *gTextRuns = nu
 
 /*
  * Cache textruns and expire them after 3*10 seconds of no use.
  */
 class FrameTextRunCache final : public nsExpirationTracker<gfxTextRun,3> {
 public:
   enum { TIMEOUT_SECONDS = 10 };
   FrameTextRunCache()
-    : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
+    : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS * 1000,
+                                        "FrameTextRunCache")
+  {}
   ~FrameTextRunCache() {
     AgeAllGenerations();
   }
 
   void RemoveFromCache(gfxTextRun* aTextRun) {
     if (aTextRun->GetExpirationState()->IsTracked()) {
       RemoveObject(aTextRun);
     }
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -259,17 +259,17 @@ struct BlurCacheData {
  * SourceSurfaces used to draw the blurs.
  *
  * An entry stays in the cache as long as it is used often.
  */
 class BlurCache final : public nsExpirationTracker<BlurCacheData,4>
 {
   public:
     BlurCache()
-      : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS)
+      : nsExpirationTracker<BlurCacheData, 4>(GENERATION_MS, "BlurCache")
     {
     }
 
     virtual void NotifyExpired(BlurCacheData* aObject)
     {
       RemoveObject(aObject);
       mHashEntries.Remove(aObject->mKey);
     }
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -160,17 +160,18 @@ gfxFontCache::Shutdown()
     printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
     printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
     printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
     printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
 #endif
 }
 
 gfxFontCache::gfxFontCache()
-    : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000)
+    : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000,
+                                     "gfxFontCache")
 {
     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     if (obs) {
         obs->AddObserver(new Observer, "memory-pressure", false);
     }
 
 #ifndef RELEASE_BUILD
     // Currently disabled for release builds, due to unexplained crashes
--- a/gfx/thebes/gfxGradientCache.cpp
+++ b/gfx/thebes/gfxGradientCache.cpp
@@ -1,16 +1,16 @@
 /* -*- 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/gfx/2D.h"
 #include "nsTArray.h"
-#include "pldhash.h"
+#include "PLDHashTable.h"
 #include "nsExpirationTracker.h"
 #include "nsClassHashtable.h"
 #include "mozilla/Telemetry.h"
 #include "gfxGradientCache.h"
 #include <time.h>
 
 namespace mozilla {
 namespace gfx {
@@ -117,17 +117,18 @@ struct GradientCacheData {
  * An entry stays in the cache as long as it is used often. As long as a cache
  * entry is in the cache, all the references it has are guaranteed to be valid:
  * the nsStyleRect for the key, the gfxPattern for the value.
  */
 class GradientCache final : public nsExpirationTracker<GradientCacheData,4>
 {
   public:
     GradientCache()
-      : nsExpirationTracker<GradientCacheData, 4>(MAX_GENERATION_MS)
+      : nsExpirationTracker<GradientCacheData,4>(MAX_GENERATION_MS,
+                                                 "GradientCache")
     {
       srand(time(nullptr));
       mTimerPeriod = rand() % MAX_GENERATION_MS + 1;
       Telemetry::Accumulate(Telemetry::GRADIENT_RETENTION_TIME, mTimerPeriod);
     }
 
     virtual void NotifyExpired(GradientCacheData* aObject)
     {
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -936,17 +936,18 @@ private:
     } else {
       mExpirationTracker.MarkUsed(aSurface);
     }
   }
 
   struct SurfaceTracker : public nsExpirationTracker<CachedSurface, 2>
   {
     explicit SurfaceTracker(uint32_t aSurfaceCacheExpirationTimeMS)
-      : nsExpirationTracker<CachedSurface, 2>(aSurfaceCacheExpirationTimeMS)
+      : nsExpirationTracker<CachedSurface, 2>(aSurfaceCacheExpirationTimeMS,
+                                              "SurfaceTracker")
     { }
 
   protected:
     virtual void NotifyExpired(CachedSurface* aSurface) override
     {
       if (sInstance) {
         MutexAutoLock lock(sInstance->GetMutex());
         sInstance->Remove(aSurface);
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -1064,17 +1064,18 @@ class imgCacheExpirationTracker final
 public:
   imgCacheExpirationTracker();
 
 protected:
   void NotifyExpired(imgCacheEntry* entry);
 };
 
 imgCacheExpirationTracker::imgCacheExpirationTracker()
- : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000)
+ : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000,
+                                         "imgCacheExpirationTracker")
 { }
 
 void
 imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry)
 {
   // Hold on to a reference to this entry, because the expiration tracker
   // mechanism doesn't.
   nsRefPtr<imgCacheEntry> kungFuDeathGrip(entry);
--- a/intl/strres/nsStringBundleTextOverride.cpp
+++ b/intl/strres/nsStringBundleTextOverride.cpp
@@ -148,22 +148,22 @@ nsStringBundleTextOverride::Init()
     nsCOMPtr<nsIURI> uri;
     rv = NS_NewURI(getter_AddRefs(uri), customStringsURLSpec);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIChannel> channel;
     rv = NS_NewChannel(getter_AddRefs(channel),
                        uri,
                        nsContentUtils::GetSystemPrincipal(),
-                       nsILoadInfo::SEC_NORMAL,
+                       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                        nsIContentPolicy::TYPE_OTHER);
 
     NS_ENSURE_SUCCESS(rv, rv);
     nsCOMPtr<nsIInputStream> in;
-    rv = channel->Open(getter_AddRefs(in));
+    rv = channel->Open2(getter_AddRefs(in));
     NS_ENSURE_SUCCESS(rv, rv);
 
     static NS_DEFINE_CID(kPersistentPropertiesCID, NS_IPERSISTENTPROPERTIES_CID);
     mValues = do_CreateInstance(kPersistentPropertiesCID, &rv);
     if (NS_FAILED(rv)) return rv;
 
     rv = mValues->Load(in);
 
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -171,17 +171,17 @@ var ignoreFunctions = {
     "void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true,
     "void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true,
 
     // And these are workarounds to avoid even more analysis work,
     // which would sadly still be needed even with bug 898815.
     "void js::AutoCompartment::AutoCompartment(js::ExclusiveContext*, JSCompartment*)": true,
 
     // The nsScriptNameSpaceManager functions can't actually GC.  They
-    // just use a pldhash which has function pointers, which makes the
+    // just use a PLDHashTable which has function pointers, which makes the
     // analysis think maybe they can.
     "nsGlobalNameStruct* nsScriptNameSpaceManager::LookupNavigatorName(nsAString_internal*)": true,
     "nsGlobalNameStruct* nsScriptNameSpaceManager::LookupName(nsAString_internal*, uint16**)": true,
 
     // Similar to heap snapshot mock classes, and GTests below. This posts a
     // synchronous runnable when a GTest fails, and we are pretty sure that the
     // particular runnable it posts can't even GC, but the analysis isn't
     // currently smart enough to determine that. In either case, this is (a)
--- a/js/src/doc/Debugger/Debugger.Script.md
+++ b/js/src/doc/Debugger/Debugger.Script.md
@@ -197,26 +197,44 @@ methods of other kinds of objects.
 
     Calling `getAllColumnOffsets()` on that code might yield an array like this:
 
     ```language-js
     [{ lineNumber: 0, columnNumber: 0, offset: 0 },
      { lineNumber: 1, columnNumber: 5, offset: 5 },
      { lineNumber: 1, columnNumber: 10, offset: 20 },
      { lineNumber: 3, columnNumber: 4, offset: 10 }]
+    ```
 
 <code>getLineOffsets(<i>line</i>)</code>
 :   Return an array of bytecode instruction offsets representing the entry
     points to source line <i>line</i>. If the script contains no executable
     code at that line, the array returned is empty.
 
 <code>getOffsetLine(<i>offset</i>)</code>
 :   Return the source code line responsible for the bytecode at
     <i>offset</i> in this script.
 
+`getOffsetsCoverage()`:
+:   Return `null` or an array which contains informations about the coverage of
+    all opcodes. The elements of the array are objects, each of which describes
+    a single opcode, and contains the following properties:
+
+    * lineNumber: the line number of the current opcode.
+
+    * columnNumber: the column number of the current opcode.
+
+    * offset: the bytecode instruction offset of the current opcode.
+
+    * count: the number of times the current opcode got executed.
+
+    If this script has no coverage, or if it is not instrumented, then this
+    function will return `null`. To ensure that the debuggee is instrumented,
+    the flag `Debugger.collectCoverageInfo` should be set to `true`.
+
 `getChildScripts()`
 :   Return a new array whose elements are Debugger.Script objects for each
     function and each generator expression in this script. Only direct
     children are included; nested children can be reached by walking the
     tree.
 
 <code>setBreakpoint(<i>offset</i>, <i>handler</i>)</code>
 :   Set a breakpoint at the bytecode instruction at <i>offset</i> in this
--- a/js/src/doc/Debugger/Debugger.md
+++ b/js/src/doc/Debugger/Debugger.md
@@ -32,16 +32,34 @@ its prototype:
     ahead-of-time asm.js compiler and forces asm.js code to run as normal
     JavaScript. This is an accessor property with a getter and setter. It is
     initially `false` in a freshly created `Debugger` instance.
 
     Setting this flag to `true` is intended for uses of subsystems of the
     Debugger API (e.g, [`Debugger.Source`][source]) for purposes other than
     step debugging a target JavaScript program.
 
+`collectCoverageInfo`
+:   A boolean value indicating whether code coverage should be enabled inside
+    each debuggee of this `Debugger` instance. Changing this flag value will
+    recompile all JIT code to add or remove code coverage
+    instrumentation. Changing this flag when any frame of the debuggee is
+    currently active on the stack will produce an exception.
+
+    Setting this to `true` enables code coverage instrumentation, which can be
+    accessed via the [`Debugger.Script`][script] `getOffsetsCoverage`
+    function. In some cases, the code coverage might expose information which
+    pre-date the modification of this flag. Code coverage reports are monotone,
+    thus one can take a snapshot when the Debugger is enabled, and output the
+    difference.
+
+    Setting this to `false` prevents this `Debugger` instance from requiring any
+    code coverage instrumentation, but it does not guarantee that the
+    instrumentation is not present.
+
 `uncaughtExceptionHook`
 :   Either `null` or a function that SpiderMonkey calls when a call to a
     debug event handler, breakpoint handler, watchpoint handler, or similar
     function throws some exception, which we refer to as
     <i>debugger-exception</i> here. Exceptions thrown in the debugger are
     not propagated to debuggee code; instead, SpiderMonkey calls this
     function, passing <i>debugger-exception</i> as its sole argument and
     the `Debugger` instance as the `this` value. This function should
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2938,16 +2938,27 @@ IsEscapeFreeStringLiteral(const TokenPos
     /*
      * If the string's length in the source code is its length as a value,
      * accounting for the quotes, then it must not contain any escape
      * sequences or line continuations.
      */
     return pos.begin + str->length() + 2 == pos.end;
 }
 
+template <typename ParseHandler>
+bool
+Parser<ParseHandler>::checkUnescapedName()
+{
+    if (!tokenStream.currentToken().nameContainsEscape())
+        return true;
+
+    report(ParseError, false, null(), JSMSG_ESCAPED_KEYWORD);
+    return false;
+}
+
 template <>
 bool
 Parser<SyntaxParseHandler>::asmJS(Node list)
 {
     // While asm.js could technically be validated and compiled during syntax
     // parsing, we have no guarantee that some later JS wouldn't abort the
     // syntax parse and cause us to re-parse (and re-compile) the asm.js module.
     // For simplicity, unconditionally abort the syntax parse when "use asm" is
@@ -4199,19 +4210,37 @@ Parser<ParseHandler>::variables(YieldHan
                 return null();
             if (data.isConst())
                 handler.setFlag(pn2, PND_CONST);
             data.setNameNode(pn2);
 
             handler.addList(pn, pn2);
 
             bool matched;
-            if (!tokenStream.matchToken(&matched, TOK_ASSIGN))
+            // The '=' context after a variable name in a declaration is an
+            // opportunity for ASI, and thus for the next token to start an
+            // ExpressionStatement:
+            //
+            //  var foo   // VariableDeclaration
+            //  /bar/g;   // ExpressionStatement
+            //
+            // Therefore we must get the token here as Operand.
+            if (!tokenStream.matchToken(&matched, TOK_ASSIGN, TokenStream::Operand))
                 return null();
-            if (matched) {
+            if (!matched) {
+                tokenStream.addModifierException(TokenStream::NoneIsOperand);
+
+                if (data.isConst() && location == NotInForInit) {
+                    report(ParseError, false, null(), JSMSG_BAD_CONST_DECL);
+                    return null();
+                }
+
+                if (!data.bind(name, this))
+                    return null();
+            } else {
                 if (psimple)
                     *psimple = false;
 
                 // In ES6, lexical bindings may not be accessed until
                 // initialized. So a declaration of the form |let x = x| results
                 // in a ReferenceError, as the 'x' on the RHS is accessing the let
                 // binding before it is initialized.
                 //
@@ -4250,24 +4279,16 @@ Parser<ParseHandler>::variables(YieldHan
 
                 if (performAssignment) {
                     if (!bindBeforeInitializer && !data.bind(name, this))
                         return null();
 
                     if (!handler.finishInitializerAssignment(pn2, init, data.op()))
                         return null();
                 }
-            } else {
-                if (data.isConst() && location == NotInForInit) {
-                    report(ParseError, false, null(), JSMSG_BAD_CONST_DECL);
-                    return null();
-                }
-
-                if (!data.bind(name, this))
-                    return null();
             }
 
             handler.setEndPosition(pn, pn2);
         } while (false);
 
         bool matched;
         if (!tokenStream.matchToken(&matched, TOK_COMMA))
             return null();
@@ -4523,34 +4544,34 @@ Parser<FullParseHandler>::namedImportsOr
             // If the next token is a keyword, the previous call to
             // peekToken matched it as a TOK_NAME, and put it in the
             // lookahead buffer, so this call will match keywords as well.
             MUST_MATCH_TOKEN_MOD(TOK_NAME, TokenStream::KeywordIsName, JSMSG_NO_IMPORT_NAME);
             Node importName = newName(tokenStream.currentName());
             if (!importName)
                 return false;
 
-            if (!tokenStream.getToken(&tt))
+            bool foundAs;
+            if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as))
                 return false;
 
-            if (tt == TOK_NAME && tokenStream.currentName() == context->names().as) {
+            if (foundAs) {
                 MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
             } else {
                 // Keywords cannot be bound to themselves, so an import name
                 // that is a keyword is a syntax error if it is not followed
                 // by the keyword 'as'.
                 // See the ImportSpecifier production in ES6 section 15.2.2.
                 if (IsKeyword(importName->name())) {
                     JSAutoByteString bytes;
                     if (!AtomToPrintableString(context, importName->name(), &bytes))
                         return false;
                     report(ParseError, false, null(), JSMSG_AS_AFTER_RESERVED_WORD, bytes.ptr());
                     return false;
                 }
-                tokenStream.ungetToken();
             }
 
             Node bindingName = newBoundImportForCurrentName();
             if (!bindingName)
                 return false;
 
             Node importSpec = handler.newBinary(PNK_IMPORT_SPEC, importName, bindingName);
             if (!importSpec)
@@ -4574,16 +4595,19 @@ Parser<FullParseHandler>::namedImportsOr
         if (!tokenStream.getToken(&tt))
             return false;
 
         if (tt != TOK_NAME || tokenStream.currentName() != context->names().as) {
             report(ParseError, false, null(), JSMSG_AS_AFTER_IMPORT_STAR);
             return false;
         }
 
+        if (!checkUnescapedName())
+            return false;
+
         MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
 
         Node importName = newName(context->names().star);
         if (!importName)
             return null();
 
         Node bindingName = newBoundImportForCurrentName();
         if (!bindingName)
@@ -4670,16 +4694,19 @@ Parser<ParseHandler>::importDeclaration(
         if (!tokenStream.getToken(&tt))
             return null();
 
         if (tt != TOK_NAME || tokenStream.currentName() != context->names().from) {
             report(ParseError, false, null(), JSMSG_FROM_AFTER_IMPORT_CLAUSE);
             return null();
         }
 
+        if (!checkUnescapedName())
+            return null();
+
         MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
     } else if (tt == TOK_STRING) {
         // Handle the form |import 'a'| by leaving the list empty. This is
         // equivalent to |import {} from 'a'|.
         importSpecSet->pn_pos.end = importSpecSet->pn_pos.begin;
     } else {
         report(ParseError, false, null(), JSMSG_DECLARATION_AFTER_IMPORT);
         return null();
@@ -4747,104 +4774,149 @@ Parser<FullParseHandler>::exportDeclarat
     }
 
     uint32_t begin = pos().begin;
 
     Node kid;
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return null();
-    bool isExportStar = tt == TOK_MUL;
     switch (tt) {
-      case TOK_LC:
-      case TOK_MUL:
+      case TOK_LC: {
         kid = handler.newList(PNK_EXPORT_SPEC_LIST);
         if (!kid)
             return null();
 
-        if (tt == TOK_LC) {
-            while (true) {
-                // Handle the forms |export {}| and |export { ..., }| (where ...
-                // is non empty), by escaping the loop early if the next token
-                // is }.
-                if (!tokenStream.peekToken(&tt))
-                    return null();
-                if (tt == TOK_RC)
-                    break;
-
-                MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
-                Node bindingName = newName(tokenStream.currentName());
-                if (!bindingName)
-                    return null();
-
-                if (!tokenStream.getToken(&tt))
+        while (true) {
+            // Handle the forms |export {}| and |export { ..., }| (where ...
+            // is non empty), by escaping the loop early if the next token
+            // is }.
+            if (!tokenStream.peekToken(&tt))
+                return null();
+            if (tt == TOK_RC)
+                break;
+
+            MUST_MATCH_TOKEN(TOK_NAME, JSMSG_NO_BINDING_NAME);
+            Node bindingName = newName(tokenStream.currentName());
+            if (!bindingName)
+                return null();
+
+            bool foundAs;
+            if (!tokenStream.matchContextualKeyword(&foundAs, context->names().as))
+                return null();
+            if (foundAs) {
+                if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
                     return null();
-                if (tt == TOK_NAME && tokenStream.currentName() == context->names().as) {
-                    if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
-                        return null();
-                    if (tt != TOK_NAME) {
-                        report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME);
-                        return null();
-                    }
-                } else {
-                    tokenStream.ungetToken();
-                }
-                Node exportName = newName(tokenStream.currentName());
-                if (!exportName)
-                    return null();
-
-                if (!addExportName(exportName->pn_atom))
+                if (tt != TOK_NAME) {
+                    report(ParseError, false, null(), JSMSG_NO_EXPORT_NAME);
                     return null();
-
-                Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName);
-                if (!exportSpec)
-                    return null();
-
-                handler.addList(kid, exportSpec);
-
-                bool matched;
-                if (!tokenStream.matchToken(&matched, TOK_COMMA))
-                    return null();
-                if (!matched)
-                    break;
+                }
             }
 
-            MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST);
-        } else {
-            // Handle the form |export *| by adding a special export batch
-            // specifier to the list.
-            Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos());
+            Node exportName = newName(tokenStream.currentName());
+            if (!exportName)
+                return null();
+
+            if (!addExportName(exportName->pn_atom))
+                return null();
+
+            Node exportSpec = handler.newBinary(PNK_EXPORT_SPEC, bindingName, exportName);
             if (!exportSpec)
                 return null();
 
             handler.addList(kid, exportSpec);
-        }
-        if (!tokenStream.getToken(&tt))
-            return null();
-        if (tt == TOK_NAME && tokenStream.currentName() == context->names().from) {
+
+            bool matched;
+            if (!tokenStream.matchToken(&matched, TOK_COMMA))
+                return null();
+            if (!matched)
+                break;
+        }
+
+        MUST_MATCH_TOKEN(TOK_RC, JSMSG_RC_AFTER_EXPORT_SPEC_LIST);
+
+        // Careful!  If |from| follows, even on a new line, it must start a
+        // FromClause:
+        //
+        //   export { x }
+        //   from "foo"; // a single ExportDeclaration
+        //
+        // But if it doesn't, we might have an ASI opportunity in Operand
+        // context, so simply matching a contextual keyword won't work:
+        //
+        //   export { x }   // ExportDeclaration, terminated by ASI
+        //   fro\u006D      // ExpressionStatement, the name "from"
+        //
+        // In that case let MatchOrInsertSemicolon sort out ASI or any
+        // necessary error.
+        TokenKind tt;
+        if (!tokenStream.getToken(&tt, TokenStream::Operand))
+            return null();
+
+        if (tt == TOK_NAME &&
+            tokenStream.currentToken().name() == context->names().from &&
+            !tokenStream.currentToken().nameContainsEscape())
+        {
             MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
 
             Node moduleSpec = stringLiteral();
             if (!moduleSpec)
                 return null();
 
             if (!MatchOrInsertSemicolon(tokenStream))
                 return null();
 
             return handler.newExportFromDeclaration(begin, kid, moduleSpec);
-        } else if (isExportStar) {
-            report(ParseError, false, null(), JSMSG_FROM_AFTER_EXPORT_STAR);
-            return null();
         } else {
             tokenStream.ungetToken();
         }
 
+        if (!MatchOrInsertSemicolon(tokenStream, TokenStream::Operand))
+            return null();
+        break;
+      }
+
+      case TOK_MUL: {
+        kid = handler.newList(PNK_EXPORT_SPEC_LIST);
+        if (!kid)
+            return null();
+
+        // Handle the form |export *| by adding a special export batch
+        // specifier to the list.
+        Node exportSpec = handler.newNullary(PNK_EXPORT_BATCH_SPEC, JSOP_NOP, pos());
+        if (!exportSpec)
+            return null();
+
+        handler.addList(kid, exportSpec);
+
+        if (!tokenStream.getToken(&tt))
+            return null();
+        if (tt == TOK_NAME && tokenStream.currentName() == context->names().from) {
+            if (!checkUnescapedName())
+                return null();
+
+            MUST_MATCH_TOKEN(TOK_STRING, JSMSG_MODULE_SPEC_AFTER_FROM);
+
+            Node moduleSpec = stringLiteral();
+            if (!moduleSpec)
+                return null();
+
+            if (!MatchOrInsertSemicolon(tokenStream))
+                return null();
+
+            return handler.newExportFromDeclaration(begin, kid, moduleSpec);
+        } else {
+            report(ParseError, false, null(), JSMSG_FROM_AFTER_EXPORT_STAR);
+            return null();
+        }
+
         if (!MatchOrInsertSemicolon(tokenStream))
             return null();
         break;
+      }
 
       case TOK_FUNCTION:
         kid = functionStmt(YieldIsKeyword, NameRequired);
         if (!kid)
             return null();
 
         if (!addExportName(kid->pn_funbox->function()->atom()))
             return null();
@@ -5025,21 +5097,20 @@ Parser<ParseHandler>::doWhileStatement(Y
     if (!cond)
         return null();
 
     // The semicolon after do-while is even more optional than most
     // semicolons in JS.  Web compat required this by 2004:
     //   http://bugzilla.mozilla.org/show_bug.cgi?id=238945
     // ES3 and ES5 disagreed, but ES6 conforms to Web reality:
     //   https://bugs.ecmascript.org/show_bug.cgi?id=157
-    bool matched;
-    if (!tokenStream.matchToken(&matched, TOK_SEMI))
-        return null();
-    if (!matched)
-        tokenStream.addModifierException(TokenStream::OperandIsNone);
+    // To parse |do {} while (true) false| correctly, use Operand.
+    bool ignored;
+    if (!tokenStream.matchToken(&ignored, TOK_SEMI, TokenStream::Operand))
+        return null();
     return handler.newDoWhileStatement(body, cond, TokenPos(begin, pos().end));
 }
 
 template <typename ParseHandler>
 typename ParseHandler::Node
 Parser<ParseHandler>::whileStatement(YieldHandling yieldHandling)
 {
     uint32_t begin = pos().begin;
@@ -5057,18 +5128,22 @@ template <typename ParseHandler>
 bool
 Parser<ParseHandler>::matchInOrOf(bool* isForInp, bool* isForOfp)
 {
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return false;
     *isForInp = tt == TOK_IN;
     *isForOfp = tt == TOK_NAME && tokenStream.currentToken().name() == context->names().of;
-    if (!*isForInp && !*isForOfp)
+    if (!*isForInp && !*isForOfp) {
         tokenStream.ungetToken();
+    } else {
+        if (tt == TOK_NAME && !checkUnescapedName())
+            return false;
+    }
     return true;
 }
 
 template <>
 bool
 Parser<FullParseHandler>::isValidForStatementLHS(ParseNode* pn1, JSVersion version,
                                                  bool isForDecl, bool isForEach,
                                                  ParseNodeKind headKind)
@@ -6457,16 +6532,19 @@ Parser<FullParseHandler>::classDefinitio
         if (tt == TOK_SEMI)
             continue;
 
         bool isStatic = false;
         if (tt == TOK_NAME && tokenStream.currentName() == context->names().static_) {
             if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
                 return null();
             if (tt != TOK_LP) {
+                if (!checkUnescapedName())
+                    return null();
+
                 isStatic = true;
             } else {
                 tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
                 tokenStream.ungetToken();
             }
         } else {
             tokenStream.ungetToken();
         }
@@ -8854,45 +8932,65 @@ Parser<ParseHandler>::propertyName(Yield
         }
 
         *propType = propAtom.get() == context->names().get ? PropertyType::Getter
                                                            : PropertyType::Setter;
 
         // We have parsed |get| or |set|. Look for an accessor property
         // name next.
         TokenKind tt;
-        if (!tokenStream.getToken(&tt, TokenStream::KeywordIsName))
+        if (!tokenStream.peekToken(&tt, TokenStream::KeywordIsName))
             return null();
         if (tt == TOK_NAME) {
+            if (!checkUnescapedName())
+                return null();
+
+            tokenStream.consumeKnownToken(TOK_NAME, TokenStream::KeywordIsName);
+
             propAtom.set(tokenStream.currentName());
             return handler.newObjectLiteralPropertyName(propAtom, pos());
         }
         if (tt == TOK_STRING) {
+            if (!checkUnescapedName())
+                return null();
+
+            tokenStream.consumeKnownToken(TOK_STRING, TokenStream::KeywordIsName);
+
             propAtom.set(tokenStream.currentToken().atom());
 
             uint32_t index;
             if (propAtom->isIndex(&index)) {
                 propAtom.set(DoubleToAtom(context, index));
                 if (!propAtom.get())
                     return null();
                 return handler.newNumber(index, NoDecimal, pos());
             }
             return stringLiteral();
         }
         if (tt == TOK_NUMBER) {
+            if (!checkUnescapedName())
+                return null();
+
+            tokenStream.consumeKnownToken(TOK_NUMBER, TokenStream::KeywordIsName);
+
             propAtom.set(DoubleToAtom(context, tokenStream.currentToken().number()));
             if (!propAtom.get())
                 return null();
             return newNumber(tokenStream.currentToken());
         }
-        if (tt == TOK_LB)
+        if (tt == TOK_LB) {
+            if (!checkUnescapedName())
+                return null();
+
+            tokenStream.consumeKnownToken(TOK_LB, TokenStream::KeywordIsName);
+
             return computedPropertyName(yieldHandling, propList);
+        }
 
         // Not an accessor property after all.
-        tokenStream.ungetToken();
         propName = handler.newObjectLiteralPropertyName(propAtom.get(), pos());
         if (!propName)
             return null();
         tokenStream.addModifierException(TokenStream::NoneIsKeywordIsName);
         break;
       }
 
       case TOK_STRING: {
@@ -9117,16 +9215,19 @@ Parser<ParseHandler>::tryNewTarget(Node 
     if (!tokenStream.getToken(&next))
         return false;
     if (next != TOK_NAME || tokenStream.currentName() != context->names().target) {
         report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                "target", TokenKindToDesc(next));
         return false;
     }
 
+    if (!checkUnescapedName())
+        return false;
+
     if (!pc->sc->allowNewTarget()) {
         reportWithOffset(ParseError, false, begin, JSMSG_BAD_NEWTARGET);
         return false;
     }
 
     Node targetHolder = handler.newPosHolder(pos());
     if (!targetHolder)
         return false;
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -547,16 +547,18 @@ class Parser : private JS::AutoGCRooter,
         return abortedSyntaxParse;
     }
     void clearAbortedSyntaxParse() {
         abortedSyntaxParse = false;
     }
 
     bool isUnexpectedEOF() const { return isUnexpectedEOF_; }
 
+    bool checkUnescapedName();
+
   private:
     Parser* thisForCtor() { return this; }
 
     JSAtom * stopStringCompression();
 
     Node stringLiteral();
     Node noSubstitutionTemplate();
     Node templateLiteral(YieldHandling yieldHandling);
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1011,26 +1011,16 @@ TokenStream::checkForKeyword(const Keywo
             return true;
     }
 
     // Strict reserved word.
     return reportStrictModeError(JSMSG_RESERVED_ID, kw->chars);
 }
 
 bool
-TokenStream::checkForKeyword(const char16_t* s, size_t length, TokenKind* ttp)
-{
-    const KeywordInfo* kw = FindKeyword(s, length);
-    if (!kw)
-        return true;
-
-    return checkForKeyword(kw, ttp);
-}
-
-bool
 TokenStream::checkForKeyword(JSAtom* atom, TokenKind* ttp)
 {
     const KeywordInfo* kw = FindKeyword(atom);
     if (!kw)
         return true;
 
     return checkForKeyword(kw, ttp);
 }
@@ -1230,23 +1220,33 @@ TokenStream::getTokenInternal(TokenKind*
 
             chars = tokenbuf.begin();
             length = tokenbuf.length();
         } else {
             chars = identStart;
             length = userbuf.addressOfNextRawChar() - identStart;
         }
 
-        // Check for keywords unless the parser told us not to.
+        // Represent keywords as keyword tokens unless told otherwise.
         if (modifier != KeywordIsName) {
-            tp->type = TOK_NAME;
-            if (!checkForKeyword(chars, length, &tp->type))
-                goto error;
-            if (tp->type != TOK_NAME)
-                goto out;
+            if (const KeywordInfo* kw = FindKeyword(chars, length)) {
+                // That said, keywords can't contain escapes.  (Contexts where
+                // keywords are treated as names, that also sometimes treat
+                // keywords as keywords, must manually check this requirement.)
+                if (hadUnicodeEscape) {
+                    reportError(JSMSG_ESCAPED_KEYWORD);
+                    goto error;
+                }
+
+                tp->type = TOK_NAME;
+                if (!checkForKeyword(kw, &tp->type))
+                    goto error;
+                if (tp->type != TOK_NAME)
+                    goto out;
+            }
         }
 
         JSAtom* atom = AtomizeChars(cx, chars, length);
         if (!atom)
             goto error;
         tp->type = TOK_NAME;
         tp->setName(atom->asPropertyName());
         goto out;
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -218,17 +218,22 @@ struct Token
         u.number.value = n;
         u.number.decimalPoint = decimalPoint;
     }
 
     // Type-safe accessors
 
     PropertyName* name() const {
         MOZ_ASSERT(type == TOK_NAME);
-        return u.name->asPropertyName(); // poor-man's type verification
+        return u.name->JSAtom::asPropertyName(); // poor-man's type verification
+    }
+
+    bool nameContainsEscape() const {
+        PropertyName* n = name();
+        return pos.begin + n->length() != pos.end;
     }
 
     JSAtom* atom() const {
         MOZ_ASSERT(type == TOK_STRING ||
                    type == TOK_TEMPLATE_HEAD ||
                    type == TOK_NO_SUBS_TEMPLATE);
         return u.atom;
     }
@@ -637,21 +642,38 @@ class MOZ_STACK_CLASS TokenStream
 
     void consumeKnownToken(TokenKind tt, Modifier modifier = None) {
         bool matched;
         MOZ_ASSERT(hasLookahead());
         MOZ_ALWAYS_TRUE(matchToken(&matched, tt, modifier));
         MOZ_ALWAYS_TRUE(matched);
     }
 
-    bool matchContextualKeyword(bool* matchedp, Handle<PropertyName*> keyword) {
+    // Like matchToken(..., TOK_NAME) but further matching the name token only
+    // if it has the given characters, without containing escape sequences.
+    // If the name token has the given characters yet *does* contain an escape,
+    // a syntax error will be reported.
+    //
+    // This latter behavior makes this method unsuitable for use in any context
+    // where ASI might occur.  In such places, an escaped "contextual keyword"
+    // on a new line is the start of an ExpressionStatement, not a continuation
+    // of a StatementListItem (or ImportDeclaration or ExportDeclaration, in
+    // modules).
+    bool matchContextualKeyword(bool* matchedp, Handle<PropertyName*> keyword,
+                                Modifier modifier = None)
+    {
         TokenKind token;
-        if (!getToken(&token))
+        if (!getToken(&token, modifier))
             return false;
         if (token == TOK_NAME && currentToken().name() == keyword) {
+            if (currentToken().nameContainsEscape()) {
+                reportError(JSMSG_ESCAPED_KEYWORD);
+                return false;
+            }
+
             *matchedp = true;
         } else {
             *matchedp = false;
             ungetToken();
         }
         return true;
     }
 
@@ -715,29 +737,30 @@ class MOZ_STACK_CLASS TokenStream
     bool hasSourceMapURL() const {
         return sourceMapURL_ != nullptr;
     }
 
     char16_t* sourceMapURL() {
         return sourceMapURL_.get();
     }
 
-    // If the name at s[0:length] is not a keyword in this version, return
-    // true with *ttp unchanged.
+    // If |atom| is not a keyword in this version, return true with *ttp
+    // unchanged.
     //
     // If it is a reserved word in this version and strictness mode, and thus
     // can't be present in correct code, report a SyntaxError and return false.
     //
     // If it is a keyword, like "if", the behavior depends on ttp. If ttp is
     // null, report a SyntaxError ("if is a reserved identifier") and return
     // false. If ttp is non-null, return true with the keyword's TokenKind in
     // *ttp.
+    bool checkForKeyword(JSAtom* atom, TokenKind* ttp);
+
+    // Same semantics as above, but for the provided keyword.
     bool checkForKeyword(const KeywordInfo* kw, TokenKind* ttp);
-    bool checkForKeyword(const char16_t* s, size_t length, TokenKind* ttp);
-    bool checkForKeyword(JSAtom* atom, TokenKind* ttp);
 
     // This class maps a userbuf offset (which is 0-indexed) to a line number
     // (which is 1-indexed) and a column index (which is 0-indexed).
     class SourceCoords
     {
         // For a given buffer holding source code, |lineStartOffsets_| has one
         // element per line of source code, plus one sentinel element.  Each
         // non-sentinel element holds the buffer offset for the start of the
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -303,32 +303,16 @@ js::gc::GCRuntime::markRuntime(JSTracer*
     }
 
     if (rt->isHeapMinorCollecting())
         jit::JitRuntime::MarkJitcodeGlobalTableUnconditionally(trc);
 
     for (ContextIter acx(rt); !acx.done(); acx.next())
         acx->mark(trc);
 
-    for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-        if (traceOrMark == MarkRuntime && !zone->isCollecting())
-            continue;
-
-        /* Do not discard scripts with counts while profiling. */
-        if (rt->profilingScripts && !rt->isHeapMinorCollecting()) {
-            for (ZoneCellIterUnderGC i(zone, AllocKind::SCRIPT); !i.done(); i.next()) {
-                JSScript* script = i.get<JSScript>();
-                if (script->hasScriptCounts()) {
-                    TraceRoot(trc, &script, "profilingScripts");
-                    MOZ_ASSERT(script == i.get<JSScript>());
-                }
-            }
-        }
-    }
-
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
         c->traceRoots(trc, traceOrMark);
 
     MarkInterpreterActivations(rt, trc);
 
     jit::MarkJitActivations(rt, trc);
 
     if (!rt->isHeapMinorCollecting()) {
--- a/js/src/jit-test/lib/syntax.js
+++ b/js/src/jit-test/lib/syntax.js
@@ -1,18 +1,22 @@
 load(libdir + "class.js");
 
 function test_syntax(replacements, check_error, ignore_opts) {
-  function test_reflect(code) {
+  function test_reflect(code, module) {
+    var options = undefined;
+    if (module) {
+      options = {target: "module"};
+    }
     for (var repl of replacements) {
       var cur_code = code.replace(/@/, repl);
 
       var caught = false;
       try {
-        Reflect.parse(cur_code);
+        Reflect.parse(cur_code, options);
       } catch (e) {
         caught = true;
         check_error(e, cur_code, "reflect");
       }
       assertEq(caught, true);
     }
   }
 
@@ -34,25 +38,26 @@ function test_syntax(replacements, check
   function test(code, opts={}) {
     if (ignore_opts) {
       opts = {};
     }
 
     let no_strict = "no_strict" in opts && opts.no_strict;
     let no_fun = "no_fun" in opts && opts.no_fun;
     let no_eval = "no_eval" in opts && opts.no_eval;
+    let module = "module" in opts && opts.module;
 
-    test_reflect(code);
+    test_reflect(code, module);
     if (!no_strict) {
-      test_reflect("'use strict'; " + code);
+      test_reflect("'use strict'; " + code, module);
     }
     if (!no_fun) {
-      test_reflect("(function() { " + code);
+      test_reflect("(function() { " + code, module);
       if (!no_strict) {
-        test_reflect("(function() { 'use strict'; " + code);
+        test_reflect("(function() { 'use strict'; " + code, module);
       }
     }
 
     if (!no_eval) {
       test_eval(code);
       if (!no_strict) {
         test_eval("'use strict'; " + code);
       }
@@ -60,24 +65,16 @@ function test_syntax(replacements, check
         test_eval("(function() { " + code);
         if (!no_strict) {
           test_eval("(function() { 'use strict'; " + code);
         }
       }
     }
   }
 
-  function test_no_strict(code) {
-    test(code, { no_strict: true });
-  }
-
-  function test_no_fun_no_eval(code) {
-    test(code, { no_fun: true, no_eval: true });
-  }
-
   function test_fun_arg(arg) {
     for (var repl of replacements) {
       var cur_arg = arg.replace(/@/, repl);
 
       var caught = false;
       try {
         new Function(cur_arg, "");
       } catch (e) {
@@ -433,189 +430,191 @@ function test_syntax(replacements, check
 
   // debugger
 
   test("debugger @");
   test("debugger; @");
 
   // export
 
-  test_no_fun_no_eval("export @");
-  test_no_fun_no_eval("export { @");
-  test_no_fun_no_eval("export { x @");
-  test_no_fun_no_eval("export { x, @");
-  test_no_fun_no_eval("export { x, y @");
-  test_no_fun_no_eval("export { x, y as @");
-  test_no_fun_no_eval("export { x, y as z @");
-  test_no_fun_no_eval("export { x, y as z } @");
-  test_no_fun_no_eval("export { x, y as z } from @");
-  test_no_fun_no_eval("export { x, y as z } from 'a' @");
-  test_no_fun_no_eval("export { x, y as z } from 'a'; @");
+  var opts = { no_fun: true, no_eval: true, module: true };
+  test("export @", opts);
+  test("export { @", opts);
+  test("export { x @", opts);
+  test("export { x, @", opts);
+  test("export { x, y @", opts);
+  test("export { x, y as @", opts);
+  test("export { x, y as z @", opts);
+  test("export { x, y as z } @", opts);
+  test("export { x, y as z } from @", opts);
+  test("export { x, y as z } from 'a' @", opts);
+  test("export { x, y as z } from 'a'; @", opts);
 
-  test_no_fun_no_eval("export * @");
-  test_no_fun_no_eval("export * from @");
-  test_no_fun_no_eval("export * from 'a' @");
-  test_no_fun_no_eval("export * from 'a'; @");
+  test("export * @", opts);
+  test("export * from @", opts);
+  test("export * from 'a' @", opts);
+  test("export * from 'a'; @", opts);
 
-  test_no_fun_no_eval("export function @");
-  test_no_fun_no_eval("export function f @");
-  test_no_fun_no_eval("export function f( @");
-  test_no_fun_no_eval("export function f() @");
-  test_no_fun_no_eval("export function f() { @");
-  test_no_fun_no_eval("export function f() {} @");
-  test_no_fun_no_eval("export function f() {}; @");
+  test("export function @", opts);
+  test("export function f @", opts);
+  test("export function f( @", opts);
+  test("export function f() @", opts);
+  test("export function f() { @", opts);
+  test("export function f() {} @", opts);
+  test("export function f() {}; @", opts);
 
-  test_no_fun_no_eval("export var @");
-  test_no_fun_no_eval("export var a @");
-  test_no_fun_no_eval("export var a = @");
-  test_no_fun_no_eval("export var a = 1 @");
-  test_no_fun_no_eval("export var a = 1, @");
-  test_no_fun_no_eval("export var a = 1, b @");
-  test_no_fun_no_eval("export var a = 1, b = @");
-  test_no_fun_no_eval("export var a = 1, b = 2 @");
-  test_no_fun_no_eval("export var a = 1, b = 2; @");
+  test("export var @", opts);
+  test("export var a @", opts);
+  test("export var a = @", opts);
+  test("export var a = 1 @", opts);
+  test("export var a = 1, @", opts);
+  test("export var a = 1, b @", opts);
+  test("export var a = 1, b = @", opts);
+  test("export var a = 1, b = 2 @", opts);
+  test("export var a = 1, b = 2; @", opts);
 
-  test_no_fun_no_eval("export let @");
-  test_no_fun_no_eval("export let a @");
-  test_no_fun_no_eval("export let a = @");
-  test_no_fun_no_eval("export let a = 1 @");
-  test_no_fun_no_eval("export let a = 1, @");
-  test_no_fun_no_eval("export let a = 1, b @");
-  test_no_fun_no_eval("export let a = 1, b = @");
-  test_no_fun_no_eval("export let a = 1, b = 2 @");
-  test_no_fun_no_eval("export let a = 1, b = 2; @");
+  test("export let @", opts);
+  test("export let a @", opts);
+  test("export let a = @", opts);
+  test("export let a = 1 @", opts);
+  test("export let a = 1, @", opts);
+  test("export let a = 1, b @", opts);
+  test("export let a = 1, b = @", opts);
+  test("export let a = 1, b = 2 @", opts);
+  test("export let a = 1, b = 2; @", opts);
 
-  test_no_fun_no_eval("export const @");
-  test_no_fun_no_eval("export const a @");
-  test_no_fun_no_eval("export const a = @");
-  test_no_fun_no_eval("export const a = 1 @");
-  test_no_fun_no_eval("export const a = 1, @");
-  test_no_fun_no_eval("export const a = 1, b @");
-  test_no_fun_no_eval("export const a = 1, b = @");
-  test_no_fun_no_eval("export const a = 1, b = 2 @");
-  test_no_fun_no_eval("export const a = 1, b = 2; @");
+  test("export const @", opts);
+  test("export const a @", opts);
+  test("export const a = @", opts);
+  test("export const a = 1 @", opts);
+  test("export const a = 1, @", opts);
+  test("export const a = 1, b @", opts);
+  test("export const a = 1, b = @", opts);
+  test("export const a = 1, b = 2 @", opts);
+  test("export const a = 1, b = 2; @", opts);
 
   if (classesEnabled()) {
-    test_no_fun_no_eval("export class @");
-    test_no_fun_no_eval("export class Foo @");
-    test_no_fun_no_eval("export class Foo {  @");
-    test_no_fun_no_eval("export class Foo { constructor @");
-    test_no_fun_no_eval("export class Foo { constructor( @");
-    test_no_fun_no_eval("export class Foo { constructor() @");
-    test_no_fun_no_eval("export class Foo { constructor() { @");
-    test_no_fun_no_eval("export class Foo { constructor() {} @");
-    test_no_fun_no_eval("export class Foo { constructor() {} } @");
-    test_no_fun_no_eval("export class Foo { constructor() {} }; @");
+    test("export class @", opts);
+    test("export class Foo @", opts);
+    test("export class Foo {  @", opts);
+    test("export class Foo { constructor @", opts);
+    test("export class Foo { constructor( @", opts);
+    test("export class Foo { constructor() @", opts);
+    test("export class Foo { constructor() { @", opts);
+    test("export class Foo { constructor() {} @", opts);
+    test("export class Foo { constructor() {} } @", opts);
+    test("export class Foo { constructor() {} }; @", opts);
   }
 
-  test_no_fun_no_eval("export default @");
-  test_no_fun_no_eval("export default 1 @");
-  test_no_fun_no_eval("export default 1; @");
+  test("export default @", opts);
+  test("export default 1 @", opts);
+  test("export default 1; @", opts);
 
-  test_no_fun_no_eval("export default function @");
-  test_no_fun_no_eval("export default function() @");
-  test_no_fun_no_eval("export default function() { @");
-  test_no_fun_no_eval("export default function() {} @");
-  test_no_fun_no_eval("export default function() {}; @");
+  test("export default function @", opts);
+  test("export default function() @", opts);
+  test("export default function() { @", opts);
+  test("export default function() {} @", opts);
+  test("export default function() {}; @", opts);
 
-  test_no_fun_no_eval("export default function foo @");
-  test_no_fun_no_eval("export default function foo( @");
-  test_no_fun_no_eval("export default function foo() @");
-  test_no_fun_no_eval("export default function foo() { @");
-  test_no_fun_no_eval("export default function foo() {} @");
-  test_no_fun_no_eval("export default function foo() {}; @");
+  test("export default function foo @", opts);
+  test("export default function foo( @", opts);
+  test("export default function foo() @", opts);
+  test("export default function foo() { @", opts);
+  test("export default function foo() {} @", opts);
+  test("export default function foo() {}; @", opts);
 
   if (classesEnabled()) {
-    test_no_fun_no_eval("export default class @");
-    test_no_fun_no_eval("export default class { @");
-    test_no_fun_no_eval("export default class { constructor @");
-    test_no_fun_no_eval("export default class { constructor( @");
-    test_no_fun_no_eval("export default class { constructor() @");
-    test_no_fun_no_eval("export default class { constructor() { @");
-    test_no_fun_no_eval("export default class { constructor() {} @");
-    test_no_fun_no_eval("export default class { constructor() {} } @");
-    test_no_fun_no_eval("export default class { constructor() {} }; @");
+    test("export default class @", opts);
+    test("export default class { @", opts);
+    test("export default class { constructor @", opts);
+    test("export default class { constructor( @", opts);
+    test("export default class { constructor() @", opts);
+    test("export default class { constructor() { @", opts);
+    test("export default class { constructor() {} @", opts);
+    test("export default class { constructor() {} } @", opts);
+    test("export default class { constructor() {} }; @", opts);
 
-    test_no_fun_no_eval("export default class Foo @");
-    test_no_fun_no_eval("export default class Foo { @");
-    test_no_fun_no_eval("export default class Foo { constructor @");
-    test_no_fun_no_eval("export default class Foo { constructor( @");
-    test_no_fun_no_eval("export default class Foo { constructor() @");
-    test_no_fun_no_eval("export default class Foo { constructor() { @");
-    test_no_fun_no_eval("export default class Foo { constructor() {} @");
-    test_no_fun_no_eval("export default class Foo { constructor() {} } @");
-    test_no_fun_no_eval("export default class Foo { constructor() {} }; @");
+    test("export default class Foo @", opts);
+    test("export default class Foo { @", opts);
+    test("export default class Foo { constructor @", opts);
+    test("export default class Foo { constructor( @", opts);
+    test("export default class Foo { constructor() @", opts);
+    test("export default class Foo { constructor() { @", opts);
+    test("export default class Foo { constructor() {} @", opts);
+    test("export default class Foo { constructor() {} } @", opts);
+    test("export default class Foo { constructor() {} }; @", opts);
   }
 
   // import
 
-  test_no_fun_no_eval("import @");
-  test_no_fun_no_eval("import x @");
-  test_no_fun_no_eval("import x from @");
-  test_no_fun_no_eval("import x from 'a' @");
-  test_no_fun_no_eval("import x from 'a'; @");
+  test("import @", opts);
+  test("import x @", opts);
+  test("import x from @", opts);
+  test("import x from 'a' @", opts);
+  test("import x from 'a'; @", opts);
 
-  test_no_fun_no_eval("import { @");
-  test_no_fun_no_eval("import { x @");
-  test_no_fun_no_eval("import { x, @");
-  test_no_fun_no_eval("import { x, y @");
-  test_no_fun_no_eval("import { x, y } @");
-  test_no_fun_no_eval("import { x, y } from @");
-  test_no_fun_no_eval("import { x, y } from 'a' @");
-  test_no_fun_no_eval("import { x, y } from 'a'; @");
+  test("import { @", opts);
+  test("import { x @", opts);
+  test("import { x, @", opts);
+  test("import { x, y @", opts);
+  test("import { x, y } @", opts);
+  test("import { x, y } from @", opts);
+  test("import { x, y } from 'a' @", opts);
+  test("import { x, y } from 'a'; @", opts);
 
-  test_no_fun_no_eval("import { x as @");
-  test_no_fun_no_eval("import { x as y @");
-  test_no_fun_no_eval("import { x as y } @");
-  test_no_fun_no_eval("import { x as y } from @");
-  test_no_fun_no_eval("import { x as y } from 'a' @");
-  test_no_fun_no_eval("import { x as y } from 'a'; @");
+  test("import { x as @", opts);
+  test("import { x as y @", opts);
+  test("import { x as y } @", opts);
+  test("import { x as y } from @", opts);
+  test("import { x as y } from 'a' @", opts);
+  test("import { x as y } from 'a'; @", opts);
 
-  test_no_fun_no_eval("import 'a' @");
-  test_no_fun_no_eval("import 'a'; @");
+  test("import 'a' @", opts);
+  test("import 'a'; @", opts);
 
-  test_no_fun_no_eval("import * @");
-  test_no_fun_no_eval("import * as @");
-  test_no_fun_no_eval("import * as a @");
-  test_no_fun_no_eval("import * as a from @");
-  test_no_fun_no_eval("import * as a from 'a' @");
-  test_no_fun_no_eval("import * as a from 'a'; @");
+  test("import * @", opts);
+  test("import * as @", opts);
+  test("import * as a @", opts);
+  test("import * as a from @", opts);
+  test("import * as a from 'a' @", opts);
+  test("import * as a from 'a'; @", opts);
 
-  test_no_fun_no_eval("import a @");
-  test_no_fun_no_eval("import a, @");
-  test_no_fun_no_eval("import a, * @");
-  test_no_fun_no_eval("import a, * as @");
-  test_no_fun_no_eval("import a, * as b @");
-  test_no_fun_no_eval("import a, * as b from @");
-  test_no_fun_no_eval("import a, * as b from 'c' @");
-  test_no_fun_no_eval("import a, * as b from 'c'; @");
+  test("import a @", opts);
+  test("import a, @", opts);
+  test("import a, * @", opts);
+  test("import a, * as @", opts);
+  test("import a, * as b @", opts);
+  test("import a, * as b from @", opts);
+  test("import a, * as b from 'c' @", opts);
+  test("import a, * as b from 'c'; @", opts);
 
-  test_no_fun_no_eval("import a, { @");
-  test_no_fun_no_eval("import a, { b @");
-  test_no_fun_no_eval("import a, { b } @");
-  test_no_fun_no_eval("import a, { b } from @");
-  test_no_fun_no_eval("import a, { b } from 'c' @");
-  test_no_fun_no_eval("import a, { b } from 'c'; @");
+  test("import a, { @", opts);
+  test("import a, { b @", opts);
+  test("import a, { b } @", opts);
+  test("import a, { b } from @", opts);
+  test("import a, { b } from 'c' @", opts);
+  test("import a, { b } from 'c'; @", opts);
 
   // label
 
   test("a @");
   test("a: @");
 
   // with
 
-  test_no_strict("with @");
-  test_no_strict("with (@");
-  test_no_strict("with (x @");
-  test_no_strict("with (x) @");
-  test_no_strict("with (x) { @");
-  test_no_strict("with (x) {} @");
+  opts = { no_strict: true };
+  test("with @", opts);
+  test("with (@", opts);
+  test("with (x @", opts);
+  test("with (x) @", opts);
+  test("with (x) { @", opts);
+  test("with (x) {} @", opts);
 
-  test_no_strict("with (x) x @");
-  test_no_strict("with (x) x; @");
+  test("with (x) x @", opts);
+  test("with (x) x; @", opts);
 
   // ==== Expressions and operators ====
 
   // ---- Primary expressions ----
 
   // this
 
   test("this @");
deleted file mode 100644
--- a/js/src/jit-test/tests/asm.js/syntax-error-illegal-character.js
+++ /dev/null
@@ -1,1 +0,0 @@
-// |jit-test| test-also-noasmjs
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/coverage/bug1203695.js
@@ -0,0 +1,14 @@
+
+var lfcode = new Array();
+lfcode.push = loadFile;
+lfcode.push(")");
+lfcode.push(`
+assertThrowsInstanceOf(function () {}, TypeError);
+var g = newGlobal();
+`);
+getLcovInfo(g);
+function loadFile(lfVarx) {
+    try {
+        evaluate(lfVarx, { noScriptRval : true, compileAndGo : true });
+    } catch (lfVare) {}
+}
--- a/js/src/jit-test/tests/coverage/simple.js
+++ b/js/src/jit-test/tests/coverage/simple.js
@@ -6,16 +6,25 @@
 //  - Baseline OSR increments the loop header twice.
 //  - Ion is not updating any counter yet.
 //
 if (getJitCompilerOptions()["ion.warmup.trigger"] != 30)
   setJitCompilerOption("ion.warmup.trigger", 30);
 if (getJitCompilerOptions()["baseline.warmup.trigger"] != 10)
   setJitCompilerOption("baseline.warmup.trigger", 10);
 
+/*
+ * These test cases are annotated with the output produced by LCOV [1].  Comment
+ * starting with //<key> without any spaces are used as a reference for the code
+ * coverage output.  Any "$" in these line comments are replaced by the current
+ * line number, and any "%" are replaced with the current function name (defined
+ * by the FN key).
+ *
+ * [1]  http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
+ */
 function checkLcov(fun) {
   var keys = [ "TN", "SF", "FN", "FNDA", "FNF", "FNH", "BRDA", "BRF", "BRH", "DA", "LF", "LH" ];
   function startsWithKey(s) {
     for (k of keys) {
       if (s.startsWith(k))
         return true;
     }
     return false;
@@ -208,8 +217,11 @@ checkLcov(function () { //FN:$,top-level
   }
   //FNF:2
   //FNH:2
   //LF:7
   //LH:5
   //BRF:0
   //BRH:0
 });
+
+// If you add a test case here, do the same in
+// jit-test/tests/debug/Script-getOffsetsCoverage-01.js
copy from js/src/jit-test/tests/coverage/simple.js
copy to js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
--- a/js/src/jit-test/tests/coverage/simple.js
+++ b/js/src/jit-test/tests/debug/Script-getOffsetsCoverage-01.js
@@ -1,145 +1,221 @@
-// |jit-test| --code-coverage;
-
 // Currently the Jit integration has a few issues, let's keep this test
 // case deterministic.
 //
 //  - Baseline OSR increments the loop header twice.
 //  - Ion is not updating any counter yet.
 //
 if (getJitCompilerOptions()["ion.warmup.trigger"] != 30)
   setJitCompilerOption("ion.warmup.trigger", 30);
 if (getJitCompilerOptions()["baseline.warmup.trigger"] != 10)
   setJitCompilerOption("baseline.warmup.trigger", 10);
 
-function checkLcov(fun) {
+/*
+ * These test cases are annotated with the output produced by LCOV [1].  Comment
+ * starting with //<key> without any spaces are used as a reference for the code
+ * coverage output.  Any "$" in these line comments are replaced by the current
+ * line number, and any "%" are replaced with the current function name (defined
+ * by the FN key).
+ *
+ * [1]  http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php
+ */
+function checkGetOffsetsCoverage(fun) {
   var keys = [ "TN", "SF", "FN", "FNDA", "FNF", "FNH", "BRDA", "BRF", "BRH", "DA", "LF", "LH" ];
   function startsWithKey(s) {
     for (k of keys) {
       if (s.startsWith(k))
         return true;
     }
     return false;
   };
 
   // Extract the body of the function, as the code to be executed.
   var source = fun.toSource();
   source = source.slice(source.indexOf('{') + 1, source.lastIndexOf('}'));
 
-  // Extract comment starting with the previous keys, as a reference of the
-  // output expected from getLcovInfo.
+  // Extract comment starting with the previous keys, as a reference.
   var lcovRef = [];
   var currLine = 0;
-  var currFun = "<badfunction>";
+  var currFun = [{name: "top-level", braces: 1}];
   for (var line of source.split('\n')) {
     currLine++;
+
     for (var comment of line.split("//").slice(1)) {
       if (!startsWithKey(comment))
         continue;
       comment = comment.trim();
       if (comment.startsWith("FN:"))
-        currFun = comment.split(',')[1];
-      comment = comment.replace('$', currLine);
-      comment = comment.replace('%', currFun);
+        currFun.push({ name: comment.split(',')[1], braces: 0 });
+      var name = currFun[currFun.length - 1].name;
+      if (!comment.startsWith("DA:"))
+        continue;
+      comment = {
+        offset: null,
+        lineNumber: currLine,
+        columnNumber: null,
+        count: comment.split(",")[1] | 0,
+        script: (name == "top-level" ? undefined : name)
+      };
       lcovRef.push(comment);
     }
+
+    var deltaBraces = line.split('{').length - line.split('}').length;
+    currFun[currFun.length - 1].braces += deltaBraces;
+    if (currFun[currFun.length - 1].braces == 0)
+      currFun.pop();
   }
 
-  // Evaluate the code, and generate the Lcov result from the execution.
+  // Create a new global and instrument it with a debugger, to find all scripts,
+  // created in the current global.
   var g = newGlobal();
+  var dbg = Debugger(g);
+  dbg.collectCoverageInfo = true;
+
+  var topLevel = null;
+  dbg.onNewScript = function (s) {
+    topLevel = s;
+    dbg.onNewScript = function () {};
+  };
+
+  // Evaluate the code, and collect the hit counts for each scripts / lines.
   g.eval(source);
-  var lcovResRaw = getLcovInfo(g);
+
+  var coverageRes = [];
+  function collectCoverage(s) {
+    var res = s.getOffsetsCoverage();
+    if (res == null)
+      res = [{
+        offset: null,
+        lineNumber: null,
+        columnNumber: null,
+        script: s.displayName,
+        count: 0
+      }];
+    else {
+      res.map(function (e) {
+        e.script = s.displayName;
+        return e;
+      });
+    }
+    coverageRes.push(res);
+    s.getChildScripts().forEach(collectCoverage);
+  };
+  collectCoverage(topLevel);
+  coverageRes = [].concat(...coverageRes);
 
   // Check that all the lines are present the result.
-  var lcovRes = lcovResRaw.split('\n');
+  function match(ref) {
+    return function (entry) {
+      return ref.lineNumber == entry.lineNumber && ref.script == entry.script;
+    }
+  }
+  function ppObj(entry) {
+    var str = "{";
+    for (var k in entry) {
+      if (entry[k] != null)
+        str += " '" + k + "': " + entry[k] + ",";
+    }
+    str += "}";
+    return str;
+  }
   for (ref of lcovRef) {
-    if (lcovRes.indexOf(ref) == -1) {
-      print("Cannot find `" + ref + "` in the following Lcov result:\n", lcovResRaw);
+    var res = coverageRes.find(match(ref));
+    if (!res) {
+      // getOffsetsCoverage returns null if we have no result for the
+      // script. We added a fake entry with an undefined lineNumber, which is
+      // used to match against the modified reference.
+      var missRef = Object.create(ref);
+      missRef.lineNumber = null;
+      res = coverageRes.find(match(missRef));
+    }
+
+    if (!res || res.count != ref.count) {
+      print("Cannot find `" + ppObj(ref) + "` in the following results:\n", coverageRes.map(ppObj).join("\n"));
       print("In the following source:\n", source);
       assertEq(true, false);
     }
   }
 }
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   ",".split(','); //DA:$,1
   //FNF:1
   //FNH:1
   //LF:1
   //LH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   function f() {    //FN:$,f
     ",".split(','); //DA:$,0
   }
   ",".split(',');   //DA:$,1
   //FNF:2
   //FNH:1
   //LF:2
   //LH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   function f() {    //FN:$,f //FNDA:1,%
     ",".split(','); //DA:$,1
   }
   f();              //DA:$,1
   //FNF:2
   //FNH:2
   //LF:2
   //LH:2
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   var l = ",".split(','); //DA:$,1
   if (l.length == 3)      //DA:$,1
     l.push('');           //DA:$,0
   else
     l.pop();              //DA:$,1
   //FNF:1
   //FNH:1
   //LF:4
   //LH:3
   //BRF:1
   //BRH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   var l = ",".split(','); //DA:$,1
   if (l.length == 2)      //DA:$,1
     l.push('');           //DA:$,1
   else
     l.pop();              //DA:$,0
   //FNF:1
   //FNH:1
   //LF:4
   //LH:3
   //BRF:1
   //BRH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   var l = ",".split(','); //DA:$,1
   if (l.length == 2)      //DA:$,1
     l.push('');           //DA:$,1
   else {
     if (l.length == 1)    //DA:$,0
       l.pop();            //DA:$,0
   }
   //FNF:1
   //FNH:1
   //LF:5
   //LH:3
   //BRF:2
   //BRH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   function f(i) { //FN:$,f //FNDA:2,%
     var x = 0;    //DA:$,2
     while (i--) { // Currently OSR wrongly count the loop header twice.
                   // So instead of DA:$,12 , we have DA:$,13 .
       x += i;     //DA:$,10
       x = x / 2;  //DA:$,10
     }
     return x;     //DA:$,2
@@ -148,17 +224,17 @@ checkLcov(function () { //FN:$,top-level
   }
 
   f(5);           //DA:$,1
   f(5);           //DA:$,1
   //FNF:2
   //FNH:2
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   try {                     //DA:$,1
     var l = ",".split(','); //DA:$,1
     if (l.length == 2) {    //DA:$,1 // BRDA:$,0
       l.push('');           //DA:$,1
       throw l;              //DA:$,1
     }
     l.pop();                //DA:$,0
   } catch (x) {             //DA:$,1
@@ -168,17 +244,17 @@ checkLcov(function () { //FN:$,top-level
   //FNH:1
   //LF:9 // Expected LF:8 , Apparently if the first statement is a try, the
          // statement following the "try{" statement is visited twice.
   //LH:8 // Expected LH:7
   //BRF:1
   //BRH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   var l = ",".split(',');   //DA:$,1
   try {                     //DA:$,1
     try {                   //DA:$,1
       if (l.length == 2) {  //DA:$,1 // BRDA:$,0
         l.push('');         //DA:$,1
         throw l;            //DA:$,1
       }
       l.pop();              //DA:$,0 // BRDA:$,-
@@ -190,17 +266,17 @@ checkLcov(function () { //FN:$,top-level
   //FNF:1
   //FNH:1
   //LF:10
   //LH:9
   //BRF:2
   //BRH:1
 });
 
-checkLcov(function () { //FN:$,top-level //FNDA:1,%
+checkGetOffsetsCoverage(function () { //FN:$,top-level //FNDA:1,%
   function f() {            //FN:$,f //FNDA:1,%
     throw 1;                //DA:$,1
     f();                    //DA:$,0
   }
   var l = ",".split(',');   //DA:$,1
   try {                     //DA:$,1
     f();                    //DA:$,1
     f();                    //DA:$,0
@@ -208,8 +284,11 @@ checkLcov(function () { //FN:$,top-level
   }
   //FNF:2
   //FNH:2
   //LF:7
   //LH:5
   //BRF:0
   //BRH:0
 });
+
+// If you add a test case here, do the same in
+// jit-test/tests/coverage/simple.js
new file mode 100644