Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 24 Mar 2015 11:34:55 -0400
changeset 235308 08f09b24606efcb5afcc69dc612dcc426f7f20ec
parent 235307 840cfd5bc9712a9dbccb829b71abc5fdcfd00020 (current diff)
parent 235262 5329cda711c8f3ca57768a3f37e6a4f17996acd9 (diff)
child 235335 97be9569b8701ed5d85ef469b4d4d442855a07fd
push id57400
push userryanvm@gmail.com
push dateTue, 24 Mar 2015 15:59:13 +0000
treeherdermozilla-inbound@47fa87252df0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.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 inbound to m-c. a=merge
testing/marionette/marionette-common.js
testing/marionette/marionette-elements.js
testing/marionette/marionette-frame-manager.js
testing/marionette/marionette-listener.js
testing/marionette/marionette-sendkeys.js
testing/marionette/marionette-server.js
testing/marionette/marionette-simpletest.js
toolkit/content/aboutwebrtc/README.txt
toolkit/content/aboutwebrtc/aboutWebrtc.jsx
--- a/accessible/xpcom/xpcAccessibleTable.cpp
+++ b/accessible/xpcom/xpcAccessibleTable.cpp
@@ -263,17 +263,17 @@ NS_IMETHODIMP
 xpcAccessibleTable::GetSelectedCells(nsIArray** aSelectedCells)
 {
   NS_ENSURE_ARG_POINTER(aSelectedCells);
   *aSelectedCells = nullptr;
 
   if (!Intl())
     return NS_ERROR_FAILURE;
 
-  NS_IMETHODIMP rv = NS_OK;
+  nsresult rv = NS_OK;
   nsCOMPtr<nsIMutableArray> selCells =
     do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoTArray<Accessible*, XPC_TABLE_DEFAULT_SIZE> cellsArray;
   Intl()->SelectedCells(&cellsArray);
 
   uint32_t totalCount = cellsArray.Length();
new file mode 100644
--- /dev/null
+++ b/b2g/config/desktop/config.json
@@ -0,0 +1,8 @@
+{
+    "gaia": {
+        "l10n": {
+            "vcs": "hgtool",
+            "root": "https://hg.mozilla.org/gaia-l10n"
+        }
+    }
+}
--- a/b2g/locales/jar.mn
+++ b/b2g/locales/jar.mn
@@ -31,16 +31,18 @@ relativesrcdir toolkit/locales:
 #about:crashes
   locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.dtd         (%crashreporter/crashes.dtd)
   locale/@AB_CD@/b2g-l10n/overrides/crashreporter/crashes.properties  (%crashreporter/crashes.properties)
 #about:mozilla
   locale/@AB_CD@/b2g-l10n/overrides/global/mozilla.dtd                (%chrome/global/mozilla.dtd)
 #about:telemetry
   locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.dtd         (%chrome/global/aboutTelemetry.dtd)
   locale/@AB_CD@/b2g-l10n/overrides/global/aboutTelemetry.properties  (%chrome/global/aboutTelemetry.properties)
+#about:webrtc
+  locale/@AB_CD@/b2g-l10n/overrides/global/aboutWebrtc.properties  (%chrome/global/aboutWebrtc.properties)
 
 % override chrome://global/locale/about.dtd chrome://b2g-l10n/locale/overrides/about.dtd
 % override chrome://global/locale/aboutAbout.dtd chrome://b2g-l10n/locale/overrides/aboutAbout.dtd
 % override chrome://global/locale/aboutRights.dtd chrome://b2g-l10n/locale/overrides/aboutRights.dtd
 % override chrome://global/locale/commonDialogs.properties chrome://b2g-l10n/locale/overrides/commonDialogs.properties
 % override chrome://mozapps/locale/handling/handling.properties chrome://b2g-l10n/locale/overrides/handling/handling.properties
 % override chrome://global/locale/intl.properties chrome://b2g-l10n/locale/overrides/intl.properties
 % override chrome://global/locale/intl.css chrome://b2g-l10n/locale/overrides/intl.css
@@ -49,16 +51,17 @@ relativesrcdir toolkit/locales:
 % override chrome://mozapps/locale/update/updates.properties chrome://b2g-l10n/locale/overrides/update/updates.properties
 % override chrome://global/locale/aboutSupport.dtd chrome://b2g-l10n/locale/overrides/global/aboutSupport.dtd
 % override chrome://global/locale/aboutSupport.properties chrome://b2g-l10n/locale/overrides/global/aboutSupport.properties
 % override chrome://global/locale/crashes.dtd chrome://b2g-l10n/locale/overrides/crashreporter/crashes.dtd
 % override chrome://global/locale/crashes.properties chrome://b2g-l10n/locale/overrides/crashreporter/crashes.properties
 % override chrome://global/locale/mozilla.dtd chrome://b2g-l10n/locale/overrides/global/mozilla.dtd
 % override chrome://global/locale/aboutTelemetry.dtd chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.dtd
 % override chrome://global/locale/aboutTelemetry.properties chrome://b2g-l10n/locale/overrides/global/aboutTelemetry.properties
+% override chrome://global/locale/aboutWebrtc.properties chrome://b2g-l10n/locale/overrides/global/aboutWebrtc.properties
 
 # overrides for dom l10n, also for en-US
 relativesrcdir dom/locales:
   locale/@AB_CD@/b2g-l10n/overrides/global.dtd                  (%chrome/global.dtd)
   locale/@AB_CD@/b2g-l10n/overrides/AccessFu.properties         (%chrome/accessibility/AccessFu.properties)
   locale/@AB_CD@/b2g-l10n/overrides/dom/dom.properties          (%chrome/dom/dom.properties)
 #about:plugins
   locale/@AB_CD@/b2g-l10n/overrides/plugins.properties          (%chrome/plugins.properties)
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -337,16 +337,27 @@ nsBrowserContentHandler.prototype = {
     if (cmdLine.handleFlag("browser", false)) {
       // Passing defaultArgs, so use NO_EXTERNAL_URIS
       openWindow(null, this.chromeURL, "_blank",
                  "chrome,dialog=no,all" + this.getFeatures(cmdLine),
                  this.defaultArgs, NO_EXTERNAL_URIS);
       cmdLine.preventDefault = true;
     }
 
+    // In the past, when an instance was not already running, the -remote
+    // option returned an error code. Any script or application invoking the
+    // -remote option is expected to be handling this case, otherwise they
+    // wouldn't be doing anything when there is no Firefox already running.
+    // Making the -remote option always return an error code makes those
+    // scripts or applications handle the situation as if Firefox was not
+    // already running.
+    if (cmdLine.handleFlag("remote", true)) {
+      throw NS_ERROR_ABORT;
+    }
+
     var uriparam;
     try {
       while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
         var uri = resolveURIInternal(cmdLine, uriparam);
         if (!shouldLoadURI(uri))
           continue;
         openWindow(null, this.chromeURL, "_blank",
                    "chrome,dialog=no,all" + this.getFeatures(cmdLine),
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4646,18 +4646,17 @@ nsDocShell::IsPrintingOrPP(bool aDisplay
   return mIsPrintingOrPP;
 }
 
 bool
 nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog,
                                 bool aCheckIfUnloadFired)
 {
   bool isAllowed = !IsPrintingOrPP(aDisplayPrintErrorDialog) &&
-                   (!aCheckIfUnloadFired || !mFiredUnloadEvent) &&
-                   !mBlockNavigation;
+                   (!aCheckIfUnloadFired || !mFiredUnloadEvent);
   if (!isAllowed) {
     return false;
   }
   if (!mContentViewer) {
     return true;
   }
   bool firingBeforeUnload;
   mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload);
@@ -9558,18 +9557,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
                          nsISHEntry* aSHEntry,
                          bool aFirstParty,
                          const nsAString& aSrcdoc,
                          nsIDocShell* aSourceDocShell,
                          nsIURI* aBaseURI,
                          nsIDocShell** aDocShell,
                          nsIRequest** aRequest)
 {
-  MOZ_RELEASE_ASSERT(!mBlockNavigation);
-
   nsresult rv = NS_OK;
   mOriginalUriString.Truncate();
 
 #ifdef PR_LOGGING
   if (gDocShellLeakLog && PR_LOG_TEST(gDocShellLeakLog, PR_LOG_DEBUG)) {
     nsAutoCString spec;
     if (aURI) {
       aURI->GetSpec(spec);
@@ -10015,29 +10012,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
        sameExceptHashes && !newHash.IsEmpty());
 
     if (doShortCircuitedLoad) {
       // Save the position of the scrollers.
       nscoord cx = 0, cy = 0;
       GetCurScrollPos(ScrollOrientation_X, &cx);
       GetCurScrollPos(ScrollOrientation_Y, &cy);
 
-      {
-        AutoRestore<bool> scrollingToAnchor(mBlockNavigation);
-        mBlockNavigation = true;
-
-        // ScrollToAnchor doesn't necessarily cause us to scroll the window;
-        // the function decides whether a scroll is appropriate based on the
-        // arguments it receives.  But even if we don't end up scrolling,
-        // ScrollToAnchor performs other important tasks, such as informing
-        // the presShell that we have a new hash.  See bug 680257.
-        rv = ScrollToAnchor(curHash, newHash, aLoadType);
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-
       // Reset mLoadType to its original value once we exit this block,
       // because this short-circuited load might have started after a
       // normal, network load, and we don't want to clobber its load type.
       // See bug 737307.
       AutoRestore<uint32_t> loadTypeResetter(mLoadType);
 
       // If a non-short-circuit load (i.e., a network load) is pending,
       // make this a replacement load, so that we don't add a SHEntry here
@@ -10117,26 +10101,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
         // Make sure we won't just repost without hitting the
         // cache first
         if (cacheKey) {
           mOSHE->SetCacheKey(cacheKey);
         }
       }
 
-      /* restore previous position of scroller(s), if we're moving
-       * back in history (bug 59774)
-       */
-      if (mOSHE && (aLoadType == LOAD_HISTORY ||
-                    aLoadType == LOAD_RELOAD_NORMAL)) {
-        nscoord bx, by;
-        mOSHE->GetScrollPosition(&bx, &by);
-        SetCurScrollPosEx(bx, by);
-      }
-
       /* Restore the original LSHE if we were loading something
        * while short-circuited load was initiated.
        */
       SetHistoryEntry(&mLSHE, oldLSHE);
       /* Set the title for the SH entry for this target url. so that
        * SH menus in go/back/forward buttons won't be empty for this.
        */
       if (mSessionHistory) {
@@ -10161,41 +10135,61 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
       // Set the doc's URI according to the new history entry's URI.
       nsCOMPtr<nsIDocument> doc = GetDocument();
       NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
       doc->SetDocumentURI(aURI);
 
       SetDocCurrentStateObj(mOSHE);
 
+      // Inform the favicon service that the favicon for oldURI also
+      // applies to aURI.
+      CopyFavicon(currentURI, aURI, mInPrivateBrowsing);
+
+      nsRefPtr<nsGlobalWindow> win = mScriptGlobal ?
+        mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+
+      // ScrollToAnchor doesn't necessarily cause us to scroll the window;
+      // the function decides whether a scroll is appropriate based on the
+      // arguments it receives.  But even if we don't end up scrolling,
+      // ScrollToAnchor performs other important tasks, such as informing
+      // the presShell that we have a new hash.  See bug 680257.
+      rv = ScrollToAnchor(curHash, newHash, aLoadType);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      /* restore previous position of scroller(s), if we're moving
+       * back in history (bug 59774)
+       */
+      if (mOSHE && (aLoadType == LOAD_HISTORY ||
+                    aLoadType == LOAD_RELOAD_NORMAL)) {
+        nscoord bx, by;
+        mOSHE->GetScrollPosition(&bx, &by);
+        SetCurScrollPosEx(bx, by);
+      }
+
       // Dispatch the popstate and hashchange events, as appropriate.
       //
       // The event dispatch below can cause us to re-enter script and
       // destroy the docshell, nulling out mScriptGlobal. Hold a stack
       // reference to avoid null derefs. See bug 914521.
-      nsRefPtr<nsGlobalWindow> win = mScriptGlobal;
       if (win) {
         // Fire a hashchange event URIs differ, and only in their hashes.
         bool doHashchange = sameExceptHashes && !curHash.Equals(newHash);
 
         if (historyNavBetweenSameDoc || doHashchange) {
           win->DispatchSyncPopState();
         }
 
         if (doHashchange) {
           // Note that currentURI hasn't changed because it's on the
           // stack, so we can just use it directly as the old URI.
           win->DispatchAsyncHashchange(currentURI, aURI);
         }
       }
 
-      // Inform the favicon service that the favicon for oldURI also
-      // applies to aURI.
-      CopyFavicon(currentURI, aURI, mInPrivateBrowsing);
-
       return NS_OK;
     }
   }
 
   // Check if the webbrowser chrome wants the load to proceed; this can be
   // used to cancel attempts to load URIs in the wrong process.
   nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
   if (browserChrome3) {
@@ -13990,17 +13984,18 @@ nsDocShell::ChannelIntercepted(nsIInterc
 
   if (!isNavigation) {
     doc = GetDocument();
     if (!doc) {
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
-  return swm->DispatchFetchEvent(doc, aChannel);
+  bool isReload = mLoadType & LOAD_CMD_RELOAD;
+  return swm->DispatchFetchEvent(doc, aChannel, isReload);
 }
 
 NS_IMETHODIMP
 nsDocShell::SetPaymentRequestId(const nsAString& aPaymentRequestId)
 {
   mPaymentRequestId = aPaymentRequestId;
   return NS_OK;
 }
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -893,17 +893,16 @@ protected:
   bool mIsActive;
   bool mIsPrerendered;
   bool mIsAppTab;
   bool mUseGlobalHistory;
   bool mInPrivateBrowsing;
   bool mUseRemoteTabs;
   bool mDeviceSizeIsPageSize;
   bool mWindowDraggingAllowed;
-  bool mBlockNavigation;
 
   // Because scriptability depends on the mAllowJavascript values of our
   // ancestors, we cache the effective scriptability and recompute it when
   // it might have changed;
   bool mCanExecuteScripts;
   void RecomputeCanExecuteScripts();
 
   // This boolean is set to true right before we fire pagehide and generally
--- a/dom/animation/AnimationPlayer.cpp
+++ b/dom/animation/AnimationPlayer.cpp
@@ -437,16 +437,19 @@ AnimationPlayer::DoPlay()
 
   if (mHoldTime.IsNull()) {
     return;
   }
 
   // Clear ready promise. We'll create a new one lazily.
   mReady = nullptr;
 
+  // Clear the start time until we resolve a new one
+  mStartTime.SetNull();
+
   mIsPending = true;
 
   nsIDocument* doc = GetRenderedDocument();
   if (!doc) {
     StartOnNextTick(Nullable<TimeDuration>());
     return;
   }
 
--- a/dom/animation/test/css-animations/test_animation-player-starttime.html
+++ b/dom/animation/test/css-animations/test_animation-player-starttime.html
@@ -194,37 +194,16 @@ function EventWatcher(watchedNode, event
 // animation. The terms can be found here:
 //
 //   http://w3c.github.io/web-animations/#animation-node-phases-and-states
 //
 // Note the distinction between "player start time" and "animation start time".
 // The former is the start of the start delay. The latter is the start of the
 // active interval. (If there is no delay, they are the same.)
 
-// Called when startTime is set to the time the start delay would ideally
-// start (not accounting for any delay to next paint tick).
-function checkStateOnSettingStartTimeToAnimationCreationTime(player)
-{
-  // We don't test player.startTime since our caller just set it.
-
-  assert_equals(player.playState, 'running',
-    'AnimationPlayer.playState should be "running" at the start of ' +
-    'the start delay');
-
-  assert_equals(player.source.target.style.animationPlayState, 'running',
-    'AnimationPlayer.source.target.style.animationPlayState should be ' +
-    '"running" at the start of the start delay');
-
-  var div = player.source.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'at the beginning of the start delay');
-}
-
 // Called when the ready Promise's callbacks should happen
 function checkStateOnReadyPromiseResolved(player)
 {
   assert_less_than_equal(player.startTime, player.timeline.currentTime,
     'AnimationPlayer.startTime should be less than the timeline\'s ' +
     'currentTime on the first paint tick after animation creation');
 
   assert_equals(player.playState, 'running',
@@ -289,51 +268,102 @@ function checkStateAtActiveIntervalEndTi
   var div = player.source.target;
   var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
   assert_equals(marginLeft, UNANIMATED_POSITION,
     'the computed value of margin-left should be unaffected ' +
     'by the animation at the end of the active duration when the ' +
     'animation-fill-mode is none');
 }
 
+test(function(t)
+{
+  var div = addDiv(t, { 'style': 'animation: anim 100s' });
+  var player = div.getAnimationPlayers()[0];
+  assert_equals(player.startTime, null, 'startTime is unresolved');
+}, 'startTime of a newly created (play-pending) animation is unresolved');
+
+test(function(t)
+{
+  var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
+  var player = div.getAnimationPlayers()[0];
+  assert_equals(player.startTime, null, 'startTime is unresolved');
+}, 'startTime of a newly created (pause-pending) animation is unresolved');
+
+async_test(function(t)
+{
+  var div = addDiv(t, { 'style': 'animation: anim 100s' });
+  var player = div.getAnimationPlayers()[0];
+  player.ready.then(t.step_func(function() {
+    assert_true(player.startTime > 0,
+                'startTime is resolved when running');
+    t.done();
+  }));
+}, 'startTime is resolved when running');
+
+async_test(function(t)
+{
+  var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
+  var player = div.getAnimationPlayers()[0];
+  player.ready.then(t.step_func(function() {
+    assert_equals(player.startTime, null,
+                  'startTime is unresolved when paused');
+    t.done();
+  }));
+}, 'startTime is unresolved when paused');
+
+async_test(function(t)
+{
+  var div = addDiv(t, { 'style': 'animation: anim 100s' });
+  var player = div.getAnimationPlayers()[0];
+  player.ready.then(t.step_func(function() {
+    div.style.animationPlayState = 'paused';
+    getComputedStyle(div).animationPlayState;
+    /* FIXME: Switch this on once deferred pausing is enabled
+    assert_not_equals(player.startTime, null,
+                      'startTime is resolved when pause-pending');
+    */
+
+    div.style.animationPlayState = 'running';
+    getComputedStyle(div).animationPlayState;
+    assert_equals(player.startTime, null,
+                  'startTime is unresolved when play-pending');
+    t.done();
+  }));
+}, 'startTime while pause-pending and play-pending');
+
+async_test(function(t)
+{
+  var div = addDiv(t, { 'style': 'animation: anim 100s' });
+  var player = div.getAnimationPlayers()[0];
+  // Seek to end to put us in the finished state
+  // FIXME: Once we implement finish(), use that here.
+  player.currentTime = 100 * 1000;
+  player.ready.then(t.step_func(function() {
+    // Call play() which puts us back in the running state
+    player.play();
+    // FIXME: Enable this once we implement finishing behavior (bug 1074630)
+    /*
+    assert_equals(player.startTime, null, 'startTime is unresolved');
+    */
+    t.done();
+  }));
+}, 'startTime while play-pending from finished state');
+
 
 test(function(t)
 {
   var div = addDiv(t, {'class': 'animated-div'});
-
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var player = div.getAnimationPlayers()[0];
-
-  // Animations shouldn't start until the next paint tick, so:
-  assert_equals(player.startTime, null,
-    'AnimationPlayer.startTime should be unresolved when an animation ' +
-    'is initially created');
-
-  assert_equals(player.playState, "pending",
-    'AnimationPlayer.playState should be "pending" when an animation ' +
-    'is initially created');
-
-  assert_equals(player.source.target.style.animationPlayState, 'running',
-    'AnimationPlayer.source.target.style.animationPlayState should be ' +
-    '"running" when an animation is initially created');
-
-  // XXX Ideally we would have a test to check the ready Promise is initially
-  // unresolved, but currently there is no Web API to do that. Waiting for the
-  // ready Promise with a timeout doesn't work because the resolved callback
-  // will be called (async) regardless of whether the Promise was resolved in
-  // the past or is resolved in the future.
-
   var currentTime = player.timeline.currentTime;
   player.startTime = currentTime;
   assert_approx_equals(player.startTime, currentTime, 0.0001, // rounding error
     'Check setting of startTime actually works');
-
-  checkStateOnSettingStartTimeToAnimationCreationTime(player);
-}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
+}, 'Sanity test to check round-tripping assigning to a new animation\'s ' +
    'startTime');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
 
   div.style.animation = ANIM_PROPERTY_VAL;
--- a/dom/animation/test/css-transitions/test_animation-player-starttime.html
+++ b/dom/animation/test/css-transitions/test_animation-player-starttime.html
@@ -185,33 +185,16 @@ function EventWatcher(watchedNode, event
 // animation. The terms can be found here:
 //
 //   http://w3c.github.io/web-animations/#animation-node-phases-and-states
 //
 // Note the distinction between "player start time" and "animation start time".
 // The former is the start of the start delay. The latter is the start of the
 // active interval. (If there is no delay, they are the same.)
 
-// Called when startTime is set to the time the start delay would ideally
-// start (not accounting for any delay to next paint tick).
-function checkStateOnSettingStartTimeToAnimationCreationTime(player)
-{
-  // We don't test player.startTime since our caller just set it.
-
-  assert_equals(player.playState, 'running',
-    'AnimationPlayer.playState should be "running" at the start of ' +
-    'the start delay');
-
-  var div = player.source.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, INITIAL_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'at the beginning of the start delay');
-}
-
 // Called when the ready Promise's callbacks should happen
 function checkStateOnReadyPromiseResolved(player)
 {
   assert_less_than_equal(player.startTime, player.timeline.currentTime,
     'AnimationPlayer.startTime should be less than the timeline\'s ' +
     'currentTime on the first paint tick after animation creation');
 
   assert_equals(player.playState, 'running',
@@ -263,47 +246,38 @@ function checkStateAtActiveIntervalEndTi
 
   var div = player.source.target;
   var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
   assert_equals(marginLeft, END_POSITION,
     'the computed value of margin-left should be the final transitioned-to ' +
     'value at the end of the active duration');
 }
 
-
 test(function(t)
 {
   var div = addDiv(t, {'class': 'animated-div'});
-
   flushComputedStyle(div);
   div.style.marginLeft = '200px'; // initiate transition
 
   var player = div.getAnimationPlayers()[0];
+  assert_equals(player.startTime, null, 'startTime is unresolved');
+}, 'startTime of a newly created transition is unresolved');
 
-  // Animations shouldn't start until the next paint tick, so:
-  assert_equals(player.startTime, null,
-    'AnimationPlayer.startTime should be unresolved when an animation ' +
-    'is initially created');
 
-  assert_equals(player.playState, "pending",
-    'AnimationPlayer.playState should be "pending" when an animation ' +
-    'is initially created');
+test(function(t)
+{
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
 
-  // XXX Ideally we would have a test to check the ready Promise is initially
-  // unresolved, but currently there is no Web API to do that. Waiting for the
-  // ready Promise with a timeout doesn't work because the resolved callback
-  // will be called (async) regardless of whether the Promise was resolved in
-  // the past or is resolved in the future.
-
+  var player = div.getAnimationPlayers()[0];
   var currentTime = player.timeline.currentTime;
   player.startTime = currentTime;
   assert_approx_equals(player.startTime, currentTime, 0.0001, // rounding error
     'Check setting of startTime actually works');
-
-  checkStateOnSettingStartTimeToAnimationCreationTime(player);
 }, 'Sanity test to check round-tripping assigning to new animation\'s ' +
    'startTime');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   var eventWatcher = new EventWatcher(div, 'transitionend');
 
--- a/dom/base/nsPerformance.cpp
+++ b/dom/base/nsPerformance.cpp
@@ -19,16 +19,17 @@
 #include "PerformanceMeasure.h"
 #include "PerformanceResourceTiming.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/PerformanceBinding.h"
 #include "mozilla/dom/PerformanceTimingBinding.h"
 #include "mozilla/dom/PerformanceNavigationBinding.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/TimeStamp.h"
+#include "js/HeapAPI.h"
 
 #ifdef MOZ_WIDGET_GONK
 #define PERFLOG(msg, ...)  __android_log_print(ANDROID_LOG_INFO, "PerformanceTiming", msg, ##__VA_ARGS__)
 #else
 #define PERFLOG(msg, ...) printf_stderr(msg, ##__VA_ARGS__)
 #endif
 
 using namespace mozilla;
@@ -384,20 +385,37 @@ nsPerformanceNavigation::~nsPerformanceN
 
 JSObject*
 nsPerformanceNavigation::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
 {
   return PerformanceNavigationBinding::Wrap(cx, this, aGivenProto);
 }
 
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(nsPerformance, DOMEventTargetHelper,
-                                   mWindow, mTiming,
-                                   mNavigation, mEntries,
-                                   mParentPerformance)
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsPerformance)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow, mTiming,
+                                mNavigation, mEntries,
+                                mParentPerformance)
+  tmp->mMozMemory = nullptr;
+  mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mTiming,
+                                    mNavigation, mEntries,
+                                    mParentPerformance)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(nsPerformance, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMozMemory)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
 NS_IMPL_ADDREF_INHERITED(nsPerformance, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsPerformance, DOMEventTargetHelper)
 
 nsPerformance::nsPerformance(nsPIDOMWindow* aWindow,
                              nsDOMNavigationTiming* aDOMTiming,
                              nsITimedChannel* aChannel,
                              nsPerformance* aParentPerformance)
   : DOMEventTargetHelper(aWindow),
@@ -407,24 +425,37 @@ nsPerformance::nsPerformance(nsPIDOMWind
     mParentPerformance(aParentPerformance),
     mPrimaryBufferSize(kDefaultBufferSize)
 {
   MOZ_ASSERT(aWindow, "Parent window object should be provided");
 }
 
 nsPerformance::~nsPerformance()
 {
+  mozilla::DropJSObjects(this);
 }
 
 // QueryInterface implementation for nsPerformance
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPerformance)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
+void
+nsPerformance::GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj)
+{
+  if (!mMozMemory) {
+    mMozMemory = js::gc::NewMemoryInfoObject(aCx);
+    if (mMozMemory) {
+      mozilla::HoldJSObjects(this);
+    }
+  }
+
+  aObj.set(mMozMemory);
+}
 
 nsPerformanceTiming*
 nsPerformance::Timing()
 {
   if (!mTiming) {
     // For navigation timing, the third argument (an nsIHtttpChannel) is null
     // since the cross-domain redirect were already checked.
     // The last argument (zero time) for performance.timing is the navigation
--- a/dom/base/nsPerformance.h
+++ b/dom/base/nsPerformance.h
@@ -8,16 +8,17 @@
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Attributes.h"
 #include "nsWrapperCache.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsContentUtils.h"
 #include "nsPIDOMWindow.h"
 #include "js/TypeDecls.h"
+#include "js/RootingAPI.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/DOMEventTargetHelper.h"
 
 class nsITimedChannel;
 class nsPerformance;
 class nsIHttpChannel;
 
 namespace mozilla {
@@ -292,17 +293,17 @@ class nsPerformance final : public mozil
 public:
   typedef mozilla::dom::PerformanceEntry PerformanceEntry;
   nsPerformance(nsPIDOMWindow* aWindow,
                 nsDOMNavigationTiming* aDOMTiming,
                 nsITimedChannel* aChannel,
                 nsPerformance* aParentPerformance);
 
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsPerformance, DOMEventTargetHelper)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsPerformance, DOMEventTargetHelper)
 
   nsDOMNavigationTiming* GetDOMTiming() const
   {
     return mDOMTiming;
   }
 
   nsITimedChannel* GetChannel() const
   {
@@ -339,16 +340,18 @@ public:
   void Mark(const nsAString& aName, mozilla::ErrorResult& aRv);
   void ClearMarks(const mozilla::dom::Optional<nsAString>& aName);
   void Measure(const nsAString& aName,
                const mozilla::dom::Optional<nsAString>& aStartMark,
                const mozilla::dom::Optional<nsAString>& aEndMark,
                mozilla::ErrorResult& aRv);
   void ClearMeasures(const mozilla::dom::Optional<nsAString>& aName);
 
+  void GetMozMemory(JSContext *aCx, JS::MutableHandle<JSObject*> aObj);
+
   IMPL_EVENT_HANDLER(resourcetimingbufferfull)
 
 private:
   ~nsPerformance();
   bool IsPerformanceTimingAttribute(const nsAString& aName);
   DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, mozilla::ErrorResult& aRv);
   DOMTimeMilliSec GetPerformanceTimingFromString(const nsAString& aTimingName);
   DOMHighResTimeStamp ConvertDOMMilliSecToHighRes(const DOMTimeMilliSec aTime);
@@ -359,16 +362,17 @@ private:
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsRefPtr<nsDOMNavigationTiming> mDOMTiming;
   nsCOMPtr<nsITimedChannel> mChannel;
   nsRefPtr<nsPerformanceTiming> mTiming;
   nsRefPtr<nsPerformanceNavigation> mNavigation;
   nsTArray<nsRefPtr<PerformanceEntry> > mEntries;
   nsRefPtr<nsPerformance> mParentPerformance;
   uint64_t mPrimaryBufferSize;
+  JS::Heap<JSObject*> mMozMemory;
 
   static const uint64_t kDefaultBufferSize = 150;
 
   // Helper classes
   class PerformanceEntryComparator {
     public:
       bool Equals(const PerformanceEntry* aElem1,
                   const PerformanceEntry* aElem2) const;
--- a/dom/cache/Action.cpp
+++ b/dom/cache/Action.cpp
@@ -5,23 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/Action.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
-NS_IMPL_ISUPPORTS0(mozilla::dom::cache::Action::Resolver);
-
 void
 Action::CancelOnInitiatingThread()
 {
   NS_ASSERT_OWNINGTHREAD(Action);
-  MOZ_ASSERT(!mCanceled);
+  // It is possible for cancellation to be duplicated.  For example, an
+  // individual Cache could have its Actions canceled and then shutdown
+  // could trigger a second action.
   mCanceled = true;
 }
 
 Action::Action()
   : mCanceled(false)
 {
 }
 
--- a/dom/cache/Action.h
+++ b/dom/cache/Action.h
@@ -13,31 +13,29 @@
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class Action
 {
 public:
-  class Resolver : public nsISupports
+  class Resolver
   {
-  protected:
-    // virtual because deleted through base class pointer
-    virtual ~Resolver() { }
-
   public:
     // Note: Action must drop Resolver ref after calling Resolve()!
     // Note: Must be called on the same thread used to execute
     //       Action::RunOnTarget().
     virtual void Resolve(nsresult aRv) = 0;
 
-    // We must use ISUPPORTS for our refcounting here because sub-classes also
-    // want to inherit interfaces like nsIRunnable.
-    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_IMETHOD_(MozExternalRefCountType)
+    AddRef(void) = 0;
+
+    NS_IMETHOD_(MozExternalRefCountType)
+    Release(void) = 0;
   };
 
   // Execute operations on the target thread.  Once complete call
   // Resolver::Resolve().  This can be done sync or async.
   // Note: Action should hold Resolver ref until its ready to call Resolve().
   // Note: The "target" thread is determined when the Action is scheduled on
   //       Context.  The Action should not assume any particular thread is used.
   virtual void RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo) = 0;
@@ -46,30 +44,32 @@ public:
   // responsible for calling Resolver::Resolve() as normal; either with a
   // normal error code or NS_ERROR_ABORT.  If CancelOnInitiatingThread() is
   // called after Resolve() has already occurred, then the cancel can be
   // ignored.
   //
   // Cancellation is a best effort to stop processing as soon as possible, but
   // does not guarantee the Action will not run.
   //
+  // CancelOnInitiatingThread() may be called more than once.  Subsequent
+  // calls should have no effect.
+  //
   // Default implementation sets an internal cancellation flag that can be
   // queried with IsCanceled().
   virtual void CancelOnInitiatingThread();
 
   // Executed on the initiating thread and is passed the nsresult given to
   // Resolver::Resolve().
   virtual void CompleteOnInitiatingThread(nsresult aRv) { }
 
   // Executed on the initiating thread.  If this Action will operate on the
   // given cache ID then override this to return true.
   virtual bool MatchesCacheId(CacheId aCacheId) const { return false; }
 
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Action)
-  NS_DECL_OWNINGTHREAD
+  NS_INLINE_DECL_REFCOUNTING(cache::Action)
 
 protected:
   Action();
 
   // virtual because deleted through base class pointer
   virtual ~Action();
 
   // Check if this Action has been canceled.  May be called from any thread,
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -1,62 +1,62 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/Context.h"
 
+#include "mozilla/AutoRestore.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/cache/Action.h"
 #include "mozilla/dom/cache/Manager.h"
 #include "mozilla/dom/cache/ManagerId.h"
+#include "mozilla/dom/cache/OfflineStorage.h"
 #include "mozilla/dom/quota/OriginOrPatternString.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "nsIFile.h"
 #include "nsIPrincipal.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 
 namespace {
 
 using mozilla::dom::Nullable;
 using mozilla::dom::cache::QuotaInfo;
+using mozilla::dom::quota::Client;
 using mozilla::dom::quota::OriginOrPatternString;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
 using mozilla::dom::quota::PersistenceType;
 
-// Executed when the context is destroyed to release our lock on the
-// QuotaManager.
+// Release our lock on the QuotaManager directory asynchronously.
 class QuotaReleaseRunnable final : public nsRunnable
 {
 public:
-  QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo, const nsACString& aQuotaId)
+  explicit QuotaReleaseRunnable(const QuotaInfo& aQuotaInfo)
     : mQuotaInfo(aQuotaInfo)
-    , mQuotaId(aQuotaId)
   { }
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
     QuotaManager* qm = QuotaManager::Get();
     MOZ_ASSERT(qm);
     qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
                                 Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
-                                mQuotaId);
+                                mQuotaInfo.mStorageId);
     return NS_OK;
   }
 
 private:
   ~QuotaReleaseRunnable() { }
 
   const QuotaInfo mQuotaInfo;
-  const nsCString mQuotaId;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
@@ -65,30 +65,29 @@ using mozilla::dom::quota::OriginOrPatte
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
 using mozilla::dom::quota::PersistenceType;
 
 // Executed to perform the complicated dance of steps necessary to initialize
 // the QuotaManager.  This must be performed for each origin before any disk
 // IO occurrs.
 class Context::QuotaInitRunnable final : public nsIRunnable
-                                           , public Action::Resolver
 {
 public:
   QuotaInitRunnable(Context* aContext,
                     Manager* aManager,
-                    const nsACString& aQuotaId,
                     Action* aQuotaIOThreadAction)
     : mContext(aContext)
+    , mThreadsafeHandle(aContext->CreateThreadsafeHandle())
     , mManager(aManager)
-    , mQuotaId(aQuotaId)
     , mQuotaIOThreadAction(aQuotaIOThreadAction)
     , mInitiatingThread(NS_GetCurrentThread())
+    , mResult(NS_OK)
     , mState(STATE_INIT)
-    , mResult(NS_OK)
+    , mNeedsQuotaRelease(false)
   {
     MOZ_ASSERT(mContext);
     MOZ_ASSERT(mManager);
     MOZ_ASSERT(mInitiatingThread);
   }
 
   nsresult Dispatch()
   {
@@ -99,36 +98,45 @@ public:
     nsresult rv = NS_DispatchToMainThread(this, nsIThread::DISPATCH_NORMAL);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mState = STATE_COMPLETE;
       Clear();
     }
     return rv;
   }
 
-  virtual void Resolve(nsresult aRv) override
+private:
+  class SyncResolver final : public Action::Resolver
   {
-    // Depending on the error or success path, this can run on either the
-    // main thread or the QuotaManager IO thread.  The IO thread is an
-    // idle thread which may be destroyed and recreated, so its hard to
-    // assert on.
-    MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(aRv));
+  public:
+    SyncResolver()
+      : mResolved(false)
+      , mResult(NS_OK)
+    { }
 
-    mResult = aRv;
-    mState = STATE_COMPLETING;
+    virtual void
+    Resolve(nsresult aRv) override
+    {
+      MOZ_ASSERT(!mResolved);
+      mResolved = true;
+      mResult = aRv;
+    };
 
-    nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL);
-    if (NS_FAILED(rv)) {
-      // Shutdown must be delayed until all Contexts are destroyed.  Crash for
-      // this invariant violation.
-      MOZ_CRASH("Failed to dispatch QuotaInitRunnable to initiating thread.");
-    }
-  }
+    bool Resolved() const { return mResolved; }
+    nsresult Result() const { return mResult; }
+
+  private:
+    ~SyncResolver() { }
 
-private:
+    bool mResolved;
+    nsresult mResult;
+
+    NS_INLINE_DECL_REFCOUNTING(Context::QuotaInitRunnable::SyncResolver, override)
+  };
+
   ~QuotaInitRunnable()
   {
     MOZ_ASSERT(mState == STATE_COMPLETE);
     MOZ_ASSERT(!mContext);
     MOZ_ASSERT(!mQuotaIOThreadAction);
   }
 
   enum State
@@ -147,31 +155,32 @@ private:
     NS_ASSERT_OWNINGTHREAD(Action::Resolver);
     MOZ_ASSERT(mContext);
     mContext = nullptr;
     mManager = nullptr;
     mQuotaIOThreadAction = nullptr;
   }
 
   nsRefPtr<Context> mContext;
+  nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
   nsRefPtr<Manager> mManager;
-  const nsCString mQuotaId;
   nsRefPtr<Action> mQuotaIOThreadAction;
   nsCOMPtr<nsIThread> mInitiatingThread;
-  State mState;
   nsresult mResult;
   QuotaInfo mQuotaInfo;
+  nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
+  State mState;
+  bool mNeedsQuotaRelease;
 
 public:
-  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 };
 
-NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::QuotaInitRunnable,
-                            Action::Resolver, nsIRunnable);
+NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::QuotaInitRunnable, nsIRunnable);
 
 // The QuotaManager init state machine is represented in the following diagram:
 //
 //    +---------------+
 //    |     Start     |      Resolve(error)
 //    | (Orig Thread) +---------------------+
 //    +-------+-------+                     |
 //            |                             |
@@ -186,79 +195,94 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom
 //   +--------+---------+                   |
 //            |                             |
 // +----------v------------+                |
 // |EnsureOriginInitialized| Resolve(error) |
 // |   (Quota IO Thread)   +----------------+
 // +----------+------------+                |
 //            |                             |
 //  +---------v---------+            +------v------+
-//  |      Running      |  Resolve() |  Completing |
+//  |      Running      |            |  Completing |
 //  | (Quota IO Thread) +------------>(Orig Thread)|
 //  +-------------------+            +------+------+
 //                                          |
 //                                    +-----v----+
 //                                    | Complete |
 //                                    +----------+
 //
 // The initialization process proceeds through the main states.  If an error
-// occurs, then we transition back to Completing state back on the original
-// thread.
+// occurs, then we transition to Completing state back on the original thread.
 NS_IMETHODIMP
 Context::QuotaInitRunnable::Run()
 {
   // May run on different threads depending on the state.  See individual
   // state cases for thread assertions.
 
+  nsRefPtr<SyncResolver> resolver = new SyncResolver();
+
   switch(mState) {
     // -----------------------------------
     case STATE_CALL_WAIT_FOR_OPEN_ALLOWED:
     {
       MOZ_ASSERT(NS_IsMainThread());
       QuotaManager* qm = QuotaManager::GetOrCreate();
       if (!qm) {
-        Resolve(NS_ERROR_FAILURE);
-        return NS_OK;
+        resolver->Resolve(NS_ERROR_FAILURE);
+        break;
       }
 
       nsRefPtr<ManagerId> managerId = mManager->GetManagerId();
       nsCOMPtr<nsIPrincipal> principal = managerId->Principal();
       nsresult rv = qm->GetInfoFromPrincipal(principal,
                                              &mQuotaInfo.mGroup,
                                              &mQuotaInfo.mOrigin,
                                              &mQuotaInfo.mIsApp);
       if (NS_WARN_IF(NS_FAILED(rv))) {
-        Resolve(rv);
-        return NS_OK;
+        resolver->Resolve(rv);
+        break;
       }
 
+      QuotaManager::GetStorageId(PERSISTENCE_TYPE_DEFAULT,
+                                 mQuotaInfo.mOrigin,
+                                 Client::DOMCACHE,
+                                 NS_LITERAL_STRING("cache"),
+                                 mQuotaInfo.mStorageId);
+
       // QuotaManager::WaitForOpenAllowed() will hold a reference to us as
       // a callback.  We will then get executed again on the main thread when
       // it is safe to open the quota directory.
       mState = STATE_WAIT_FOR_OPEN_ALLOWED;
       rv = qm->WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mQuotaInfo.mOrigin),
                                   Nullable<PersistenceType>(PERSISTENCE_TYPE_DEFAULT),
-                                  mQuotaId, this);
+                                  mQuotaInfo.mStorageId, this);
       if (NS_FAILED(rv)) {
-        Resolve(rv);
-        return NS_OK;
+        resolver->Resolve(rv);
+        break;
       }
       break;
     }
     // ------------------------------
     case STATE_WAIT_FOR_OPEN_ALLOWED:
     {
       MOZ_ASSERT(NS_IsMainThread());
+
+      mNeedsQuotaRelease = true;
+
       QuotaManager* qm = QuotaManager::Get();
       MOZ_ASSERT(qm);
+
+      nsRefPtr<OfflineStorage> offlineStorage =
+        OfflineStorage::Register(mThreadsafeHandle, mQuotaInfo);
+      mOfflineStorage = new nsMainThreadPtrHolder<OfflineStorage>(offlineStorage);
+
       mState = STATE_ENSURE_ORIGIN_INITIALIZED;
       nsresult rv = qm->IOThread()->Dispatch(this, nsIThread::DISPATCH_NORMAL);
       if (NS_WARN_IF(NS_FAILED(rv))) {
-        Resolve(rv);
-        return NS_OK;
+        resolver->Resolve(rv);
+        break;
       }
       break;
     }
     // ----------------------------------
     case STATE_ENSURE_ORIGIN_INITIALIZED:
     {
       // Can't assert quota IO thread because its an idle thread that can get
       // recreated.  At least assert we're not on main thread or owning thread.
@@ -268,75 +292,94 @@ Context::QuotaInitRunnable::Run()
       QuotaManager* qm = QuotaManager::Get();
       MOZ_ASSERT(qm);
       nsresult rv = qm->EnsureOriginIsInitialized(PERSISTENCE_TYPE_DEFAULT,
                                                   mQuotaInfo.mGroup,
                                                   mQuotaInfo.mOrigin,
                                                   mQuotaInfo.mIsApp,
                                                   getter_AddRefs(mQuotaInfo.mDir));
       if (NS_FAILED(rv)) {
-        Resolve(rv);
-        return NS_OK;
+        resolver->Resolve(rv);
+        break;
       }
 
       mState = STATE_RUNNING;
 
       if (!mQuotaIOThreadAction) {
-        Resolve(NS_OK);
-        return NS_OK;
+        resolver->Resolve(NS_OK);
+        break;
       }
 
-      // Execute the provided initialization Action.  We pass ourselves as the
-      // Resolver.  The Action must either call Resolve() immediately or hold
-      // a ref to us and call Resolve() later.
-      mQuotaIOThreadAction->RunOnTarget(this, mQuotaInfo);
+      // Execute the provided initialization Action.  The Action must Resolve()
+      // before returning.
+      mQuotaIOThreadAction->RunOnTarget(resolver, mQuotaInfo);
+      MOZ_ASSERT(resolver->Resolved());
 
       break;
     }
     // -------------------
     case STATE_COMPLETING:
     {
       NS_ASSERT_OWNINGTHREAD(Action::Resolver);
       if (mQuotaIOThreadAction) {
         mQuotaIOThreadAction->CompleteOnInitiatingThread(mResult);
       }
-      mContext->OnQuotaInit(mResult, mQuotaInfo);
+      mContext->OnQuotaInit(mResult, mQuotaInfo, mOfflineStorage);
       mState = STATE_COMPLETE;
+
+      if (mNeedsQuotaRelease) {
+        // Unlock the quota dir if we locked it previously
+        nsCOMPtr<nsIRunnable> runnable = new QuotaReleaseRunnable(mQuotaInfo);
+        MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
+      }
+
       // Explicitly cleanup here as the destructor could fire on any of
       // the threads we have bounced through.
       Clear();
       break;
     }
     // -----
     default:
     {
       MOZ_CRASH("unexpected state in QuotaInitRunnable");
-      break;
     }
   }
 
+  if (resolver->Resolved()) {
+    MOZ_ASSERT(mState == STATE_RUNNING || NS_FAILED(resolver->Result()));
+
+    MOZ_ASSERT(NS_SUCCEEDED(mResult));
+    mResult = resolver->Result();
+
+    mState = STATE_COMPLETING;
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
+  }
+
   return NS_OK;
 }
 
 // Runnable wrapper around Action objects dispatched on the Context.  This
 // runnable executes the Action on the appropriate threads while the Context
 // is initialized.
 class Context::ActionRunnable final : public nsIRunnable
                                     , public Action::Resolver
+                                    , public Context::Activity
 {
 public:
   ActionRunnable(Context* aContext, nsIEventTarget* aTarget, Action* aAction,
                  const QuotaInfo& aQuotaInfo)
     : mContext(aContext)
     , mTarget(aTarget)
     , mAction(aAction)
     , mQuotaInfo(aQuotaInfo)
     , mInitiatingThread(NS_GetCurrentThread())
     , mState(STATE_INIT)
     , mResult(NS_OK)
+    , mExecutingRunOnTarget(false)
   {
     MOZ_ASSERT(mContext);
     MOZ_ASSERT(mTarget);
     MOZ_ASSERT(mAction);
     MOZ_ASSERT(mQuotaInfo.mDir);
     MOZ_ASSERT(mInitiatingThread);
   }
 
@@ -349,99 +392,123 @@ public:
     nsresult rv = mTarget->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       mState = STATE_COMPLETE;
       Clear();
     }
     return rv;
   }
 
-  bool MatchesCacheId(CacheId aCacheId) {
+  virtual bool
+  MatchesCacheId(CacheId aCacheId) const override
+  {
     NS_ASSERT_OWNINGTHREAD(Action::Resolver);
     return mAction->MatchesCacheId(aCacheId);
   }
 
-  void Cancel()
+  virtual void
+  Cancel() override
   {
     NS_ASSERT_OWNINGTHREAD(Action::Resolver);
     mAction->CancelOnInitiatingThread();
   }
 
   virtual void Resolve(nsresult aRv) override
   {
     MOZ_ASSERT(mTarget == NS_GetCurrentThread());
     MOZ_ASSERT(mState == STATE_RUNNING);
+
     mResult = aRv;
-    mState = STATE_COMPLETING;
-    nsresult rv = mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL);
-    if (NS_FAILED(rv)) {
-      // Shutdown must be delayed until all Contexts are destroyed.  Crash
-      // for this invariant violation.
-      MOZ_CRASH("Failed to dispatch ActionRunnable to initiating thread.");
+
+    // We ultimately must complete on the initiating thread, but bounce through
+    // the current thread again to ensure that we don't destroy objects and
+    // state out from under the currently running action's stack.
+    mState = STATE_RESOLVING;
+
+    // If we were resolved synchronously within Action::RunOnTarget() then we
+    // can avoid a thread bounce and just resolve once RunOnTarget() returns.
+    // The Run() method will handle this by looking at mState after
+    // RunOnTarget() returns.
+    if (mExecutingRunOnTarget) {
+      return;
     }
+
+    // Otherwise we are in an asynchronous resolve.  And must perform a thread
+    // bounce to run on the target thread again.
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      mTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
   }
 
 private:
   ~ActionRunnable()
   {
     MOZ_ASSERT(mState == STATE_COMPLETE);
     MOZ_ASSERT(!mContext);
     MOZ_ASSERT(!mAction);
   }
 
   void Clear()
   {
     NS_ASSERT_OWNINGTHREAD(Action::Resolver);
     MOZ_ASSERT(mContext);
     MOZ_ASSERT(mAction);
-    mContext->OnActionRunnableComplete(this);
+    mContext->RemoveActivity(this);
     mContext = nullptr;
     mAction = nullptr;
   }
 
   enum State
   {
     STATE_INIT,
     STATE_RUN_ON_TARGET,
     STATE_RUNNING,
+    STATE_RESOLVING,
     STATE_COMPLETING,
     STATE_COMPLETE
   };
 
   nsRefPtr<Context> mContext;
   nsCOMPtr<nsIEventTarget> mTarget;
   nsRefPtr<Action> mAction;
   const QuotaInfo mQuotaInfo;
   nsCOMPtr<nsIThread> mInitiatingThread;
   State mState;
   nsresult mResult;
 
+  // Only accessible on target thread;
+  bool mExecutingRunOnTarget;
+
 public:
-  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 };
 
-NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom::cache::Context::ActionRunnable,
-                            Action::Resolver, nsIRunnable);
+NS_IMPL_ISUPPORTS(mozilla::dom::cache::Context::ActionRunnable, nsIRunnable);
 
 // The ActionRunnable has a simpler state machine.  It basically needs to run
 // the action on the target thread and then complete on the original thread.
 //
 //   +-------------+
 //   |    Start    |
 //   |(Orig Thread)|
 //   +-----+-------+
 //         |
 // +-------v---------+
 // |  RunOnTarget    |
-// |Target IO Thread)+-------------------------------+
-// +-------+---------+                               |
-//         |                                         |
-// +-------v----------+ Resolve()            +-------v-----+
-// |     Running      |                      |  Completing |
+// |Target IO Thread)+---+ Resolve()
+// +-------+---------+   |
+//         |             |
+// +-------v----------+  |
+// |     Running      |  |
+// |(Target IO Thread)|  |
+// +------------------+  |
+//         | Resolve()   |
+// +-------v----------+  |
+// |     Resolving    <--+                   +-------------+
+// |                  |                      |  Completing |
 // |(Target IO Thread)+---------------------->(Orig Thread)|
 // +------------------+                      +-------+-----+
 //                                                   |
 //                                                   |
 //                                              +----v---+
 //                                              |Complete|
 //                                              +--------+
 //
@@ -452,18 +519,49 @@ NS_IMPL_ISUPPORTS_INHERITED(mozilla::dom
 NS_IMETHODIMP
 Context::ActionRunnable::Run()
 {
   switch(mState) {
     // ----------------------
     case STATE_RUN_ON_TARGET:
     {
       MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
+      MOZ_ASSERT(!mExecutingRunOnTarget);
+
+      // Note that we are calling RunOnTarget().  This lets us detect
+      // if Resolve() is called synchronously.
+      AutoRestore<bool> executingRunOnTarget(mExecutingRunOnTarget);
+      mExecutingRunOnTarget = true;
+
       mState = STATE_RUNNING;
       mAction->RunOnTarget(this, mQuotaInfo);
+
+      // Resolve was called synchronously from RunOnTarget().  We can
+      // immediately move to completing now since we are sure RunOnTarget()
+      // completed.
+      if (mState == STATE_RESOLVING) {
+        // Use recursion instead of switch case fall-through...  Seems slightly
+        // easier to understand.
+        Run();
+      }
+
+      break;
+    }
+    // -----------------
+    case STATE_RESOLVING:
+    {
+      MOZ_ASSERT(NS_GetCurrentThread() == mTarget);
+      // The call to Action::RunOnTarget() must have returned now if we
+      // are running on the target thread again.  We may now proceed
+      // with completion.
+      mState = STATE_COMPLETING;
+      // Shutdown must be delayed until all Contexts are destroyed.  Crash
+      // for this invariant violation.
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+        mInitiatingThread->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
       break;
     }
     // -------------------
     case STATE_COMPLETING:
     {
       NS_ASSERT_OWNINGTHREAD(Action::Resolver);
       mAction->CompleteOnInitiatingThread(mResult);
       mState = STATE_COMPLETE;
@@ -477,25 +575,117 @@ Context::ActionRunnable::Run()
     {
       MOZ_CRASH("unexpected state in ActionRunnable");
       break;
     }
   }
   return NS_OK;
 }
 
+void
+Context::ThreadsafeHandle::AllowToClose()
+{
+  if (mOwningThread == NS_GetCurrentThread()) {
+    AllowToCloseOnOwningThread();
+    return;
+  }
+
+  // Dispatch is guaranteed to succeed here because we block shutdown until
+  // all Contexts have been destroyed.
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &ThreadsafeHandle::AllowToCloseOnOwningThread);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
+}
+
+void
+Context::ThreadsafeHandle::InvalidateAndAllowToClose()
+{
+  if (mOwningThread == NS_GetCurrentThread()) {
+    InvalidateAndAllowToCloseOnOwningThread();
+    return;
+  }
+
+  // Dispatch is guaranteed to succeed here because we block shutdown until
+  // all Contexts have been destroyed.
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewRunnableMethod(this, &ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
+}
+
+Context::ThreadsafeHandle::ThreadsafeHandle(Context* aContext)
+  : mStrongRef(aContext)
+  , mWeakRef(aContext)
+  , mOwningThread(NS_GetCurrentThread())
+{
+}
+
+Context::ThreadsafeHandle::~ThreadsafeHandle()
+{
+  // Normally we only touch mStrongRef on the owning thread.  This is safe,
+  // however, because when we do use mStrongRef on the owning thread we are
+  // always holding a strong ref to the ThreadsafeHandle via the owning
+  // runnable.  So we cannot run the ThreadsafeHandle destructor simultaneously.
+  if (!mStrongRef || mOwningThread == NS_GetCurrentThread()) {
+    return;
+  }
+
+  // Dispatch is guaranteed to succeed here because we block shutdown until
+  // all Contexts have been destroyed.
+  nsCOMPtr<nsIRunnable> runnable =
+    NS_NewNonOwningRunnableMethod(mStrongRef.forget().take(), &Context::Release);
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+    mOwningThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
+}
+
+void
+Context::ThreadsafeHandle::AllowToCloseOnOwningThread()
+{
+  MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
+  // A Context "closes" when its ref count drops to zero.  Dropping this
+  // strong ref is necessary, but not sufficient for the close to occur.
+  // Any outstanding IO will continue and keep the Context alive.  Once
+  // the Context is idle, it will be destroyed.
+  mStrongRef = nullptr;
+}
+
+void
+Context::ThreadsafeHandle::InvalidateAndAllowToCloseOnOwningThread()
+{
+  MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
+  // Cancel the Context through the weak reference.  This means we can
+  // allow the Context to close by dropping the strong ref, but then
+  // still cancel ongoing IO if necessary.
+  if (mWeakRef) {
+    mWeakRef->Invalidate();
+  }
+  // We should synchronously have AllowToCloseOnOwningThread called when
+  // the Context is canceled.
+  MOZ_ASSERT(!mStrongRef);
+}
+
+void
+Context::ThreadsafeHandle::ContextDestroyed(Context* aContext)
+{
+  MOZ_ASSERT(mOwningThread == NS_GetCurrentThread());
+  MOZ_ASSERT(!mStrongRef);
+  MOZ_ASSERT(mWeakRef);
+  MOZ_ASSERT(mWeakRef == aContext);
+  mWeakRef = nullptr;
+}
+
 // static
 already_AddRefed<Context>
 Context::Create(Manager* aManager, Action* aQuotaIOThreadAction)
 {
   nsRefPtr<Context> context = new Context(aManager);
 
   nsRefPtr<QuotaInitRunnable> runnable =
-    new QuotaInitRunnable(context, aManager, NS_LITERAL_CSTRING("Cache"),
-                          aQuotaIOThreadAction);
+    new QuotaInitRunnable(context, aManager, aQuotaIOThreadAction);
   nsresult rv = runnable->Dispatch();
   if (NS_FAILED(rv)) {
     // Shutdown must be delayed until all Contexts are destroyed.  Shutdown
     // must also prevent any new Contexts from being constructed.  Crash
     // for this invariant violation.
     MOZ_CRASH("Failed to dispatch QuotaInitRunnable.");
   }
 
@@ -530,52 +720,71 @@ Context::Dispatch(nsIEventTarget* aTarge
 }
 
 void
 Context::CancelAll()
 {
   NS_ASSERT_OWNINGTHREAD(Context);
   mState = STATE_CONTEXT_CANCELED;
   mPendingActions.Clear();
-  for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
-    nsRefPtr<ActionRunnable> runnable = mActionRunnables[i];
-    runnable->Cancel();
+  {
+    ActivityList::ForwardIterator iter(mActivityList);
+    while (iter.HasMore()) {
+      iter.GetNext()->Cancel();
+    }
+  }
+  AllowToClose();
+}
+
+void
+Context::Invalidate()
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  mManager->Invalidate();
+  CancelAll();
+}
+
+void
+Context::AllowToClose()
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  if (mThreadsafeHandle) {
+    mThreadsafeHandle->AllowToClose();
   }
 }
 
 void
 Context::CancelForCacheId(CacheId aCacheId)
 {
   NS_ASSERT_OWNINGTHREAD(Context);
-  for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
+
+  // Remove matching pending actions
+  for (int32_t i = mPendingActions.Length() - 1; i >= 0; --i) {
     if (mPendingActions[i].mAction->MatchesCacheId(aCacheId)) {
       mPendingActions.RemoveElementAt(i);
     }
   }
-  for (uint32_t i = 0; i < mActionRunnables.Length(); ++i) {
-    nsRefPtr<ActionRunnable> runnable = mActionRunnables[i];
-    if (runnable->MatchesCacheId(aCacheId)) {
-      runnable->Cancel();
+
+  // Cancel activities and let them remove themselves
+  ActivityList::ForwardIterator iter(mActivityList);
+  while (iter.HasMore()) {
+    Activity* activity = iter.GetNext();
+    if (activity->MatchesCacheId(aCacheId)) {
+      activity->Cancel();
     }
   }
 }
 
 Context::~Context()
 {
   NS_ASSERT_OWNINGTHREAD(Context);
   MOZ_ASSERT(mManager);
 
-  // Unlock the quota dir as we go out of scope.
-  nsCOMPtr<nsIRunnable> runnable =
-    new QuotaReleaseRunnable(mQuotaInfo, NS_LITERAL_CSTRING("Cache"));
-  nsresult rv = NS_DispatchToMainThread(runnable, nsIThread::DISPATCH_NORMAL);
-  if (NS_FAILED(rv)) {
-    // Shutdown must be delayed until all Contexts are destroyed.  Crash
-    // for this invariant violation.
-    MOZ_CRASH("Failed to dispatch QuotaReleaseRunnable to main thread.");
+  if (mThreadsafeHandle) {
+    mThreadsafeHandle->ContextDestroyed(this);
   }
 
   mManager->RemoveContext(this);
 }
 
 void
 Context::DispatchAction(nsIEventTarget* aTarget, Action* aAction)
 {
@@ -584,47 +793,75 @@ Context::DispatchAction(nsIEventTarget* 
   nsRefPtr<ActionRunnable> runnable =
     new ActionRunnable(this, aTarget, aAction, mQuotaInfo);
   nsresult rv = runnable->Dispatch();
   if (NS_FAILED(rv)) {
     // Shutdown must be delayed until all Contexts are destroyed.  Crash
     // for this invariant violation.
     MOZ_CRASH("Failed to dispatch ActionRunnable to target thread.");
   }
-  mActionRunnables.AppendElement(runnable);
+  AddActivity(runnable);
 }
 
 void
-Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo)
+Context::OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
+                     nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage)
 {
   NS_ASSERT_OWNINGTHREAD(Context);
 
   mQuotaInfo = aQuotaInfo;
 
+  // Always save the offline storage to ensure QuotaManager does not shutdown
+  // before the Context has gone away.
+  MOZ_ASSERT(!mOfflineStorage);
+  mOfflineStorage = aOfflineStorage;
+
   if (mState == STATE_CONTEXT_CANCELED || NS_FAILED(aRv)) {
     for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
       mPendingActions[i].mAction->CompleteOnInitiatingThread(aRv);
     }
     mPendingActions.Clear();
+    mThreadsafeHandle->AllowToClose();
     // Context will destruct after return here and last ref is released.
     return;
   }
 
   MOZ_ASSERT(mState == STATE_CONTEXT_INIT);
   mState = STATE_CONTEXT_READY;
 
   for (uint32_t i = 0; i < mPendingActions.Length(); ++i) {
     DispatchAction(mPendingActions[i].mTarget, mPendingActions[i].mAction);
   }
   mPendingActions.Clear();
 }
 
 void
-Context::OnActionRunnableComplete(ActionRunnable* aActionRunnable)
+Context::AddActivity(Activity* aActivity)
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  MOZ_ASSERT(aActivity);
+  MOZ_ASSERT(!mActivityList.Contains(aActivity));
+  mActivityList.AppendElement(aActivity);
+}
+
+void
+Context::RemoveActivity(Activity* aActivity)
 {
   NS_ASSERT_OWNINGTHREAD(Context);
-  MOZ_ASSERT(aActionRunnable);
-  MOZ_ALWAYS_TRUE(mActionRunnables.RemoveElement(aActionRunnable));
+  MOZ_ASSERT(aActivity);
+  MOZ_ALWAYS_TRUE(mActivityList.RemoveElement(aActivity));
+  MOZ_ASSERT(!mActivityList.Contains(aActivity));
+}
+
+already_AddRefed<Context::ThreadsafeHandle>
+Context::CreateThreadsafeHandle()
+{
+  NS_ASSERT_OWNINGTHREAD(Context);
+  if (!mThreadsafeHandle) {
+    mThreadsafeHandle = new ThreadsafeHandle(this);
+  }
+  nsRefPtr<ThreadsafeHandle> ref = mThreadsafeHandle;
+  return ref.forget();
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/Context.h
+++ b/dom/cache/Context.h
@@ -6,71 +6,153 @@
 
 #ifndef mozilla_dom_cache_Context_h
 #define mozilla_dom_cache_Context_h
 
 #include "mozilla/dom/cache/Types.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsISupportsImpl.h"
+#include "nsProxyRelease.h"
 #include "nsString.h"
 #include "nsTArray.h"
+#include "nsTObserverArray.h"
 
 class nsIEventTarget;
+class nsIThread;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class Action;
 class Manager;
+class OfflineStorage;
 
 // The Context class is RAII-style class for managing IO operations within the
 // Cache.
 //
 // When a Context is created it performs the complicated steps necessary to
 // initialize the QuotaManager.  Action objects dispatched on the Context are
 // delayed until this initialization is complete.  They are then allow to
 // execute on any specified thread.  Once all references to the Context are
 // gone, then the steps necessary to release the QuotaManager are performed.
-// Since pending Action objects reference the Context, this allows overlapping
-// IO to opportunistically run without re-initializing the QuotaManager again.
+// After initialization the Context holds a self reference, so it will stay
+// alive until one of three conditions occur:
+//
+//  1) The Manager will call Context::AllowToClose() when all of the actors
+//     have removed themselves as listener.  This means an idle context with
+//     no active DOM objects will close gracefully.
+//  2) The QuotaManager invalidates the storage area so it can delete the
+//     files.  In this case the OfflineStorage calls Cache::Invalidate() which
+//     in turn cancels all existing Action objects and then marks the Manager
+//     as invalid.
+//  3) Browser shutdown occurs and the Manager calls Context::CancelAll().
+//
+// In either case, though, the Action objects must be destroyed first to
+// allow the Context to be destroyed.
 //
 // While the Context performs operations asynchronously on threads, all of
 // methods in its public interface must be called on the same thread
 // originally used to create the Context.
 //
 // As an invariant, all Context objects must be destroyed before permitting
 // the "profile-before-change" shutdown event to complete.  This is ensured
 // via the code in ShutdownObserver.cpp.
 class Context final
 {
 public:
+  // Define a class allowing other threads to hold the Context alive.  This also
+  // allows these other threads to safely close or cancel the Context.
+  class ThreadsafeHandle final
+  {
+    friend class Context;
+  public:
+    void AllowToClose();
+    void InvalidateAndAllowToClose();
+  private:
+    explicit ThreadsafeHandle(Context* aContext);
+    ~ThreadsafeHandle();
+
+    // disallow copying
+    ThreadsafeHandle(const ThreadsafeHandle&) = delete;
+    ThreadsafeHandle& operator=(const ThreadsafeHandle&) = delete;
+
+    void AllowToCloseOnOwningThread();
+    void InvalidateAndAllowToCloseOnOwningThread();
+
+    void ContextDestroyed(Context* aContext);
+
+    // Cleared to allow the Context to close.  Only safe to access on
+    // owning thread.
+    nsRefPtr<Context> mStrongRef;
+
+    // Used to support cancelation even while the Context is already allowed
+    // to close.  Cleared by ~Context() calling ContextDestroyed().  Only
+    // safe to access on owning thread.
+    Context* mWeakRef;
+
+    nsCOMPtr<nsIThread> mOwningThread;
+
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(cache::Context::ThreadsafeHandle)
+  };
+
+  // Different objects hold references to the Context while some work is being
+  // performed asynchronously.  These objects must implement the Activity
+  // interface and register themselves with the AddActivity().  When they are
+  // destroyed they must call RemoveActivity().  This allows the Context to
+  // cancel any outstanding Activity work when the Context is cancelled.
+  class Activity
+  {
+  public:
+    virtual void Cancel() = 0;
+    virtual bool MatchesCacheId(CacheId aCacheId) const = 0;
+  };
+
+  // Create a Context attached to the given Manager.  The given Action
+  // will run on the QuotaManager IO thread.  Note, this Action must
+  // be execute synchronously.
   static already_AddRefed<Context>
   Create(Manager* aManager, Action* aQuotaIOThreadAction);
 
   // Execute given action on the target once the quota manager has been
   // initialized.
   //
   // Only callable from the thread that created the Context.
   void Dispatch(nsIEventTarget* aTarget, Action* aAction);
 
   // Cancel any Actions running or waiting to run.  This should allow the
   // Context to be released and Listener::RemoveContext() will be called
   // when complete.
   //
   // Only callable from the thread that created the Context.
   void CancelAll();
 
+  // Like CancelAll(), but also marks the Manager as "invalid".
+  void Invalidate();
+
+  // Remove any self references and allow the Context to be released when
+  // there are no more Actions to process.
+  void AllowToClose();
+
   // Cancel any Actions running or waiting to run that operate on the given
   // cache ID.
   //
   // Only callable from the thread that created the Context.
   void CancelForCacheId(CacheId aCacheId);
 
+  void AddActivity(Activity* aActivity);
+  void RemoveActivity(Activity* aActivity);
+
+  const QuotaInfo&
+  GetQuotaInfo() const
+  {
+    return mQuotaInfo;
+  }
+
 private:
   class QuotaInitRunnable;
   class ActionRunnable;
 
   enum State
   {
     STATE_CONTEXT_INIT,
     STATE_CONTEXT_READY,
@@ -81,26 +163,38 @@ private:
   {
     nsCOMPtr<nsIEventTarget> mTarget;
     nsRefPtr<Action> mAction;
   };
 
   explicit Context(Manager* aManager);
   ~Context();
   void DispatchAction(nsIEventTarget* aTarget, Action* aAction);
-  void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo);
-  void OnActionRunnableComplete(ActionRunnable* const aAction);
+  void OnQuotaInit(nsresult aRv, const QuotaInfo& aQuotaInfo,
+                   nsMainThreadPtrHandle<OfflineStorage>& aOfflineStorage);
+
+  already_AddRefed<ThreadsafeHandle>
+  CreateThreadsafeHandle();
 
   nsRefPtr<Manager> mManager;
   State mState;
   QuotaInfo mQuotaInfo;
   nsTArray<PendingAction> mPendingActions;
 
-  // weak refs since ~ActionRunnable() removes itself from this list
-  nsTArray<ActionRunnable*> mActionRunnables;
+  // Weak refs since activites must remove themselves from this list before
+  // being destroyed by calling RemoveActivity().
+  typedef nsTObserverArray<Activity*> ActivityList;
+  ActivityList mActivityList;
+
+  // The ThreadsafeHandle may have a strong ref back to us.  This creates
+  // a ref-cycle that keeps the Context alive.  The ref-cycle is broken
+  // when ThreadsafeHandle::AllowToClose() is called.
+  nsRefPtr<ThreadsafeHandle> mThreadsafeHandle;
+
+  nsMainThreadPtrHandle<OfflineStorage> mOfflineStorage;
 
 public:
   NS_INLINE_DECL_REFCOUNTING(cache::Context)
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -17,18 +17,18 @@
 #include "nsCRT.h"
 #include "nsHttp.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 
-const int32_t DBSchema::kMaxWipeSchemaVersion = 3;
-const int32_t DBSchema::kLatestSchemaVersion = 3;
+const int32_t DBSchema::kMaxWipeSchemaVersion = 4;
+const int32_t DBSchema::kLatestSchemaVersion = 4;
 const int32_t DBSchema::kMaxEntriesPerStatement = 255;
 
 using mozilla::void_t;
 
 // static
 nsresult
 DBSchema::CreateSchema(mozIStorageConnection* aConn)
 {
@@ -84,16 +84,17 @@ DBSchema::CreateSchema(mozIStorageConnec
         "id INTEGER NOT NULL PRIMARY KEY, "
         "request_method TEXT NOT NULL, "
         "request_url TEXT NOT NULL, "
         "request_url_no_query TEXT NOT NULL, "
         "request_referrer TEXT NOT NULL, "
         "request_headers_guard INTEGER NOT NULL, "
         "request_mode INTEGER NOT NULL, "
         "request_credentials INTEGER NOT NULL, "
+        "request_cache INTEGER NOT NULL, "
         "request_body_id TEXT NULL, "
         "response_type INTEGER NOT NULL, "
         "response_url TEXT NOT NULL, "
         "response_status INTEGER NOT NULL, "
         "response_status_text TEXT NOT NULL, "
         "response_headers_guard INTEGER NOT NULL, "
         "response_body_id TEXT NULL, "
         "response_security_info BLOB NULL, "
@@ -965,26 +966,27 @@ DBSchema::InsertEntry(mozIStorageConnect
     "INSERT INTO entries ("
       "request_method, "
       "request_url, "
       "request_url_no_query, "
       "request_referrer, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
+      "request_cache, "
       "request_body_id, "
       "response_type, "
       "response_url, "
       "response_status, "
       "response_status_text, "
       "response_headers_guard, "
       "response_body_id, "
       "response_security_info, "
       "cache_id "
-    ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)"
+    ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)"
   ), getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindUTF8StringParameter(0, aRequest.method());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindStringParameter(1, aRequest.url());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -1001,44 +1003,48 @@ DBSchema::InsertEntry(mozIStorageConnect
 
   rv = state->BindInt32Parameter(5, static_cast<int32_t>(aRequest.mode()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32Parameter(6,
     static_cast<int32_t>(aRequest.credentials()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = BindId(state, 7, aRequestBodyId);
+  rv = state->BindInt32Parameter(7,
+    static_cast<int32_t>(aRequest.requestCache()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(8, static_cast<int32_t>(aResponse.type()));
+  rv = BindId(state, 8, aRequestBodyId);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(9, static_cast<int32_t>(aResponse.type()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindStringParameter(9, aResponse.url());
+  rv = state->BindStringParameter(10, aResponse.url());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(10, aResponse.status());
+  rv = state->BindInt32Parameter(11, aResponse.status());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindUTF8StringParameter(11, aResponse.statusText());
+  rv = state->BindUTF8StringParameter(12, aResponse.statusText());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(12,
+  rv = state->BindInt32Parameter(13,
     static_cast<int32_t>(aResponse.headersGuard()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = BindId(state, 13, aResponseBodyId);
+  rv = BindId(state, 14, aResponseBodyId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindBlobParameter(14, reinterpret_cast<const uint8_t*>
+  rv = state->BindBlobParameter(15, reinterpret_cast<const uint8_t*>
                                   (aResponse.securityInfo().get()),
                                 aResponse.securityInfo().Length());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(15, aCacheId);
+  rv = state->BindInt32Parameter(16, aCacheId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT last_insert_rowid()"
   ), getter_AddRefs(state));
@@ -1214,16 +1220,17 @@ DBSchema::ReadRequest(mozIStorageConnect
     "SELECT "
       "request_method, "
       "request_url, "
       "request_url_no_query, "
       "request_referrer, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
+      "request_cache, "
       "request_body_id "
     "FROM entries "
     "WHERE id=?1;"
   ), getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32Parameter(0, aEntryId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -1256,23 +1263,29 @@ DBSchema::ReadRequest(mozIStorageConnect
   aSavedRequestOut->mValue.mode() = static_cast<RequestMode>(mode);
 
   int32_t credentials;
   rv = state->GetInt32(6, &credentials);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.credentials() =
     static_cast<RequestCredentials>(credentials);
 
+  int32_t requestCache;
+  rv = state->GetInt32(7, &requestCache);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.requestCache() =
+    static_cast<RequestCache>(requestCache);
+
   bool nullBody = false;
-  rv = state->GetIsNull(7, &nullBody);
+  rv = state->GetIsNull(8, &nullBody);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mHasBodyId = !nullBody;
 
   if (aSavedRequestOut->mHasBodyId) {
-    rv = ExtractId(state, 7, &aSavedRequestOut->mBodyId);
+    rv = ExtractId(state, 8, &aSavedRequestOut->mBodyId);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value "
     "FROM request_headers "
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -172,17 +172,20 @@ public:
     mozilla::ipc::AssertIsOnBackgroundThread();
 
     nsresult rv = MaybeCreateInstance();
     if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; }
 
     ManagerList::ForwardIterator iter(sFactory->mManagerList);
     while (iter.HasMore()) {
       nsRefPtr<Manager> manager = iter.GetNext();
-      if (*manager->mManagerId == *aManagerId) {
+      // If there is an invalid Manager finishing up and a new Manager
+      // is created for the same origin, then the new Manager will
+      // be blocked until QuotaManager finishes clearing the origin.
+      if (manager->IsValid() && *manager->mManagerId == *aManagerId) {
         return manager.forget();
       }
     }
 
     return nullptr;
   }
 
   static void
@@ -906,25 +909,29 @@ private:
     mCopyContextList.Clear();
   }
 
   static void
   AsyncCopyCompleteFunc(void* aClosure, nsresult aRv)
   {
     // May be on any thread, including STS event target.
     MOZ_ASSERT(aClosure);
-    nsRefPtr<CachePutAllAction> action = static_cast<CachePutAllAction*>(aClosure);
+    // Weak ref as we are guaranteed to the action is alive until
+    // CompleteOnInitiatingThread is called.
+    CachePutAllAction* action = static_cast<CachePutAllAction*>(aClosure);
     action->CallOnAsyncCopyCompleteOnTargetThread(aRv);
   }
 
   void
   CallOnAsyncCopyCompleteOnTargetThread(nsresult aRv)
   {
-    // May be on any thread, including STS event target.
-    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableMethodWithArg<nsresult>(
+    // May be on any thread, including STS event target.  Non-owning runnable
+    // here since we are guaranteed the Action will survive until
+    // CompleteOnInitiatingThread is called.
+    nsCOMPtr<nsIRunnable> runnable = NS_NewNonOwningRunnableMethodWithArgs<nsresult>(
       this, &CachePutAllAction::OnAsyncCopyComplete, aRv);
     MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
       mTargetThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL)));
   }
 
   void
   DoResolve(nsresult aRv)
   {
@@ -1399,16 +1406,19 @@ void
 Manager::RemoveListener(Listener* aListener)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   // There may not be a listener here in the case where an actor is killed
   // before it can perform any actual async requests on Manager.
   mListeners.RemoveElement(aListener, ListenerEntryListenerComparator());
   MOZ_ASSERT(!mListeners.Contains(aListener,
                                   ListenerEntryListenerComparator()));
+  if (mListeners.IsEmpty() && mContext) {
+    mContext->AllowToClose();
+  }
 }
 
 void
 Manager::RemoveContext(Context* aContext)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(mContext);
   MOZ_ASSERT(mContext == aContext);
@@ -1417,16 +1427,31 @@ Manager::RemoveContext(Context* aContext
   // If we're trying to shutdown, then note that we're done.  This is the
   // delayed case from Manager::Shutdown().
   if (mShuttingDown) {
     Factory::Remove(this);
   }
 }
 
 void
+Manager::Invalidate()
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  // QuotaManager can trigger this more than once.
+  mValid = false;
+}
+
+bool
+Manager::IsValid() const
+{
+  NS_ASSERT_OWNINGTHREAD(Manager);
+  return mValid;
+}
+
+void
 Manager::AddRefCacheId(CacheId aCacheId)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   for (uint32_t i = 0; i < mCacheIdRefs.Length(); ++i) {
     if (mCacheIdRefs[i].mCacheId == aCacheId) {
       mCacheIdRefs[i].mCount += 1;
       return;
     }
@@ -1445,17 +1470,17 @@ Manager::ReleaseCacheId(CacheId aCacheId
     if (mCacheIdRefs[i].mCacheId == aCacheId) {
       DebugOnly<uint32_t> oldRef = mCacheIdRefs[i].mCount;
       mCacheIdRefs[i].mCount -= 1;
       MOZ_ASSERT(mCacheIdRefs[i].mCount < oldRef);
       if (mCacheIdRefs[i].mCount == 0) {
         bool orphaned = mCacheIdRefs[i].mOrphaned;
         mCacheIdRefs.RemoveElementAt(i);
         // TODO: note that we need to check this cache for staleness on startup (bug 1110446)
-        if (orphaned && !mShuttingDown) {
+        if (orphaned && !mShuttingDown && mValid) {
           nsRefPtr<Context> context = CurrentContext();
           context->CancelForCacheId(aCacheId);
           nsRefPtr<Action> action = new DeleteOrphanedCacheAction(this,
                                                                   aCacheId);
           context->Dispatch(mIOThread, action);
         }
       }
       return;
@@ -1488,17 +1513,17 @@ Manager::ReleaseBodyId(const nsID& aBody
     if (mBodyIdRefs[i].mBodyId == aBodyId) {
       DebugOnly<uint32_t> oldRef = mBodyIdRefs[i].mCount;
       mBodyIdRefs[i].mCount -= 1;
       MOZ_ASSERT(mBodyIdRefs[i].mCount < oldRef);
       if (mBodyIdRefs[i].mCount < 1) {
         bool orphaned = mBodyIdRefs[i].mOrphaned;
         mBodyIdRefs.RemoveElementAt(i);
         // TODO: note that we need to check this body for staleness on startup (bug 1110446)
-        if (orphaned && !mShuttingDown) {
+        if (orphaned && !mShuttingDown && mValid) {
           nsRefPtr<Action> action = new DeleteOrphanedBodyAction(aBodyId);
           nsRefPtr<Context> context = CurrentContext();
           context->Dispatch(mIOThread, action);
         }
       }
       return;
     }
   }
@@ -1530,19 +1555,18 @@ Manager::RemoveStreamList(StreamList* aS
 
 void
 Manager::CacheMatch(Listener* aListener, RequestId aRequestId, CacheId aCacheId,
                     const PCacheRequest& aRequest,
                     const PCacheQueryParams& aParams)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnCacheMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
-                            nullptr, nullptr);
+  if (mShuttingDown || !mValid) {
+    aListener->OnCacheMatch(aRequestId, NS_ERROR_FAILURE, nullptr, nullptr);
     return;
   }
   nsRefPtr<Context> context = CurrentContext();
   nsRefPtr<StreamList> streamList = new StreamList(this, context);
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new CacheMatchAction(this, listenerId, aRequestId,
                                                  aCacheId, aRequest, aParams,
                                                  streamList);
@@ -1551,18 +1575,18 @@ Manager::CacheMatch(Listener* aListener,
 
 void
 Manager::CacheMatchAll(Listener* aListener, RequestId aRequestId,
                        CacheId aCacheId, const PCacheRequestOrVoid& aRequest,
                        const PCacheQueryParams& aParams)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnCacheMatchAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+  if (mShuttingDown || !mValid) {
+    aListener->OnCacheMatchAll(aRequestId, NS_ERROR_FAILURE,
                                nsTArray<SavedResponse>(), nullptr);
     return;
   }
   nsRefPtr<Context> context = CurrentContext();
   nsRefPtr<StreamList> streamList = new StreamList(this, context);
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new CacheMatchAllAction(this, listenerId, aRequestId,
                                                     aCacheId, aRequest, aParams,
@@ -1573,18 +1597,18 @@ Manager::CacheMatchAll(Listener* aListen
 void
 Manager::CachePutAll(Listener* aListener, RequestId aRequestId, CacheId aCacheId,
                      const nsTArray<CacheRequestResponse>& aPutList,
                      const nsTArray<nsCOMPtr<nsIInputStream>>& aRequestStreamList,
                      const nsTArray<nsCOMPtr<nsIInputStream>>& aResponseStreamList)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnCachePutAll(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN);
+  if (mShuttingDown || !mValid) {
+    aListener->OnCachePutAll(aRequestId, NS_ERROR_FAILURE);
     return;
   }
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new CachePutAllAction(this, listenerId, aRequestId,
                                                   aCacheId, aPutList,
                                                   aRequestStreamList,
                                                   aResponseStreamList);
   nsRefPtr<Context> context = CurrentContext();
@@ -1593,36 +1617,36 @@ Manager::CachePutAll(Listener* aListener
 
 void
 Manager::CacheDelete(Listener* aListener, RequestId aRequestId,
                      CacheId aCacheId, const PCacheRequest& aRequest,
                      const PCacheQueryParams& aParams)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnCacheDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, false);
+  if (mShuttingDown || !mValid) {
+    aListener->OnCacheDelete(aRequestId, NS_ERROR_FAILURE, false);
     return;
   }
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new CacheDeleteAction(this, listenerId, aRequestId,
                                                   aCacheId, aRequest, aParams);
   nsRefPtr<Context> context = CurrentContext();
   context->Dispatch(mIOThread, action);
 }
 
 void
 Manager::CacheKeys(Listener* aListener, RequestId aRequestId,
                    CacheId aCacheId, const PCacheRequestOrVoid& aRequestOrVoid,
                    const PCacheQueryParams& aParams)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnCacheKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+  if (mShuttingDown || !mValid) {
+    aListener->OnCacheKeys(aRequestId, NS_ERROR_FAILURE,
                            nsTArray<SavedRequest>(), nullptr);
     return;
   }
   nsRefPtr<Context> context = CurrentContext();
   nsRefPtr<StreamList> streamList = new StreamList(this, context);
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new CacheKeysAction(this, listenerId, aRequestId,
                                                 aCacheId, aRequestOrVoid,
@@ -1632,18 +1656,18 @@ Manager::CacheKeys(Listener* aListener, 
 
 void
 Manager::StorageMatch(Listener* aListener, RequestId aRequestId,
                       Namespace aNamespace, const PCacheRequest& aRequest,
                       const PCacheQueryParams& aParams)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnStorageMatch(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+  if (mShuttingDown || !mValid) {
+    aListener->OnStorageMatch(aRequestId, NS_ERROR_FAILURE,
                               nullptr, nullptr);
     return;
   }
   nsRefPtr<Context> context = CurrentContext();
   nsRefPtr<StreamList> streamList = new StreamList(this, context);
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new StorageMatchAction(this, listenerId, aRequestId,
                                                    aNamespace, aRequest,
@@ -1652,86 +1676,87 @@ Manager::StorageMatch(Listener* aListene
 }
 
 void
 Manager::StorageHas(Listener* aListener, RequestId aRequestId,
                     Namespace aNamespace, const nsAString& aKey)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnStorageHas(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+  if (mShuttingDown || !mValid) {
+    aListener->OnStorageHas(aRequestId, NS_ERROR_FAILURE,
                             false);
     return;
   }
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new StorageHasAction(this, listenerId, aRequestId,
                                                  aNamespace, aKey);
   nsRefPtr<Context> context = CurrentContext();
   context->Dispatch(mIOThread, action);
 }
 
 void
 Manager::StorageOpen(Listener* aListener, RequestId aRequestId,
                      Namespace aNamespace, const nsAString& aKey)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnStorageOpen(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN, 0);
+  if (mShuttingDown || !mValid) {
+    aListener->OnStorageOpen(aRequestId, NS_ERROR_FAILURE, 0);
     return;
   }
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new StorageOpenAction(this, listenerId, aRequestId,
                                                   aNamespace, aKey);
   nsRefPtr<Context> context = CurrentContext();
   context->Dispatch(mIOThread, action);
 }
 
 void
 Manager::StorageDelete(Listener* aListener, RequestId aRequestId,
                        Namespace aNamespace, const nsAString& aKey)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnStorageDelete(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+  if (mShuttingDown || !mValid) {
+    aListener->OnStorageDelete(aRequestId, NS_ERROR_FAILURE,
                                false);
     return;
   }
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new StorageDeleteAction(this, listenerId, aRequestId,
                                                     aNamespace, aKey);
   nsRefPtr<Context> context = CurrentContext();
   context->Dispatch(mIOThread, action);
 }
 
 void
 Manager::StorageKeys(Listener* aListener, RequestId aRequestId,
                      Namespace aNamespace)
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   MOZ_ASSERT(aListener);
-  if (mShuttingDown) {
-    aListener->OnStorageKeys(aRequestId, NS_ERROR_ILLEGAL_DURING_SHUTDOWN,
+  if (mShuttingDown || !mValid) {
+    aListener->OnStorageKeys(aRequestId, NS_ERROR_FAILURE,
                              nsTArray<nsString>());
     return;
   }
   ListenerId listenerId = SaveListener(aListener);
   nsRefPtr<Action> action = new StorageKeysAction(this, listenerId, aRequestId,
                                                   aNamespace);
   nsRefPtr<Context> context = CurrentContext();
   context->Dispatch(mIOThread, action);
 }
 
 Manager::Manager(ManagerId* aManagerId, nsIThread* aIOThread)
   : mManagerId(aManagerId)
   , mIOThread(aIOThread)
   , mContext(nullptr)
   , mShuttingDown(false)
+  , mValid(true)
 {
   MOZ_ASSERT(mManagerId);
   MOZ_ASSERT(mIOThread);
 }
 
 Manager::~Manager()
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
@@ -1760,21 +1785,16 @@ Manager::Shutdown()
     return;
   }
 
   // Set a flag to prevent any new requests from coming in and creating
   // a new Context.  We must ensure all Contexts and IO operations are
   // complete before shutdown proceeds.
   mShuttingDown = true;
 
-  for (uint32_t i = 0; i < mStreamLists.Length(); ++i) {
-    nsRefPtr<StreamList> streamList = mStreamLists[i];
-    streamList->CloseAll();
-  }
-
   // If there is a context, then we must wait for it to complete.  Cancel and
   // only note that we are done after its cleaned up.
   if (mContext) {
     nsRefPtr<Context> context = mContext;
     context->CancelAll();
     return;
   }
 
@@ -1784,16 +1804,17 @@ Manager::Shutdown()
 
 already_AddRefed<Context>
 Manager::CurrentContext()
 {
   NS_ASSERT_OWNINGTHREAD(Manager);
   nsRefPtr<Context> ref = mContext;
   if (!ref) {
     MOZ_ASSERT(!mShuttingDown);
+    MOZ_ASSERT(mValid);
     nsRefPtr<Action> setupAction = new SetupAction();
     ref = Context::Create(this, setupAction);
     mContext = ref;
   }
   return ref.forget();
 }
 
 Manager::ListenerId
--- a/dom/cache/Manager.h
+++ b/dom/cache/Manager.h
@@ -120,16 +120,21 @@ public:
   static void ShutdownAllOnMainThread();
 
   // Must be called by Listener objects before they are destroyed.
   void RemoveListener(Listener* aListener);
 
   // Must be called by Context objects before they are destroyed.
   void RemoveContext(Context* aContext);
 
+  // Marks the Manager "invalid".  Once the Context completes no new operations
+  // will be permitted with this Manager.  New actors will get a new Manager.
+  void Invalidate();
+  bool IsValid() const;
+
   // If an actor represents a long term reference to a cache or body stream,
   // then they must call AddRefCacheId() or AddRefBodyId().  This will
   // cause the Manager to keep the backing data store alive for the given
   // object.  The actor must then call ReleaseCacheId() or ReleaseBodyId()
   // exactly once for every AddRef*() call it made.  Any delayed deletion
   // will then be performed.
   void AddRefCacheId(CacheId aCacheId);
   void ReleaseCacheId(CacheId aCacheId);
@@ -209,18 +214,18 @@ private:
 
   // Weak reference cleared by RemoveContext() in Context destructor.
   Context* MOZ_NON_OWNING_REF mContext;
 
   // Weak references cleared by RemoveListener() in Listener destructors.
   struct ListenerEntry
   {
     ListenerEntry()
-      : mId(UINT64_MAX),
-      mListener(nullptr)
+      : mId(UINT64_MAX)
+      , mListener(nullptr)
     {
     }
 
     ListenerEntry(ListenerId aId, Listener* aListener)
       : mId(aId)
       , mListener(aListener)
     {
     }
@@ -250,16 +255,17 @@ private:
   typedef nsTArray<ListenerEntry> ListenerList;
   ListenerList mListeners;
   static ListenerId sNextListenerId;
 
   // Weak references cleared by RemoveStreamList() in StreamList destructors.
   nsTArray<StreamList*> mStreamLists;
 
   bool mShuttingDown;
+  bool mValid;
 
   struct CacheIdRefCounter
   {
     CacheId mCacheId;
     MozRefCountType mCount;
     bool mOrphaned;
   };
   nsTArray<CacheIdRefCounter> mCacheIdRefs;
new file mode 100644
--- /dev/null
+++ b/dom/cache/OfflineStorage.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/cache/OfflineStorage.h"
+
+#include "mozilla/dom/cache/Context.h"
+#include "mozilla/dom/cache/QuotaClient.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+using mozilla::dom::quota::Client;
+using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT;
+using mozilla::dom::quota::QuotaManager;
+
+NS_IMPL_ISUPPORTS(OfflineStorage, nsIOfflineStorage);
+
+// static
+already_AddRefed<OfflineStorage>
+OfflineStorage::Register(Context::ThreadsafeHandle* aContext,
+                         const QuotaInfo& aQuotaInfo)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  QuotaManager* qm = QuotaManager::Get();
+  if (NS_WARN_IF(!qm)) {
+    return nullptr;
+  }
+
+  nsRefPtr<Client> client = qm->GetClient(Client::DOMCACHE);
+
+  nsRefPtr<OfflineStorage> storage =
+    new OfflineStorage(aContext, aQuotaInfo, client);
+
+  if (NS_WARN_IF(!qm->RegisterStorage(storage))) {
+    return nullptr;
+  }
+
+  return storage.forget();
+}
+
+void
+OfflineStorage::AddDestroyCallback(nsIRunnable* aCallback)
+{
+  MOZ_ASSERT(aCallback);
+  MOZ_ASSERT(!mDestroyCallbacks.Contains(aCallback));
+  mDestroyCallbacks.AppendElement(aCallback);
+}
+
+OfflineStorage::OfflineStorage(Context::ThreadsafeHandle* aContext,
+                               const QuotaInfo& aQuotaInfo,
+                               Client* aClient)
+  : mContext(aContext)
+  , mQuotaInfo(aQuotaInfo)
+  , mClient(aClient)
+{
+  MOZ_ASSERT(mContext);
+  MOZ_ASSERT(mClient);
+
+  mPersistenceType = PERSISTENCE_TYPE_DEFAULT;
+  mGroup = mQuotaInfo.mGroup;
+}
+
+OfflineStorage::~OfflineStorage()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  QuotaManager* qm = QuotaManager::Get();
+  MOZ_ASSERT(qm);
+  qm->UnregisterStorage(this);
+  for (uint32_t i = 0; i < mDestroyCallbacks.Length(); ++i) {
+    mDestroyCallbacks[i]->Run();
+  }
+}
+
+NS_IMETHODIMP_(const nsACString&)
+OfflineStorage::Id()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mQuotaInfo.mStorageId;
+}
+
+NS_IMETHODIMP_(Client*)
+OfflineStorage::GetClient()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mClient;
+}
+
+NS_IMETHODIMP_(bool)
+OfflineStorage::IsOwnedByProcess(ContentParent* aOwner)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // The Cache and Context can be shared by multiple client processes.  They
+  // are not exclusively owned by a single process.
+  //
+  // As far as I can tell this is used by QuotaManager to shutdown storages
+  // when a particular process goes away.  We definitely don't want this
+  // since we are shared.  Also, the Cache actor code already properly
+  // handles asynchronous actor destruction when the child process dies.
+  //
+  // Therefore, always return false here.
+  return false;
+}
+
+NS_IMETHODIMP_(const nsACString&)
+OfflineStorage::Origin()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return mQuotaInfo.mOrigin;
+}
+
+NS_IMETHODIMP_(nsresult)
+OfflineStorage::Close()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mContext->AllowToClose();
+  return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+OfflineStorage::Invalidate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mContext->InvalidateAndAllowToClose();
+}
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/cache/OfflineStorage.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_cache_QuotaOfflineStorage_h
+#define mozilla_dom_cache_QuotaOfflineStorage_h
+
+#include "nsISupportsImpl.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/cache/Context.h"
+#include "nsIOfflineStorage.h"
+#include "nsTArray.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace dom {
+namespace cache {
+
+class OfflineStorage final : public nsIOfflineStorage
+{
+public:
+  static already_AddRefed<OfflineStorage>
+  Register(Context::ThreadsafeHandle* aContext, const QuotaInfo& aQuotaInfo);
+
+  void
+  AddDestroyCallback(nsIRunnable* aCallback);
+
+private:
+  OfflineStorage(Context::ThreadsafeHandle* aContext,
+                 const QuotaInfo& aQuotaInfo,
+                 Client* aClient);
+  ~OfflineStorage();
+
+  nsRefPtr<Context::ThreadsafeHandle> mContext;
+  const QuotaInfo mQuotaInfo;
+  nsRefPtr<Client> mClient;
+  nsTArray<nsCOMPtr<nsIRunnable>> mDestroyCallbacks;
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOFFLINESTORAGE
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_cache_QuotaOfflineStorage_h
--- a/dom/cache/PCacheTypes.ipdlh
+++ b/dom/cache/PCacheTypes.ipdlh
@@ -5,16 +5,17 @@
 include protocol PCachePushStream;
 include protocol PCacheStreamControl;
 include PHeaders;
 include InputStreamParams;
 
 using HeadersGuardEnum from "mozilla/dom/FetchIPCUtils.h";
 using RequestCredentials from "mozilla/dom/FetchIPCUtils.h";
 using RequestMode from "mozilla/dom/FetchIPCUtils.h";
+using RequestCache from "mozilla/dom/FetchIPCUtils.h";
 using mozilla::dom::ResponseType from "mozilla/dom/FetchIPCUtils.h";
 using mozilla::void_t from "ipc/IPCMessageUtils.h";
 using struct nsID from "nsID.h";
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
@@ -49,16 +50,17 @@ struct PCacheRequest
   nsString urlWithoutQuery;
   PHeadersEntry[] headers;
   HeadersGuardEnum headersGuard;
   nsString referrer;
   RequestMode mode;
   RequestCredentials credentials;
   PCacheReadStreamOrVoid body;
   uint32_t context;
+  RequestCache requestCache;
 };
 
 union PCacheRequestOrVoid
 {
   void_t;
   PCacheRequest;
 };
 
--- a/dom/cache/QuotaClient.cpp
+++ b/dom/cache/QuotaClient.cpp
@@ -3,26 +3,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/cache/QuotaClient.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/cache/Manager.h"
+#include "mozilla/dom/cache/OfflineStorage.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/UsageInfo.h"
 #include "nsIFile.h"
 #include "nsISimpleEnumerator.h"
 #include "nsThreadUtils.h"
 
 namespace {
 
 using mozilla::DebugOnly;
 using mozilla::dom::cache::Manager;
+using mozilla::dom::cache::OfflineStorage;
 using mozilla::dom::quota::Client;
 using mozilla::dom::quota::PersistenceType;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::quota::UsageInfo;
 
 static nsresult
 GetBodyUsage(nsIFile* aDir, UsageInfo* aUsageInfo)
 {
@@ -55,16 +57,51 @@ GetBodyUsage(nsIFile* aDir, UsageInfo* a
     MOZ_ASSERT(fileSize >= 0);
 
     aUsageInfo->AppendToFileUsage(fileSize);
   }
 
   return NS_OK;
 }
 
+class StoragesDestroyedRunnable final : public nsRunnable
+{
+  uint32_t mExpectedCalls;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+public:
+  StoragesDestroyedRunnable(uint32_t aExpectedCalls, nsIRunnable* aCallback)
+    : mExpectedCalls(aExpectedCalls)
+    , mCallback(aCallback)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mExpectedCalls);
+    MOZ_ASSERT(mCallback);
+  }
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mExpectedCalls);
+    mExpectedCalls -= 1;
+    if (!mExpectedCalls) {
+      mCallback->Run();
+    }
+    return NS_OK;
+  }
+
+private:
+  ~StoragesDestroyedRunnable()
+  {
+    // This is a callback runnable and not used for thread dispatch.  It should
+    // always be destroyed on the main thread.
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+};
+
 class CacheQuotaClient final : public Client
 {
 public:
   virtual Type
   GetType() override
   {
     return DOMCACHE;
   }
@@ -146,59 +183,73 @@ public:
 
     return NS_OK;
   }
 
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin) override
   {
-    // nothing to do
+    // Nothing to do here.
   }
 
   virtual void
   ReleaseIOThreadObjects() override
   {
-    // nothing to do
+    // Nothing to do here as the Context handles cleaning everything up
+    // automatically.
   }
 
   virtual bool
   IsFileServiceUtilized() override
   {
     return false;
   }
 
   virtual bool
   IsTransactionServiceActivated() override
   {
-    // TODO: implement nsIOfflineStorage interface (bug 1110487)
-    return false;
+    return true;
   }
 
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
-    // TODO: implement nsIOfflineStorage interface (bug 1110487)
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(!aStorages.IsEmpty());
+
+    nsCOMPtr<nsIRunnable> callback =
+      new StoragesDestroyedRunnable(aStorages.Length(), aCallback);
+
+    for (uint32_t i = 0; i < aStorages.Length(); ++i) {
+      MOZ_ASSERT(aStorages[i]->GetClient());
+      MOZ_ASSERT(aStorages[i]->GetClient()->GetType() == Client::DOMCACHE);
+      nsRefPtr<OfflineStorage> storage =
+        static_cast<OfflineStorage*>(aStorages[i]);
+      storage->AddDestroyCallback(callback);
+    }
   }
 
 
   virtual void
   ShutdownTransactionService() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // spins the event loop and synchronously shuts down all Managers
     Manager::ShutdownAllOnMainThread();
   }
 
 private:
-  ~CacheQuotaClient() { }
+  ~CacheQuotaClient()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
 
-public:
   NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, override)
 };
 
 } // anonymous namespace;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
--- a/dom/cache/QuotaClient.h
+++ b/dom/cache/QuotaClient.h
@@ -9,15 +9,16 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/quota/Client.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
-already_AddRefed<quota::Client> CreateQuotaClient();
+already_AddRefed<quota::Client>
+CreateQuotaClient();
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_QuotaClient_h
--- a/dom/cache/StreamList.cpp
+++ b/dom/cache/StreamList.cpp
@@ -18,17 +18,17 @@ namespace cache {
 StreamList::StreamList(Manager* aManager, Context* aContext)
   : mManager(aManager)
   , mContext(aContext)
   , mCacheId(0)
   , mStreamControl(nullptr)
   , mActivated(false)
 {
   MOZ_ASSERT(mManager);
-  MOZ_ASSERT(mContext);
+  mContext->AddActivity(this);
 }
 
 void
 StreamList::SetStreamControl(CacheStreamControlParent* aStreamControl)
 {
   NS_ASSERT_OWNINGTHREAD(StreamList);
   MOZ_ASSERT(aStreamControl);
 
@@ -137,24 +137,39 @@ void
 StreamList::CloseAll()
 {
   NS_ASSERT_OWNINGTHREAD(StreamList);
   if (mStreamControl) {
     mStreamControl->CloseAll();
   }
 }
 
+void
+StreamList::Cancel()
+{
+  NS_ASSERT_OWNINGTHREAD(StreamList);
+  CloseAll();
+}
+
+bool
+StreamList::MatchesCacheId(CacheId aCacheId) const
+{
+  NS_ASSERT_OWNINGTHREAD(StreamList);
+  return aCacheId == mCacheId;
+}
+
 StreamList::~StreamList()
 {
   NS_ASSERT_OWNINGTHREAD(StreamList);
   MOZ_ASSERT(!mStreamControl);
   if (mActivated) {
     mManager->RemoveStreamList(this);
     for (uint32_t i = 0; i < mList.Length(); ++i) {
       mManager->ReleaseBodyId(mList[i].mId);
     }
     mManager->ReleaseCacheId(mCacheId);
   }
+  mContext->RemoveActivity(this);
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/StreamList.h
+++ b/dom/cache/StreamList.h
@@ -2,31 +2,31 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_cache_StreamList_h
 #define mozilla_dom_cache_StreamList_h
 
+#include "mozilla/dom/cache/Context.h"
 #include "mozilla/dom/cache/Types.h"
 #include "nsRefPtr.h"
 #include "nsTArray.h"
 
 class nsIInputStream;
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class CacheStreamControlParent;
-class Context;
 class Manager;
 
-class StreamList final
+class StreamList final : public Context::Activity
 {
 public:
   StreamList(Manager* aManager, Context* aContext);
 
   void SetStreamControl(CacheStreamControlParent* aStreamControl);
   void RemoveStreamControl(CacheStreamControlParent* aStreamControl);
 
   void Activate(CacheId aCacheId);
@@ -34,16 +34,20 @@ public:
   void Add(const nsID& aId, nsIInputStream* aStream);
   already_AddRefed<nsIInputStream> Extract(const nsID& aId);
 
   void NoteClosed(const nsID& aId);
   void NoteClosedAll();
   void Close(const nsID& aId);
   void CloseAll();
 
+  // Context::Activity methods
+  virtual void Cancel() override;
+  virtual bool MatchesCacheId(CacheId aCacheId) const override;
+
 private:
   ~StreamList();
   struct Entry
   {
     nsID mId;
     nsCOMPtr<nsIInputStream> mStream;
   };
   nsRefPtr<Manager> mManager;
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -220,16 +220,17 @@ TypeUtils::ToPCacheRequest(PCacheRequest
 
   nsRefPtr<InternalHeaders> headers = aIn->Headers();
   MOZ_ASSERT(headers);
   headers->GetPHeaders(aOut.headers());
   aOut.headersGuard() = headers->Guard();
   aOut.mode() = aIn->Mode();
   aOut.credentials() = aIn->GetCredentialsMode();
   aOut.context() = aIn->ContentPolicyType();
+  aOut.requestCache() = aIn->GetCacheMode();
 
   if (aBodyAction == IgnoreBody) {
     aOut.body() = void_t();
     return;
   }
 
   // BodyUsed flag is checked and set previously in ToInternalRequest()
 
@@ -362,16 +363,17 @@ TypeUtils::ToInternalRequest(const PCach
   nsRefPtr<InternalRequest> internalRequest = new InternalRequest();
 
   internalRequest->SetMethod(aIn.method());
   internalRequest->SetURL(NS_ConvertUTF16toUTF8(aIn.url()));
   internalRequest->SetReferrer(aIn.referrer());
   internalRequest->SetMode(aIn.mode());
   internalRequest->SetCredentialsMode(aIn.credentials());
   internalRequest->SetContentPolicyType(aIn.context());
+  internalRequest->SetCacheMode(aIn.requestCache());
 
   nsRefPtr<InternalHeaders> internalHeaders =
     new InternalHeaders(aIn.headers(), aIn.headersGuard());
   ErrorResult result;
   internalRequest->Headers()->SetGuard(aIn.headersGuard(), result);
   MOZ_ASSERT(!result.Failed());
   internalRequest->Headers()->Fill(*internalHeaders, result);
   MOZ_ASSERT(!result.Failed());
--- a/dom/cache/Types.h
+++ b/dom/cache/Types.h
@@ -29,16 +29,17 @@ static const RequestId INVALID_REQUEST_I
 typedef int32_t CacheId;
 
 struct QuotaInfo
 {
   QuotaInfo() : mIsApp(false) { }
   nsCOMPtr<nsIFile> mDir;
   nsCString mGroup;
   nsCString mOrigin;
+  nsCString mStorageId;
   bool mIsApp;
 };
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_cache_Types_h
--- a/dom/cache/moz.build
+++ b/dom/cache/moz.build
@@ -23,16 +23,17 @@ EXPORTS.mozilla.dom.cache += [
     'DBAction.h',
     'DBSchema.h',
     'Feature.h',
     'FetchPut.h',
     'FileUtils.h',
     'IPCUtils.h',
     'Manager.h',
     'ManagerId.h',
+    'OfflineStorage.h',
     'PrincipalVerifier.h',
     'QuotaClient.h',
     'ReadStream.h',
     'SavedTypes.h',
     'StreamControl.h',
     'StreamList.h',
     'StreamUtils.h',
     'Types.h',
@@ -56,16 +57,17 @@ UNIFIED_SOURCES += [
     'Context.cpp',
     'DBAction.cpp',
     'DBSchema.cpp',
     'Feature.cpp',
     'FetchPut.cpp',
     'FileUtils.cpp',
     'Manager.cpp',
     'ManagerId.cpp',
+    'OfflineStorage.cpp',
     'PrincipalVerifier.cpp',
     'QuotaClient.cpp',
     'ReadStream.cpp',
     'StreamControl.cpp',
     'StreamList.cpp',
     'StreamUtils.cpp',
     'TypeUtils.cpp',
 ]
--- a/dom/cache/test/mochitest/driver.js
+++ b/dom/cache/test/mochitest/driver.js
@@ -25,16 +25,32 @@ function runTests(testFile, order) {
                 ["dom.serviceWorkers.testing.enabled", true],
                 ["dom.serviceWorkers.exemptFromPerDomainMax", true]]
       }, function() {
         resolve();
       });
     });
   }
 
+  // adapted from dom/indexedDB/test/helpers.js
+  function clearStorage() {
+    return new Promise(function(resolve, reject) {
+      var principal = SpecialPowers.wrap(document).nodePrincipal;
+      var appId, inBrowser;
+      var nsIPrincipal = SpecialPowers.Components.interfaces.nsIPrincipal;
+      if (principal.appId != nsIPrincipal.UNKNOWN_APP_ID &&
+          principal.appId != nsIPrincipal.NO_APP_ID) {
+        appId = principal.appId;
+        inBrowser = principal.isInBrowserElement;
+      }
+      SpecialPowers.clearStorageForURI(document.documentURI, resolve, appId,
+                                       inBrowser);
+    });
+  }
+
   function loadScript(script) {
     return new Promise(function(resolve, reject) {
       var s = document.createElement("script");
       s.src = script;
       s.onerror = reject;
       s.onload = resolve;
       document.body.appendChild(s);
     });
@@ -95,22 +111,26 @@ function runTests(testFile, order) {
           info("Running tests in parallel mode");
           return runTests(testFile, "parallel");
         });
   }
   if (order == "sequential") {
     return setupPrefs()
         .then(importDrivers)
         .then(runWorkerTest)
+        .then(clearStorage)
         .then(runServiceWorkerTest)
+        .then(clearStorage)
         .then(runFrameTest)
+        .then(clearStorage)
         .catch(function(e) {
           ok(false, "A promise was rejected during test execution: " + e);
         });
   }
   return setupPrefs()
       .then(importDrivers)
       .then(() => Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()]))
+      .then(clearStorage)
       .catch(function(e) {
         ok(false, "A promise was rejected during test execution: " + e);
       });
 }
 
--- a/dom/cache/test/mochitest/mochitest.ini
+++ b/dom/cache/test/mochitest/mochitest.ini
@@ -12,18 +12,20 @@ support-files =
   test_cache_matchAll_request.js
   test_cache_overwrite.js
   mirror.sjs
   test_cache_match_vary.js
   vary.sjs
   test_caches.js
   test_cache_keys.js
   test_cache_put.js
+  test_cache_requestCache.js
 
 [test_cache.html]
 [test_cache_add.html]
 [test_cache_match_request.html]
 [test_cache_matchAll_request.html]
 [test_cache_overwrite.html]
 [test_cache_match_vary.html]
 [test_caches.html]
 [test_cache_keys.html]
 [test_cache_put.html]
+[test_cache_requestCache.html]
--- a/dom/cache/test/mochitest/test_cache_keys.js
+++ b/dom/cache/test/mochitest/test_cache_keys.js
@@ -52,18 +52,16 @@ caches.open(name).then(function(cache) {
       })
   );
 }).then(function() {
   // But HEAD should be allowed even without ignoreMethod
   return c.keys(new Request(tests[0], {method: "HEAD"}));
 }).then(function(keys) {
   is(keys.length, 1, "One match should be found");
   ok(keys[0].url.indexOf(tests[0]) >= 0, "Valid URL");
-  // TODO: Add tests for ignoreVary
-
   // Make sure cacheName is ignored.
   return c.keys(tests[0], {cacheName: "non-existing-cache"});
 }).then(function(keys) {
   is(keys.length, 1, "One match should be found");
   ok(keys[0].url.indexOf(tests[0]) >= 0, "Valid URL");
   return caches.delete(name);
 }).then(function(deleted) {
   ok(deleted, "The cache should be successfully deleted");
--- a/dom/cache/test/mochitest/test_cache_match_vary.js
+++ b/dom/cache/test/mochitest/test_cache_match_vary.js
@@ -74,16 +74,43 @@ function testBasics() {
       // Ensure that searching with a non-matching value for the Cookie header but with ignoreVary set succeeds.
       return test.cache.match(new Request(requestURL, {headers: {"Cookie": "foo=bar"}}),
                               {ignoreVary: true});
     }).then(function(r) {
       return checkResponse(r, test.response, test.responseText);
     });
 }
 
+function testBasicKeys() {
+  function checkRequest(reqs) {
+    is(reqs.length, 1, "One request expected");
+    ok(reqs[0].url.indexOf(requestURL) >= 0, "The correct request expected");
+    ok(reqs[0].headers.get("WhatToVary"), "Cookie", "The correct request headers expected");
+  }
+  var test;
+  return setupTest({"WhatToVary": "Cookie"})
+    .then(function(t) {
+      test = t;
+      // Ensure that searching without specifying a Cookie header succeeds.
+      return test.cache.keys(requestURL);
+    }).then(function(r) {
+      return checkRequest(r);
+    }).then(function() {
+      // Ensure that searching with a non-matching value for the Cookie header fails.
+      return test.cache.keys(new Request(requestURL, {headers: {"Cookie": "foo=bar"}}));
+    }).then(function(r) {
+      is(r.length, 0, "Searching for a request with an unknown Vary header should not succeed");
+      // Ensure that searching with a non-matching value for the Cookie header but with ignoreVary set succeeds.
+      return test.cache.keys(new Request(requestURL, {headers: {"Cookie": "foo=bar"}}),
+                             {ignoreVary: true});
+    }).then(function(r) {
+      return checkRequest(r);
+    });
+}
+
 function testStar() {
   var test;
   return setupTest({"WhatToVary": "*", "Cookie": "foo=bar"})
     .then(function(t) {
       test = t;
       // Ensure that searching with a different Cookie header with Vary:* succeeds.
       return test.cache.match(new Request(requestURL, {headers: {"Cookie": "bar=baz"}}));
     }).then(function(r) {
@@ -257,16 +284,18 @@ function testMultipleCacheEntries() {
 // Make sure to clean up after each test step.
 function step(testPromise) {
   return testPromise.then(function() {
     caches.delete(name);
   });
 }
 
 step(testBasics()).then(function() {
+  return step(testBasicKeys());
+}).then(function() {
   return step(testStar());
 }).then(function() {
   return step(testMatch());
 }).then(function() {
   return step(testStarAndAnotherHeader());
 }).then(function() {
   return step(testInvalidHeaderName());
 }).then(function() {
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/test_cache_requestCache.html
@@ -0,0 +1,20 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Validate the Cache.keys() method</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="driver.js"></script>
+</head>
+<body>
+<iframe id="frame"></iframe>
+<script class="testbody" type="text/javascript">
+  runTests("test_cache_requestCache.js")
+    .then(function() {
+      SimpleTest.finish();
+    });
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/cache/test/mochitest/test_cache_requestCache.js
@@ -0,0 +1,27 @@
+var name = "requestCache" + context;
+var c;
+
+var reqWithoutCache = new Request("//mochi.test:8888/?noCache" + context);
+var reqWithCache = new Request("//mochi.test:8888/?withCache" + context,
+                               {cache: "force-cache"});
+
+// Sanity check
+is(reqWithoutCache.cache, "default", "Correct default value");
+is(reqWithCache.cache, "force-cache", "Correct value set by the ctor");
+
+caches.open(name).then(function(cache) {
+  c = cache;
+  return c.addAll([reqWithoutCache, reqWithCache]);
+}).then(function() {
+  return c.keys();
+}).then(function(keys) {
+  is(keys.length, 2, "Correct number of requests");
+  is(keys[0].url, reqWithoutCache.url, "Correct URL");
+  is(keys[0].cache, reqWithoutCache.cache, "Correct cache attribute");
+  is(keys[1].url, reqWithCache.url, "Correct URL");
+  is(keys[1].cache, reqWithCache.cache, "Correct cache attribute");
+  return caches.delete(name);
+}).then(function(deleted) {
+  ok(deleted, "The cache should be successfully deleted");
+  testDone();
+});
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -29,15 +29,16 @@ public:
     // nsWrapperCache
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
 
 private:
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override;
     virtual bool ValidateBufferTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) override;
-
+    virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override;
+    
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_1_CONTEXT_H_
--- a/dom/canvas/WebGL1ContextBuffers.cpp
+++ b/dom/canvas/WebGL1ContextBuffers.cpp
@@ -45,8 +45,24 @@ WebGL1Context::ValidateBufferForTarget(G
 
     if (buffer->HasEverBeenBound() && target != buffer->Target()) {
         ErrorInvalidOperation("%s: buffer already bound to a different target", info);
         return false;
     }
 
     return true;
 }
+
+bool
+WebGL1Context::ValidateBufferUsageEnum(GLenum usage, const char* info)
+{
+    switch (usage) {
+    case LOCAL_GL_STREAM_DRAW:
+    case LOCAL_GL_STATIC_DRAW:
+    case LOCAL_GL_DYNAMIC_DRAW:
+        return true;
+    default:
+        break;
+    }
+
+    ErrorInvalidEnumInfo(info, usage);
+    return false;
+}
--- a/dom/canvas/WebGL2Context.cpp
+++ b/dom/canvas/WebGL2Context.cpp
@@ -82,17 +82,18 @@ static const gl::GLFeature kRequiredFeat
     gl::GLFeature::instanced_arrays,
     gl::GLFeature::instanced_non_arrays,
     gl::GLFeature::map_buffer_range,
     gl::GLFeature::occlusion_query2,
     gl::GLFeature::packed_depth_stencil,
     gl::GLFeature::query_objects,
     gl::GLFeature::renderbuffer_color_float,
     gl::GLFeature::renderbuffer_color_half_float,
-    gl::GLFeature::sRGB,
+    gl::GLFeature::sRGB_framebuffer,
+    gl::GLFeature::sRGB_texture,
     gl::GLFeature::sampler_objects,
     gl::GLFeature::standard_derivatives,
     gl::GLFeature::texture_3D,
     gl::GLFeature::texture_3D_compressed,
     gl::GLFeature::texture_3D_copy,
     gl::GLFeature::texture_float,
     gl::GLFeature::texture_float_linear,
     gl::GLFeature::texture_half_float,
@@ -115,22 +116,32 @@ WebGLContext::InitWebGL2()
         !gl->IsSupported(gl::GLFeature::occlusion_query_boolean))
     {
         // On desktop, we fake occlusion_query_boolean with occlusion_query if
         // necessary. (See WebGL2ContextQueries.cpp)
         GenerateWarning("WebGL 2 unavailable. Requires occlusion queries.");
         return false;
     }
 
+    std::vector<gl::GLFeature> missingList;
+
     for (size_t i = 0; i < ArrayLength(kRequiredFeatures); i++) {
-        if (!gl->IsSupported(kRequiredFeatures[i])) {
-            GenerateWarning("WebGL 2 unavailable. Requires feature %s.",
-                            gl::GLContext::GetFeatureName(kRequiredFeatures[i]));
-            return false;
+        if (!gl->IsSupported(kRequiredFeatures[i]))
+            missingList.push_back(kRequiredFeatures[i]);
+    }
+
+    if (missingList.size()) {
+        nsAutoCString exts;
+        for (auto itr = missingList.begin(); itr != missingList.end(); ++itr) {
+            exts.AppendLiteral("\n  ");
+            exts.Append(gl::GLContext::GetFeatureName(*itr));
         }
+        GenerateWarning("WebGL 2 unavailable. The following required features are"
+                        " unavailible: %s", exts.BeginReading());
+        return false;
     }
 
     // ok WebGL 2 is compatible, we can enable natively supported extensions.
     for (size_t i = 0; i < ArrayLength(kNativelySupportedExtensions); i++) {
         EnableExtension(kNativelySupportedExtensions[i]);
 
         MOZ_ASSERT(IsExtensionEnabled(kNativelySupportedExtensions[i]));
     }
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -343,15 +343,16 @@ private:
     bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
                                 GLsizei width, GLsizei height, GLsizei depth,
                                 const char* info);
 
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override;
     virtual bool ValidateBufferTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) override;
+    virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override;
 
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -70,16 +70,38 @@ WebGL2Context::ValidateBufferForTarget(G
             buffer->Target() != LOCAL_GL_ELEMENT_ARRAY_BUFFER;
     }
 
     ErrorInvalidOperation("%s: buffer already bound to a incompatible target %s",
                           info, EnumName(buffer->Target().get()));
     return false;
 }
 
+bool
+WebGL2Context::ValidateBufferUsageEnum(GLenum usage, const char* info)
+{
+    switch (usage) {
+    case LOCAL_GL_DYNAMIC_COPY:
+    case LOCAL_GL_DYNAMIC_DRAW:
+    case LOCAL_GL_DYNAMIC_READ:
+    case LOCAL_GL_STATIC_COPY:
+    case LOCAL_GL_STATIC_DRAW:
+    case LOCAL_GL_STATIC_READ:
+    case LOCAL_GL_STREAM_COPY:
+    case LOCAL_GL_STREAM_DRAW:
+    case LOCAL_GL_STREAM_READ:
+        return true;
+    default:
+        break;
+    }
+
+    ErrorInvalidEnumInfo(info, usage);
+    return false;
+}
+
 // -------------------------------------------------------------------------
 // Buffer objects
 
 void
 WebGL2Context::CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
                                  GLintptr readOffset, GLintptr writeOffset,
                                  GLsizeiptr size)
 {
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -927,17 +927,16 @@ protected:
     WebGLRefPtr<WebGLBuffer> mBoundUniformBuffer;
 
     nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundUniformBuffers;
     nsTArray<WebGLRefPtr<WebGLBuffer>> mBoundTransformFeedbackBuffers;
 
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target);
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target,
                                                            GLuint index);
-    bool ValidateBufferUsageEnum(GLenum target, const char* info);
 
 // -----------------------------------------------------------------------------
 // Queries (WebGL2ContextQueries.cpp)
 protected:
     WebGLRefPtr<WebGLQuery>* GetQueryTargetSlot(GLenum target);
 
     WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery;
     WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery;
@@ -1384,16 +1383,17 @@ private:
 
 private:
     // -------------------------------------------------------------------------
     // Context customization points
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) = 0;
     virtual bool ValidateBufferTarget(GLenum target, const char* info) = 0;
     virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) = 0;
     virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) = 0;
+    virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) = 0;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) = 0;
 
 protected:
     int32_t MaxTextureSizeForTarget(TexTarget target) const {
         return (target == LOCAL_GL_TEXTURE_2D) ? mGLMaxTextureSize
                                                : mGLMaxCubeMapTextureSize;
     }
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -885,16 +885,21 @@ WebGLContext::FramebufferTexture2D(GLenu
                                    GLint level)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateFramebufferTarget(target, "framebufferTexture2D"))
         return;
 
+    if (!IsWebGL2() && level != 0) {
+        ErrorInvalidValue("framebufferTexture2D: level must be 0.");
+        return;
+    }
+
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
 
     case LOCAL_GL_READ_FRAMEBUFFER:
--- a/dom/canvas/WebGLExtensionSRGB.cpp
+++ b/dom/canvas/WebGLExtensionSRGB.cpp
@@ -29,15 +29,16 @@ WebGLExtensionSRGB::~WebGLExtensionSRGB(
 {
 }
 
 bool
 WebGLExtensionSRGB::IsSupported(const WebGLContext* webgl)
 {
     gl::GLContext* gl = webgl->GL();
 
-    return gl->IsSupported(gl::GLFeature::sRGB);
+    return gl->IsSupported(gl::GLFeature::sRGB_framebuffer) &&
+           gl->IsSupported(gl::GLFeature::sRGB_texture);
 }
 
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionSRGB)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -494,37 +494,29 @@ WebGLFramebuffer::FramebufferRenderbuffe
 void
 WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPoint,
                                        TexImageTarget texImageTarget,
                                        WebGLTexture* tex, GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
-    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture",
-                                           tex))
-    {
+    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
         return;
-    }
 
     if (tex) {
         bool isTexture2D = tex->Target() == LOCAL_GL_TEXTURE_2D;
         bool isTexTarget2D = texImageTarget == LOCAL_GL_TEXTURE_2D;
         if (isTexture2D != isTexTarget2D) {
             mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
                                             " texture and texture target.");
             return;
         }
     }
 
-    if (level != 0) {
-        mContext->ErrorInvalidValue("framebufferTexture2D: Level must be 0.");
-        return;
-    }
-
     /* Get the requested attachment. If result is NULL, attachment is invalid
      * and an error is generated.
      *
      * Don't use GetAttachment(...) here because it opt builds it returns
      * mColorAttachment[0] for invalid attachment, which we really don't want to
      * mess with.
      */
     Attachment* attachment = GetAttachmentOrNull(attachPoint);
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -4641,17 +4641,17 @@ EventStateManager::DoStateChange(nsICont
 /* static */
 void
 EventStateManager::UpdateAncestorState(nsIContent* aStartNode,
                                        nsIContent* aStopBefore,
                                        EventStates aState,
                                        bool aAddState)
 {
   for (; aStartNode && aStartNode != aStopBefore;
-       aStartNode = aStartNode->GetParent()) {
+       aStartNode = aStartNode->GetParentElementCrossingShadowRoot()) {
     // We might be starting with a non-element (e.g. a text node) and
     // if someone is doing something weird might be ending with a
     // non-element too (e.g. a document fragment)
     if (!aStartNode->IsElement()) {
       continue;
     }
     Element* element = aStartNode->AsElement();
     DoStateChange(element, aState, aAddState);
@@ -4669,17 +4669,17 @@ EventStateManager::UpdateAncestorState(n
     // same node, and while one is no longer hovered the other still
     // is.  In that situation, the label that's still hovered will be
     // aStopBefore or some ancestor of it, and the call we just made
     // to UpdateAncestorState with aAddState = false would have
     // removed the hover state from the node.  But the node should
     // still be in hover state.  To handle this situation we need to
     // keep walking up the tree and any time we find a label mark its
     // corresponding node as still in our state.
-    for ( ; aStartNode; aStartNode = aStartNode->GetParent()) {
+    for ( ; aStartNode; aStartNode = aStartNode->GetParentElementCrossingShadowRoot()) {
       if (!aStartNode->IsElement()) {
         continue;
       }
 
       Element* labelTarget = GetLabelTarget(aStartNode->AsElement());
       if (labelTarget && !labelTarget->State().HasState(aState)) {
         DoStateChange(labelTarget, aState, true);
       }
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -136,16 +136,17 @@ skip-if = buildapp == 'b2g'
 skip-if = toolkit == "gonk" || e10s
 [test_bug985988.html]
 [test_bug998809.html]
 [test_bug1017086_disable.html]
 support-files = bug1017086_inner.html
 [test_bug1017086_enable.html]
 support-files = bug1017086_inner.html
 [test_bug1079236.html]
+[test_bug1145910.html]
 [test_clickevent_on_input.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_continuous_wheel_events.html]
 skip-if = buildapp == 'b2g' || e10s # b2g(5535 passed, 108 failed - more tests running than desktop) b2g-debug(5535 passed, 108 failed - more tests running than desktop) b2g-desktop(5535 passed, 108 failed - more tests running than desktop)
 [test_dblclick_explicit_original_target.html]
 [test_dom_keyboard_event.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_dom_mouse_event.html]
new file mode 100644
--- /dev/null
+++ b/dom/events/test/test_bug1145910.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1145910
+-->
+<head>
+  <title>Test for Bug 1145910</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<style>
+div:active {
+  color: rgb(0, 255, 0);
+}
+</style>
+<div id="host">Foo</div>
+<script type="application/javascript">
+
+/** Test for Bug 1145910 **/
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(function() {
+  var host = document.getElementById("host");
+  var shadow = host.createShadowRoot();
+  shadow.innerHTML = '<style>div:active { color: rgb(0, 255, 0); }</style><div id="inner">Bar</div>';
+  var inner = shadow.getElementById("inner");
+
+  is(window.getComputedStyle(host).color, "rgb(0, 0, 0)", "The host should not be active");
+  is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "The div inside the shadow root should not be active.");
+
+  synthesizeMouseAtCenter(host, { type: "mousedown" });
+
+  is(window.getComputedStyle(inner).color, "rgb(0, 255, 0)", "Div inside shadow root should be active.");
+  is(window.getComputedStyle(host).color, "rgb(0, 255, 0)", "Host should be active when the inner div is made active.");
+
+  synthesizeMouseAtCenter(host, { type: "mouseup" });
+
+  is(window.getComputedStyle(inner).color, "rgb(0, 0, 0)", "Div inside shadow root should no longer be active.");
+  is(window.getComputedStyle(host).color, "rgb(0, 0, 0)", "Host should no longer be active.");
+
+  SimpleTest.finish();
+});
+
+</script>
+</body>
+</html>
--- a/dom/fetch/FetchIPCUtils.h
+++ b/dom/fetch/FetchIPCUtils.h
@@ -27,15 +27,20 @@ namespace IPC {
                                     mozilla::dom::RequestMode::Same_origin,
                                     mozilla::dom::RequestMode::EndGuard_> {};
   template<>
   struct ParamTraits<mozilla::dom::RequestCredentials> :
     public ContiguousEnumSerializer<mozilla::dom::RequestCredentials,
                                     mozilla::dom::RequestCredentials::Omit,
                                     mozilla::dom::RequestCredentials::EndGuard_> {};
   template<>
+  struct ParamTraits<mozilla::dom::RequestCache> :
+    public ContiguousEnumSerializer<mozilla::dom::RequestCache,
+                                    mozilla::dom::RequestCache::Default,
+                                    mozilla::dom::RequestCache::EndGuard_> {};
+  template<>
   struct ParamTraits<mozilla::dom::ResponseType> :
     public ContiguousEnumSerializer<mozilla::dom::ResponseType,
                                     mozilla::dom::ResponseType::Basic,
                                     mozilla::dom::ResponseType::EndGuard_> {};
 }
 
 #endif // mozilla_dom_FetchIPCUtils_h
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -203,16 +203,22 @@ public:
   }
 
   RequestCache
   GetCacheMode() const
   {
     return mCacheMode;
   }
 
+  void
+  SetCacheMode(RequestCache aCacheMode)
+  {
+    mCacheMode = aCacheMode;
+  }
+
   nsContentPolicyType
   ContentPolicyType() const
   {
     return mContentPolicyType;
   }
 
   void
   SetContentPolicyType(nsContentPolicyType aContentPolicyType)
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -77,16 +77,17 @@ Request::Constructor(const GlobalObject&
 
   request = request->GetRequestConstructorCopy(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RequestMode fallbackMode = RequestMode::EndGuard_;
   RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_;
+  RequestCache fallbackCache = RequestCache::EndGuard_;
   if (aInput.IsUSVString()) {
     nsString input;
     input.Assign(aInput.GetAsUSVString());
 
     nsString requestURL;
     if (NS_IsMainThread()) {
       nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
       MOZ_ASSERT(window);
@@ -122,16 +123,17 @@ Request::Constructor(const GlobalObject&
       url->Stringify(requestURL, aRv);
       if (aRv.Failed()) {
         return nullptr;
       }
     }
     request->SetURL(NS_ConvertUTF16toUTF8(requestURL));
     fallbackMode = RequestMode::Cors;
     fallbackCredentials = RequestCredentials::Omit;
+    fallbackCache = RequestCache::Default;
   }
 
   // CORS-with-forced-preflight is not publicly exposed and should not be
   // considered a valid value.
   if (aInit.mMode.WasPassed() &&
       aInit.mMode.Value() == RequestMode::Cors_with_forced_preflight) {
     NS_NAMED_LITERAL_STRING(sourceDescription, "'mode' member of RequestInit");
     NS_NAMED_LITERAL_STRING(value, "cors-with-forced-preflight");
@@ -147,16 +149,22 @@ Request::Constructor(const GlobalObject&
   if (mode != RequestMode::EndGuard_) {
     request->SetMode(mode);
   }
 
   if (credentials != RequestCredentials::EndGuard_) {
     request->SetCredentialsMode(credentials);
   }
 
+  RequestCache cache = aInit.mCache.WasPassed() ?
+                       aInit.mCache.Value() : fallbackCache;
+  if (cache != RequestCache::EndGuard_) {
+    request->SetCacheMode(cache);
+  }
+
   // Request constructor step 14.
   if (aInit.mMethod.WasPassed()) {
     nsAutoCString method(aInit.mMethod.Value());
     nsAutoCString upperCaseMethod = method;
     ToUpperCase(upperCaseMethod);
 
     // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP
     // token, since HTTP states that Method may be any of the defined values or
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -223,23 +223,10 @@ Headers*
 Response::Headers_()
 {
   if (!mHeaders) {
     mHeaders = new Headers(mOwner, mInternalResponse->Headers());
   }
 
   return mHeaders;
 }
-
-void
-Response::SetFinalURL(bool aFinalURL, ErrorResult& aRv)
-{
-  nsCString url;
-  mInternalResponse->GetUrl(url);
-  if (url.IsEmpty()) {
-    aRv.ThrowTypeError(MSG_RESPONSE_URL_IS_NULL);
-    return;
-  }
-
-  mInternalResponse->SetFinalURL(aFinalURL);
-}
 } // namespace dom
 } // namespace mozilla
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -51,25 +51,16 @@ public:
   void
   GetUrl(DOMString& aUrl) const
   {
     nsCString url;
     mInternalResponse->GetUrl(url);
     aUrl.AsAString() = NS_ConvertUTF8toUTF16(url);
   }
 
-  bool
-  GetFinalURL(ErrorResult& aRv) const
-  {
-    return mInternalResponse->FinalURL();
-  }
-
-  void
-  SetFinalURL(bool aFinalURL, ErrorResult& aRv);
-
   uint16_t
   Status() const
   {
     return mInternalResponse->GetStatus();
   }
 
   bool
   Ok() const
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -14,17 +14,17 @@ interface nsIURI;
 interface nsIServiceWorkerUnregisterCallback : nsISupports
 {
   // aState is true if the unregistration succeded.
   // It's false if this ServiceWorkerRegistration doesn't exist.
   [noscript] void UnregisterSucceeded(in bool aState);
   [noscript] void UnregisterFailed();
 };
 
-[builtinclass, uuid(706c3e6b-c9d2-4857-893d-4b4845fec48f)]
+[builtinclass, uuid(e4c8baa5-237a-4bf6-82d4-ea06eb4b76ba)]
 interface nsIServiceWorkerManager : nsISupports
 {
   /**
    * Registers a ServiceWorker with script loaded from `aScriptURI` to act as
    * the ServiceWorker for aScope.  Requires a valid entry settings object on
    * the stack. This means you must call this from content code 'within'
    * a window.
    *
@@ -54,17 +54,18 @@ interface nsIServiceWorkerManager : nsIS
 
   // Returns true if a ServiceWorker is available for the scope of aURI.
   bool isAvailableForURI(in nsIURI aURI);
 
   // Returns true if a given document is currently controlled by a ServiceWorker
   bool isControlled(in nsIDocument aDocument);
 
   // Cause a fetch event to be dispatched to the worker global associated with the given document.
-  void dispatchFetchEvent(in nsIDocument aDoc, in nsIInterceptedChannel aChannel);
+  void dispatchFetchEvent(in nsIDocument aDoc, in nsIInterceptedChannel aChannel,
+                          in boolean aIsReload);
 
   // aTarget MUST be a ServiceWorkerRegistration.
   [noscript] void AddRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
   [noscript] void RemoveRegistrationEventListener(in DOMString aScope, in nsIDOMEventTarget aTarget);
 
   /**
    * Call this to request that document `aDoc` be controlled by a ServiceWorker
    * if a registration exists for it's scope.
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1426,17 +1426,16 @@ void MediaDecoder::UpdateEstimatedMediaD
     return;
   }
   NS_ENSURE_TRUE_VOID(GetStateMachine());
   GetStateMachine()->UpdateEstimatedDuration(aDuration);
 }
 
 void MediaDecoder::SetMediaSeekable(bool aMediaSeekable) {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-  MOZ_ASSERT(NS_IsMainThread() || OnDecodeThread());
   mMediaSeekable = aMediaSeekable;
 }
 
 bool
 MediaDecoder::IsTransportSeekable()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   return GetResource()->IsTransportSeekable();
@@ -1636,19 +1635,21 @@ void MediaDecoder::NotifyDataArrived(con
 // Provide access to the state machine object
 MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const {
   return mDecoderStateMachine;
 }
 
 void
 MediaDecoder::NotifyWaitingForResourcesStatusChanged()
 {
-  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   if (mDecoderStateMachine) {
-    mDecoderStateMachine->NotifyWaitingForResourcesStatusChanged();
+    RefPtr<nsRunnable> task =
+      NS_NewRunnableMethod(mDecoderStateMachine,
+                           &MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged);
+    mDecoderStateMachine->TaskQueue()->Dispatch(task.forget());
   }
 }
 
 bool MediaDecoder::IsShutdown() const {
   NS_ENSURE_TRUE(GetStateMachine(), true);
   return GetStateMachine()->IsShutdown();
 }
 
@@ -1736,17 +1737,16 @@ MediaDecoder::SetCDMProxy(CDMProxy* aPro
   NotifyWaitingForResourcesStatusChanged();
   return NS_OK;
 }
 
 CDMProxy*
 MediaDecoder::GetCDMProxy()
 {
   GetReentrantMonitor().AssertCurrentThreadIn();
-  MOZ_ASSERT(OnDecodeThread() || NS_IsMainThread());
   return mProxy;
 }
 #endif
 
 #ifdef MOZ_RAW
 bool
 MediaDecoder::IsRawEnabled()
 {
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaDecoderReader.h"
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #include "VideoUtils.h"
 #include "ImageContainer.h"
 
+#include "nsPrintfCString.h"
 #include "mozilla/mozalloc.h"
 #include <stdint.h>
 #include <algorithm>
 
 namespace mozilla {
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
@@ -22,16 +23,22 @@ namespace mozilla {
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
 #define DECODER_LOG(x, ...) \
   PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, ("Decoder=%p " x, mDecoder, ##__VA_ARGS__))
 #else
 #define DECODER_LOG(x, ...)
 #endif
 
+// Same workaround as MediaDecoderStateMachine.cpp.
+#define DECODER_WARN_HELPER(a, b) NS_WARNING b
+#define DECODER_WARN(x, ...) \
+  DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder, ##__VA_ARGS__).get()))
+
+
 PRLogModuleInfo* gMediaPromiseLog;
 
 void
 EnsureMediaPromiseLog()
 {
   if (!gMediaPromiseLog) {
     gMediaPromiseLog = PR_NewLogModule("MediaPromise");
   }
@@ -178,16 +185,53 @@ MediaDecoderReader::ComputeStartTime(con
     startTime = 0;
   }
   DECODER_LOG("ComputeStartTime first video frame start %lld", aVideo ? aVideo->mTime : -1);
   DECODER_LOG("ComputeStartTime first audio frame start %lld", aAudio ? aAudio->mTime : -1);
   NS_ASSERTION(startTime >= 0, "Start time is negative");
   return startTime;
 }
 
+nsRefPtr<MediaDecoderReader::MetadataPromise>
+MediaDecoderReader::CallReadMetadata()
+{
+  typedef ReadMetadataFailureReason Reason;
+
+  MOZ_ASSERT(OnDecodeThread());
+  mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
+  DECODER_LOG("MediaDecoderReader::CallReadMetadata");
+
+  // PreReadMetadata causes us to try to allocate various hardware and OS
+  // resources, which may not be available at the moment.
+  PreReadMetadata();
+  if (IsWaitingMediaResources()) {
+    return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__);
+  }
+
+  // Attempt to read the metadata.
+  nsRefPtr<MetadataHolder> metadata = new MetadataHolder();
+  nsresult rv = ReadMetadata(&metadata->mInfo, getter_Transfers(metadata->mTags));
+
+  // Reading metadata can cause us to discover that we need resources (like
+  // encryption keys).
+  if (IsWaitingMediaResources()) {
+    return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__);
+  }
+
+  // We're not waiting for anything. If we didn't get the metadata, that's an
+  // error.
+  if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) {
+    DECODER_WARN("ReadMetadata failed, rv=%x HasValidMedia=%d", rv, metadata->mInfo.HasValidMedia());
+    return MetadataPromise::CreateAndReject(Reason::METADATA_ERROR, __func__);
+  }
+
+  // Success!
+  return MetadataPromise::CreateAndResolve(metadata, __func__);
+}
+
 class ReRequestVideoWithSkipTask : public nsRunnable
 {
 public:
   ReRequestVideoWithSkipTask(MediaDecoderReader* aReader,
                              int64_t aTimeThreshold)
     : mReader(aReader)
     , mTimeThreshold(aTimeThreshold)
   {
@@ -352,8 +396,12 @@ MediaDecoderReader::Shutdown()
   }
 
   mDecoder = nullptr;
 
   return p;
 }
 
 } // namespace mozilla
+
+#undef DECODER_LOG
+#undef DECODER_WARN
+#undef DECODER_WARN_HELPER
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -17,43 +17,62 @@ namespace mozilla {
 
 namespace dom {
 class TimeRanges;
 }
 
 class MediaDecoderReader;
 class SharedDecoderManager;
 
-struct WaitForDataRejectValue {
+struct WaitForDataRejectValue
+{
   enum Reason {
     SHUTDOWN,
     CANCELED
   };
 
   WaitForDataRejectValue(MediaData::Type aType, Reason aReason)
     :mType(aType), mReason(aReason) {}
   MediaData::Type mType;
   Reason mReason;
 };
 
+class MetadataHolder
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataHolder)
+  MediaInfo mInfo;
+  nsAutoPtr<MetadataTags> mTags;
+
+private:
+  virtual ~MetadataHolder() {}
+};
+
+enum class ReadMetadataFailureReason : int8_t
+{
+  WAITING_FOR_RESOURCES,
+  METADATA_ERROR
+};
+
 // Encapsulates the decoding and reading of media data. Reading can either
 // synchronous and done on the calling "decode" thread, or asynchronous and
 // performed on a background thread, with the result being returned by
 // callback. Never hold the decoder monitor when calling into this class.
 // Unless otherwise specified, methods and fields of this class can only
 // be accessed on the decode task queue.
 class MediaDecoderReader {
 public:
   enum NotDecodedReason {
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
+  typedef MediaPromise<nsRefPtr<MetadataHolder>, ReadMetadataFailureReason, /* IsExclusive = */ true> MetadataPromise;
   typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason, /* IsExclusive = */ true> AudioDataPromise;
   typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason, /* IsExclusive = */ true> VideoDataPromise;
   typedef MediaPromise<int64_t, nsresult, /* IsExclusive = */ true> SeekPromise;
 
   // Note that, conceptually, WaitForData makes sense in a non-exclusive sense.
   // But in the current architecture it's only ever used exclusively (by MDSM),
   // so we mark it that way to verify our assumptions. If you have a use-case
   // for multiple WaitForData consumers, feel free to flip the exclusivity here.
@@ -143,16 +162,21 @@ public:
   // in buffering mode. Some readers support a promise-based mechanism by which
   // they notify the state machine when the data arrives.
   virtual bool IsWaitForDataSupported() { return false; }
   virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
+  // The ReadMetadata API is unfortunately synchronous. We should fix that at
+  // some point, but for now we can make things a bit better by using a
+  // promise-y API on top of a synchronous call.
+  nsRefPtr<MetadataPromise> CallReadMetadata();
+
   // A function that is called before ReadMetadata() call.
   virtual void PreReadMetadata() {};
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -1494,24 +1494,30 @@ void MediaDecoderStateMachine::SetDorman
       nsRefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__);
     }
     mPendingSeek.RejectIfExists(__func__);
     mCurrentSeek.RejectIfExists(__func__);
     SetState(DECODER_STATE_DORMANT);
     if (IsPlaying()) {
       StopPlayback();
     }
-    StopAudioThread();
-    FlushDecoding();
-    // Now that those threads are stopped, there's no possibility of
-    // mPendingWakeDecoder being needed again. Revoke it.
-    mPendingWakeDecoder = nullptr;
+
+    Reset();
+
+    // Note that we do not wait for the decode task queue to go idle before
+    // queuing the ReleaseMediaResources task - instead, we disconnect promises,
+    // reset state, and put a ResetDecode in the decode task queue. Any tasks
+    // that run after ResetDecode are supposed to run with a clean slate. We rely
+    // on that in other places (i.e. seeking), so it seems reasonable to rely on
+    // it here as well.
     DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(
     NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources));
     MOZ_ASSERT(NS_SUCCEEDED(rv));
+    // There's now no possibility of mPendingWakeDecoder being needed again. Revoke it.
+    mPendingWakeDecoder = nullptr;
     mDecoder->GetReentrantMonitor().NotifyAll();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
     mDecodingFrozenAtStateDecoding = true;
     ScheduleStateMachine();
     mCurrentFrameTime = 0;
     SetState(DECODER_STATE_DECODING_NONE);
     mDecoder->GetReentrantMonitor().NotifyAll();
   }
@@ -1555,53 +1561,31 @@ void MediaDecoderStateMachine::StartDeco
   mIsVideoPrerolling = !DonePrerollingVideo();
 
   // Ensure that we've got tasks enqueued to decode data if we need to.
   DispatchDecodeTasksIfNeeded();
 
   ScheduleStateMachine();
 }
 
-void MediaDecoderStateMachine::StartWaitForResources()
-{
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  NS_ASSERTION(OnDecodeThread(),
-               "Should be on decode thread.");
-  SetState(DECODER_STATE_WAIT_FOR_RESOURCES);
-  DECODER_LOG("StartWaitForResources");
-}
-
 void MediaDecoderStateMachine::NotifyWaitingForResourcesStatusChanged()
 {
-  AssertCurrentThreadInMonitor();
+  MOZ_ASSERT(OnStateMachineThread());
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DECODER_LOG("NotifyWaitingForResourcesStatusChanged");
-  RefPtr<nsIRunnable> task(
-    NS_NewRunnableMethod(this,
-      &MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged));
-  DecodeTaskQueue()->Dispatch(task);
-}
-
-void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged()
-{
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-
-  DECODER_LOG("DoNotifyWaitingForResourcesStatusChanged");
 
   if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
-    // The reader is no longer waiting for resources (say a hardware decoder),
-    // we can now proceed to decode metadata.
+    // Try again.
     SetState(DECODER_STATE_DECODING_NONE);
+    ScheduleStateMachine();
   } else if (mState == DECODER_STATE_WAIT_FOR_CDM &&
              !mReader->IsWaitingOnCDMResource()) {
     SetState(DECODER_STATE_DECODING_FIRSTFRAME);
     EnqueueDecodeFirstFrameTask();
   }
-
-  ScheduleStateMachine();
 }
 
 void MediaDecoderStateMachine::PlayInternal()
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Once we start playing, we don't want to minimize our prerolling, as we
@@ -1636,48 +1620,16 @@ void MediaDecoderStateMachine::PlayInter
   // when the state machine notices the decoder's state change to PLAYING.
   if (mState == DECODER_STATE_BUFFERING) {
     StartDecoding();
   }
 
   ScheduleStateMachine();
 }
 
-void MediaDecoderStateMachine::ResetPlayback()
-{
-  MOZ_ASSERT(OnStateMachineThread());
-
-  // We should be reseting because we're seeking, shutting down, or
-  // entering dormant state. We could also be in the process of going dormant,
-  // and have just switched to exiting dormant before we finished entering
-  // dormant, hence the DECODING_NONE case below.
-  AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(mState == DECODER_STATE_SEEKING ||
-             mState == DECODER_STATE_SHUTDOWN ||
-             mState == DECODER_STATE_DORMANT ||
-             mState == DECODER_STATE_DECODING_NONE);
-
-  // Audio thread should've been stopped at the moment. Otherwise, AudioSink
-  // might be accessing AudioQueue outside of the decoder monitor while we
-  // are clearing the queue and causes crash for no samples to be popped.
-  MOZ_ASSERT(!mAudioSink);
-
-  mVideoFrameEndTime = -1;
-  mDecodedVideoEndTime = -1;
-  mAudioStartTime = -1;
-  mAudioEndTime = -1;
-  mDecodedAudioEndTime = -1;
-  mAudioCompleted = false;
-  AudioQueue().Reset();
-  VideoQueue().Reset();
-  mFirstVideoFrameAfterSeek = nullptr;
-  mDropAudioUntilNextDiscontinuity = true;
-  mDropVideoUntilNextDiscontinuity = true;
-}
-
 void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer,
                                                      uint32_t aLength,
                                                      int64_t aOffset)
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
 
   // While playing an unseekable stream of unknown duration, mEndTime is
@@ -1767,29 +1719,16 @@ void MediaDecoderStateMachine::StopAudio
     }
     mAudioSink = nullptr;
   }
   // Wake up those waiting for audio sink to finish.
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 nsresult
-MediaDecoderStateMachine::EnqueueDecodeMetadataTask()
-{
-  AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
-
-  RefPtr<nsIRunnable> task(
-    NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata));
-  nsresult rv = DecodeTaskQueue()->Dispatch(task);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return NS_OK;
-}
-
-nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
 
   RefPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
   nsresult rv = TaskQueue()->Dispatch(task);
@@ -1921,24 +1860,18 @@ MediaDecoderStateMachine::InitiateSeek()
   // to display
   nsCOMPtr<nsIRunnable> startEvent =
       NS_NewRunnableMethodWithArg<MediaDecoderEventVisibility>(
         mDecoder,
         &MediaDecoder::SeekingStarted,
         mCurrentSeek.mTarget.mEventVisibility);
   NS_DispatchToMainThread(startEvent, NS_DISPATCH_NORMAL);
 
-  // The seek target is different than the current playback position,
-  // we'll need to seek the playback position, so shutdown our decode
-  // thread and audio sink.
-  StopAudioThread();
-  ResetPlayback();
-
-  // Put a reset in the pipe before seek.
-  ResetDecode();
+  // Reset our state machine and decoding pipeline before seeking.
+  Reset();
 
   // Do the seek.
   mSeekRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
                                     &MediaDecoderReader::Seek, mCurrentSeek.mTarget.mTime,
                                     GetEndTime())
     ->RefableThen(TaskQueue(), __func__, this,
                   &MediaDecoderStateMachine::OnSeekCompleted,
                   &MediaDecoderStateMachine::OnSeekFailed));
@@ -2200,69 +2133,26 @@ MediaDecoderStateMachine::DecodeError()
     nsCOMPtr<nsIRunnable> event =
       NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
     ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
     NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
   }
 }
 
 void
-MediaDecoderStateMachine::CallDecodeMetadata()
+MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  if (mState != DECODER_STATE_DECODING_METADATA) {
-    return;
-  }
-  if (NS_FAILED(DecodeMetadata())) {
-    DECODER_WARN("Decode metadata failed, shutting down decoder");
-    DecodeError();
-  }
-}
-
-nsresult MediaDecoderStateMachine::DecodeMetadata()
-{
-  AssertCurrentThreadInMonitor();
-  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
+  MOZ_ASSERT(OnStateMachineThread());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
-  DECODER_LOG("Decoding Media Headers");
-
-  nsresult res;
-  MediaInfo info;
-  bool isAwaitingResources = false;
-  {
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    mReader->PreReadMetadata();
-
-    if (mReader->IsWaitingMediaResources()) {
-      StartWaitForResources();
-      return NS_OK;
-    }
-    res = mReader->ReadMetadata(&info, getter_Transfers(mMetadataTags));
-    isAwaitingResources = mReader->IsWaitingMediaResources();
-  }
-
-  if (NS_SUCCEEDED(res) &&
-      mState == DECODER_STATE_DECODING_METADATA &&
-      isAwaitingResources) {
-    // change state to DECODER_STATE_WAIT_FOR_RESOURCES
-    StartWaitForResources();
-    // affect values only if ReadMetadata succeeds
-    return NS_OK;
-  }
-
-  if (NS_FAILED(res) || (!info.HasValidMedia())) {
-    DECODER_WARN("ReadMetadata failed, res=%x HasValidMedia=%d", res, info.HasValidMedia());
-    return NS_ERROR_FAILURE;
-  }
-
-  if (NS_SUCCEEDED(res)) {
-    mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
-  }
-
-  mInfo = info;
+  mMetadataRequest.Complete();
+
+  mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
+  mInfo = aMetadata->mInfo;
+  mMetadataTags = aMetadata->mTags.forget();
 
   if (HasVideo()) {
     DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
                 mReader->IsAsync(),
                 mReader->VideoIsHardwareAccelerated(),
                 GetAmpleVideoFrames());
   }
 
@@ -2270,33 +2160,43 @@ nsresult MediaDecoderStateMachine::Decod
   mGotDurationFromMetaData = (GetDuration() != -1) || mDurationSet;
 
   if (mGotDurationFromMetaData) {
     // We have all the information required: duration and size
     // Inform the element that we've loaded the metadata.
     EnqueueLoadedMetadataEvent();
   }
 
-  if (mState == DECODER_STATE_DECODING_METADATA) {
-    if (mReader->IsWaitingOnCDMResource()) {
-      // Metadata parsing was successful but we're still waiting for CDM caps
-      // to become available so that we can build the correct decryptor/decoder.
-      SetState(DECODER_STATE_WAIT_FOR_CDM);
-      return NS_OK;
-    }
-
-    SetState(DECODER_STATE_DECODING_FIRSTFRAME);
-    res = EnqueueDecodeFirstFrameTask();
-    if (NS_FAILED(res)) {
-      return NS_ERROR_FAILURE;
-    }
+  if (mReader->IsWaitingOnCDMResource()) {
+    // Metadata parsing was successful but we're still waiting for CDM caps
+    // to become available so that we can build the correct decryptor/decoder.
+    SetState(DECODER_STATE_WAIT_FOR_CDM);
+    return;
   }
+
+  SetState(DECODER_STATE_DECODING_FIRSTFRAME);
+  EnqueueDecodeFirstFrameTask();
   ScheduleStateMachine();
-
-  return NS_OK;
+}
+
+void
+MediaDecoderStateMachine::OnMetadataNotRead(ReadMetadataFailureReason aReason)
+{
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+  MOZ_ASSERT(OnStateMachineThread());
+  MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
+  mMetadataRequest.Complete();
+
+  if (aReason == ReadMetadataFailureReason::WAITING_FOR_RESOURCES) {
+    SetState(DECODER_STATE_WAIT_FOR_RESOURCES);
+  } else {
+    MOZ_ASSERT(aReason == ReadMetadataFailureReason::METADATA_ERROR);
+    DECODER_WARN("Decode metadata failed, shutting down decoder");
+    DecodeError();
+  }
 }
 
 void
 MediaDecoderStateMachine::EnqueueLoadedMetadataEvent()
 {
   nsAutoPtr<MediaInfo> info(new MediaInfo());
   *info = mInfo;
   MediaDecoderEventVisibility visibility = mSentLoadedMetadataEvent?
@@ -2669,18 +2569,17 @@ nsresult MediaDecoderStateMachine::RunSt
       mQueuedSeek.RejectIfExists(__func__);
       mPendingSeek.RejectIfExists(__func__);
       mCurrentSeek.RejectIfExists(__func__);
 
       if (IsPlaying()) {
         StopPlayback();
       }
 
-      StopAudioThread();
-      FlushDecoding();
+      Reset();
 
       // Put a task in the decode queue to shutdown the reader.
       // the queue to spin down.
       RefPtr<nsIRunnable> task;
       task = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ShutdownReader);
       DebugOnly<nsresult> rv = DecodeTaskQueue()->Dispatch(task);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
 
@@ -2694,26 +2593,35 @@ nsresult MediaDecoderStateMachine::RunSt
 
     case DECODER_STATE_WAIT_FOR_CDM:
     case DECODER_STATE_WAIT_FOR_RESOURCES: {
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_NONE: {
       SetState(DECODER_STATE_DECODING_METADATA);
-      // Ensure we have a decode thread to decode metadata.
-      return EnqueueDecodeMetadataTask();
+      ScheduleStateMachine();
+      return NS_OK;
     }
 
     case DECODER_STATE_DECODING_METADATA: {
+      if (!mMetadataRequest.Exists()) {
+        DECODER_LOG("Dispatching CallReadMetadata");
+        mMetadataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
+                                              &MediaDecoderReader::CallReadMetadata)
+          ->RefableThen(TaskQueue(), __func__, this,
+                        &MediaDecoderStateMachine::OnMetadataRead,
+                        &MediaDecoderStateMachine::OnMetadataNotRead));
+
+      }
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_FIRSTFRAME: {
-      // DECODER_STATE_DECODING_FIRSTFRAME will be started by DecodeMetadata
+      // DECODER_STATE_DECODING_FIRSTFRAME will be started by OnMetadataRead.
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING: {
       if (mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
           IsPlaying())
       {
         // We're playing, but the element/decoder is in paused state. Stop
@@ -2833,58 +2741,56 @@ nsresult MediaDecoderStateMachine::RunSt
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
 void
-MediaDecoderStateMachine::FlushDecoding()
+MediaDecoderStateMachine::Reset()
 {
   MOZ_ASSERT(OnStateMachineThread());
   AssertCurrentThreadInMonitor();
-
-  // Put a task in the decode queue to abort any decoding operations.
-  // The reader is not supposed to put any tasks to deliver samples into
-  // the queue after this runs (unless we request another sample from it).
-  ResetDecode();
-  {
-    // Wait for the ResetDecode to run and for the decoder to abort
-    // decoding operations and run any pending callbacks. This is
-    // important, as we don't want any pending tasks posted to the task
-    // queue by the reader to deliver any samples after we've posted the
-    // reader Shutdown() task below, as the sample-delivery tasks will
-    // keep video frames alive until after we've called Reader::Shutdown(),
-    // and shutdown on B2G will fail as there are outstanding video frames
-    // alive.
-    ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
-    DecodeTaskQueue()->AwaitIdle();
-  }
-
-  // We must reset playback so that all references to frames queued
-  // in the state machine are dropped, else subsequent calls to Shutdown()
-  // or ReleaseMediaResources() can fail on B2G.
-  ResetPlayback();
-}
-
-void
-MediaDecoderStateMachine::ResetDecode()
-{
-  MOZ_ASSERT(OnStateMachineThread());
-  AssertCurrentThreadInMonitor();
-
+  DECODER_LOG("MediaDecoderStateMachine::Reset");
+
+  // We should be resetting because we're seeking, shutting down, or entering
+  // dormant state. We could also be in the process of going dormant, and have
+  // just switched to exiting dormant before we finished entering dormant,
+  // hence the DECODING_NONE case below.
+  MOZ_ASSERT(mState == DECODER_STATE_SEEKING ||
+             mState == DECODER_STATE_SHUTDOWN ||
+             mState == DECODER_STATE_DORMANT ||
+             mState == DECODER_STATE_DECODING_NONE);
+
+  // Stop the audio thread. Otherwise, AudioSink might be accessing AudioQueue
+  // outside of the decoder monitor while we are clearing the queue and causes
+  // crash for no samples to be popped.
+  StopAudioThread();
+
+  mVideoFrameEndTime = -1;
+  mDecodedVideoEndTime = -1;
+  mAudioStartTime = -1;
+  mAudioEndTime = -1;
+  mDecodedAudioEndTime = -1;
+  mAudioCompleted = false;
+  AudioQueue().Reset();
+  VideoQueue().Reset();
+  mFirstVideoFrameAfterSeek = nullptr;
+  mDropAudioUntilNextDiscontinuity = true;
+  mDropVideoUntilNextDiscontinuity = true;
+  mDecodeToSeekTarget = false;
+
+  mMetadataRequest.DisconnectIfExists();
   mAudioDataRequest.DisconnectIfExists();
   mAudioWaitRequest.DisconnectIfExists();
   mVideoDataRequest.DisconnectIfExists();
   mVideoWaitRequest.DisconnectIfExists();
   mSeekRequest.DisconnectIfExists();
 
-  mDecodeToSeekTarget = false;
-
   RefPtr<nsRunnable> resetTask =
     NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
   DecodeTaskQueue()->Dispatch(resetTask);
 }
 
 void MediaDecoderStateMachine::RenderVideoFrame(VideoData* aData,
                                                 TimeStamp aTarget)
 {
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -362,19 +362,18 @@ public:
   void QueueMetadata(int64_t aPublishTime,
                      nsAutoPtr<MediaInfo> aInfo,
                      nsAutoPtr<MetadataTags> aTags);
 
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying() const;
 
-  // Dispatch DoNotifyWaitingForResourcesStatusChanged task to the task queue.
   // Called when the reader may have acquired the hardware resources required
-  // to begin decoding. The decoder monitor must be held while calling this.
+  // to begin decoding.
   void NotifyWaitingForResourcesStatusChanged();
 
   // Notifies the state machine that should minimize the number of samples
   // decoded we preroll, until playback starts. The first time playback starts
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
   // samples in advance of when they're needed for playback.
   void SetMinimizePrerollUntilPlaybackStarts();
@@ -402,18 +401,19 @@ public:
   }
 
   void OnWaitForDataRejected(WaitForDataRejectValue aRejection)
   {
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     WaitRequestRef(aRejection.mType).Complete();
   }
 
-  // Resets all state related to decoding, emptying all buffers etc.
-  void ResetDecode();
+  // Resets all state related to decoding and playback, emptying all buffers
+  // and aborting all pending operations on the decode task queue.
+  void Reset();
 
 private:
   void AcquireMonitorAndInvokeDecodeError();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
   void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); }
@@ -460,18 +460,16 @@ protected:
   };
   WakeDecoderRunnable* GetWakeDecoderRunnable();
 
   MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
   MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
 
   nsresult FinishDecodeFirstFrame();
 
-  nsAutoPtr<MetadataTags> mMetadataTags;
-
   // True if our buffers of decoded audio are not full, and we should
   // decode more.
   bool NeedToDecodeAudio();
 
   // True if our buffers of decoded video are not full, and we should
   // decode more.
   bool NeedToDecodeVideo();
 
@@ -509,24 +507,16 @@ protected:
   bool HasFutureAudio();
 
   // Returns true if we recently exited "quick buffering" mode.
   bool JustExitedQuickBuffering();
 
   // Dispatches an asynchronous event to update the media element's ready state.
   void UpdateReadyState();
 
-  // Resets playback timing data. Called when we seek, on the decode thread.
-  void ResetPlayback();
-
-  // Orders the Reader to stop decoding, and blocks until the Reader
-  // has stopped decoding and finished delivering samples, then calls
-  // ResetPlayback() to discard all enqueued data.
-  void FlushDecoding();
-
   // Called when AudioSink reaches the end. |mPlayStartTime| and
   // |mPlayDuration| are updated to provide a good base for calculating video
   // stream time.
   void ResyncAudioClock();
 
   // Returns the audio clock, if we have audio, or -1 if we don't.
   // Called on the state machine thread.
   int64_t GetAudioClock() const;
@@ -585,18 +575,16 @@ protected:
   void StartDecoding();
 
   // Moves the decoder into the shutdown state, and dispatches an error
   // event to the media element. This begins shutting down the decoder.
   // The decoder monitor must be held. This is only called on the
   // decode thread.
   void DecodeError();
 
-  void StartWaitForResources();
-
   // Dispatches a task to the decode task queue to begin decoding metadata.
   // This is threadsafe and can be called on any thread.
   // The decoder monitor must be held.
   nsresult EnqueueDecodeMetadataTask();
 
   // Dispatches a LoadedMetadataEvent.
   // This is threadsafe and can be called on any thread.
   // The decoder monitor must be held.
@@ -654,22 +642,19 @@ protected:
   // decoded and playable. This is the sum of the number of usecs of audio which
   // is decoded and in the reader's audio queue, and the usecs of unplayed audio
   // which has been pushed to the audio hardware for playback. Note that after
   // calling this, the audio hardware may play some of the audio pushed to
   // hardware, so this can only be used as a upper bound. The decoder monitor
   // must be held when calling this. Called on the decode thread.
   int64_t GetDecodedAudioDuration();
 
-  // Load metadata. Called on the decode thread. The decoder monitor
-  // must be held with exactly one lock count.
-  nsresult DecodeMetadata();
-
-  // Wraps the call to DecodeMetadata(), signals a DecodeError() on failure.
-  void CallDecodeMetadata();
+  // Promise callbacks for metadata reading.
+  void OnMetadataRead(MetadataHolder* aMetadata);
+  void OnMetadataNotRead(ReadMetadataFailureReason aReason);
 
   // Initiate first content decoding. Called on the state machine thread.
   // The decoder monitor must be held with exactly one lock count.
   nsresult DecodeFirstFrame();
 
   // Wraps the call to DecodeFirstFrame(), signals a DecodeError() on failure.
   void CallDecodeFirstFrame();
 
@@ -728,20 +713,16 @@ protected:
 
   // Called by the AudioSink to signal that all outstanding work is complete
   // and the sink is shutting down.
   void OnAudioSinkComplete();
 
   // Called by the AudioSink to signal errors.
   void OnAudioSinkError();
 
-  // The state machine may move into DECODING_METADATA if we are in
-  // DECODER_STATE_WAIT_FOR_RESOURCES.
-  void DoNotifyWaitingForResourcesStatusChanged();
-
   // Return true if the video decoder's decode speed can not catch up the
   // play time.
   bool NeedToSkipToNextKeyframe();
 
   // The decoder object that created this state machine. The state machine
   // holds a strong reference to the decoder to ensure that the decoder stays
   // alive once media element has started the decoder shutdown process, and has
   // dropped its reference to the decoder. This enables the state machine to
@@ -1181,20 +1162,25 @@ protected:
   // Track the current seek promise made by the reader.
   MediaPromiseConsumerHolder<MediaDecoderReader::SeekPromise> mSeekRequest;
 
   // We record the playback position before we seek in order to
   // determine where the seek terminated relative to the playback position
   // we were at before the seek.
   int64_t mCurrentTimeBeforeSeek;
 
+  // Track our request for metadata from the reader.
+  MediaPromiseConsumerHolder<MediaDecoderReader::MetadataPromise> mMetadataRequest;
+
   // Stores presentation info required for playback. The decoder monitor
   // must be held when accessing this.
   MediaInfo mInfo;
 
+  nsAutoPtr<MetadataTags> mMetadataTags;
+
   mozilla::MediaMetadataManager mMetadataManager;
 
   MediaDecoderOwner::NextFrameStatus mLastFrameStatus;
 
   mozilla::RollingMean<uint32_t, uint32_t> mCorruptFrames;
 
   bool mDisabledHardwareAcceleration;
 
--- a/dom/media/fmp4/AVCCDecoderModule.cpp
+++ b/dom/media/fmp4/AVCCDecoderModule.cpp
@@ -269,18 +269,18 @@ already_AddRefed<MediaDataDecoder>
 AVCCDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                       layers::LayersBackend aLayersBackend,
                                       layers::ImageContainer* aImageContainer,
                                       FlushableMediaTaskQueue* aVideoTaskQueue,
                                       MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder;
 
-  if ((strcmp(aConfig.mime_type, "video/avc") &&
-       strcmp(aConfig.mime_type, "video/mp4")) ||
+  if ((!aConfig.mime_type.EqualsLiteral("video/avc") &&
+       !aConfig.mime_type.EqualsLiteral("video/mp4")) ||
       !mPDM->DecoderNeedsAVCC(aConfig)) {
     // There is no need for an AVCC wrapper for non-AVC content.
     decoder = mPDM->CreateVideoDecoder(aConfig,
                                        aLayersBackend,
                                        aImageContainer,
                                        aVideoTaskQueue,
                                        aCallback);
   } else {
@@ -300,20 +300,20 @@ AVCCDecoderModule::CreateAudioDecoder(co
                                       MediaDataDecoderCallback* aCallback)
 {
   return mPDM->CreateAudioDecoder(aConfig,
                                   aAudioTaskQueue,
                                   aCallback);
 }
 
 bool
-AVCCDecoderModule::SupportsAudioMimeType(const char* aMimeType)
+AVCCDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType)
 {
   return mPDM->SupportsAudioMimeType(aMimeType);
 }
 
 bool
-AVCCDecoderModule::SupportsVideoMimeType(const char* aMimeType)
+AVCCDecoderModule::SupportsVideoMimeType(const nsACString& aMimeType)
 {
   return mPDM->SupportsVideoMimeType(aMimeType);
 }
 
 } // namespace mozilla
--- a/dom/media/fmp4/AVCCDecoderModule.h
+++ b/dom/media/fmp4/AVCCDecoderModule.h
@@ -36,18 +36,18 @@ public:
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                      FlushableMediaTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
-  virtual bool SupportsAudioMimeType(const char* aMimeType) override;
-  virtual bool SupportsVideoMimeType(const char* aMimeType) override;
+  virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override;
+  virtual bool SupportsVideoMimeType(const nsACString& aMimeType) override;
 
 private:
   nsRefPtr<PlatformDecoderModule> mPDM;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_AVCCDecoderModule_h
--- a/dom/media/fmp4/BlankDecoderModule.cpp
+++ b/dom/media/fmp4/BlankDecoderModule.cpp
@@ -233,17 +233,17 @@ public:
     nsRefPtr<MediaDataDecoder> decoder =
       new BlankMediaDataDecoder<BlankAudioDataCreator>(creator,
                                                        aAudioTaskQueue,
                                                        aCallback);
     return decoder.forget();
   }
 
   virtual bool
-  SupportsAudioMimeType(const char* aMimeType) override
+  SupportsAudioMimeType(const nsACString& aMimeType) override
   {
     return true;
   }
 
 };
 
 already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule()
 {
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -306,29 +306,29 @@ MP4Reader::ExtractCryptoInitData(nsTArra
   MOZ_ASSERT(mCrypto.valid);
   const nsTArray<mp4_demuxer::PsshInfo>& psshs = mCrypto.pssh;
   for (uint32_t i = 0; i < psshs.Length(); i++) {
     aInitData.AppendElements(psshs[i].data);
   }
 }
 
 bool
-MP4Reader::IsSupportedAudioMimeType(const char* aMimeType)
+MP4Reader::IsSupportedAudioMimeType(const nsACString& aMimeType)
 {
-  return (!strcmp(aMimeType, "audio/mpeg") ||
-          !strcmp(aMimeType, "audio/mp4a-latm")) &&
+  return (aMimeType.EqualsLiteral("audio/mpeg") ||
+          aMimeType.EqualsLiteral("audio/mp4a-latm")) &&
          mPlatform->SupportsAudioMimeType(aMimeType);
 }
 
 bool
-MP4Reader::IsSupportedVideoMimeType(const char* aMimeType)
+MP4Reader::IsSupportedVideoMimeType(const nsACString& aMimeType)
 {
-  return (!strcmp(aMimeType, "video/mp4") ||
-          !strcmp(aMimeType, "video/avc") ||
-          !strcmp(aMimeType, "video/x-vnd.on2.vp6")) &&
+  return (aMimeType.EqualsLiteral("video/mp4") ||
+          aMimeType.EqualsLiteral("video/avc") ||
+          aMimeType.EqualsLiteral("video/x-vnd.on2.vp6")) &&
          mPlatform->SupportsVideoMimeType(aMimeType);
 }
 
 void
 MP4Reader::PreReadMetadata()
 {
   if (mPlatform) {
     RequestCodecResource();
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -128,18 +128,18 @@ private:
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
   void Output(mp4_demuxer::TrackType aType, MediaData* aSample);
   void InputExhausted(mp4_demuxer::TrackType aTrack);
   void Error(mp4_demuxer::TrackType aTrack);
   void Flush(mp4_demuxer::TrackType aTrack);
   void DrainComplete(mp4_demuxer::TrackType aTrack);
   void UpdateIndex();
-  bool IsSupportedAudioMimeType(const char* aMimeType);
-  bool IsSupportedVideoMimeType(const char* aMimeType);
+  bool IsSupportedAudioMimeType(const nsACString& aMimeType);
+  bool IsSupportedVideoMimeType(const nsACString& aMimeType);
   void NotifyResourcesStatusChanged();
   void RequestCodecResource();
   virtual bool IsWaitingOnCDMResource() override;
 
   Microseconds GetNextKeyframeTime();
   bool ShouldSkip(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
   size_t SizeOfQueue(TrackType aTrack);
--- a/dom/media/fmp4/PlatformDecoderModule.cpp
+++ b/dom/media/fmp4/PlatformDecoderModule.cpp
@@ -175,25 +175,25 @@ PlatformDecoderModule::CreatePDM()
   if (sGMPDecoderEnabled) {
     nsRefPtr<PlatformDecoderModule> m(new AVCCDecoderModule(new GMPDecoderModule()));
     return m.forget();
   }
   return nullptr;
 }
 
 bool
-PlatformDecoderModule::SupportsAudioMimeType(const char* aMimeType)
+PlatformDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType)
 {
-  return !strcmp(aMimeType, "audio/mp4a-latm");
+  return aMimeType.EqualsLiteral("audio/mp4a-latm");
 }
 
 bool
-PlatformDecoderModule::SupportsVideoMimeType(const char* aMimeType)
+PlatformDecoderModule::SupportsVideoMimeType(const nsACString& aMimeType)
 {
-  return !strcmp(aMimeType, "video/mp4") || !strcmp(aMimeType, "video/avc");
+  return aMimeType.EqualsLiteral("video/mp4") || aMimeType.EqualsLiteral("video/avc");
 }
 
 bool
 PlatformDecoderModule::DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig)
 {
   return false;
 }
 
--- a/dom/media/fmp4/PlatformDecoderModule.h
+++ b/dom/media/fmp4/PlatformDecoderModule.h
@@ -117,18 +117,18 @@ public:
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                      FlushableMediaTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) = 0;
 
   // An audio decoder module must support AAC by default.
   // If more audio codec is to be supported, SupportsAudioMimeType will have
   // to be extended
-  virtual bool SupportsAudioMimeType(const char* aMimeType);
-  virtual bool SupportsVideoMimeType(const char* aMimeType);
+  virtual bool SupportsAudioMimeType(const nsACString& aMimeType);
+  virtual bool SupportsVideoMimeType(const nsACString& aMimeType);
 
   // Indicates if the video decoder requires AVCC format.
   virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig);
 
   virtual void DisableHardwareAcceleration() {}
 
   virtual bool SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig& aConfig) const {
     return true;
--- a/dom/media/fmp4/android/AndroidDecoderModule.cpp
+++ b/dom/media/fmp4/android/AndroidDecoderModule.cpp
@@ -13,34 +13,31 @@
 
 #include "MediaData.h"
 
 #include "mp4_demuxer/AnnexB.h"
 #include "mp4_demuxer/DecoderData.h"
 
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
+#include "nsPromiseFlatString.h"
 
 #include <jni.h>
 #include <string.h>
 
 using namespace mozilla;
 using namespace mozilla::gl;
 using namespace mozilla::widget::sdk;
 
 namespace mozilla {
 
-static MediaCodec::LocalRef CreateDecoder(const char* aMimeType)
+static MediaCodec::LocalRef CreateDecoder(const nsACString& aMimeType)
 {
-  if (!aMimeType) {
-    return nullptr;
-  }
-
   MediaCodec::LocalRef codec;
-  NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(aMimeType, &codec), nullptr);
+  NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(PromiseFlatCString(aMimeType).get(), &codec), nullptr);
   return codec;
 }
 
 class VideoDataDecoder : public MediaCodecDataDecoder {
 public:
   VideoDataDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                    MediaFormat::Param aFormat, MediaDataDecoderCallback* aCallback,
                    layers::ImageContainer* aImageContainer)
@@ -246,17 +243,17 @@ public:
                                              numChannels,
                                              sampleRate);
     mCallback->Output(data);
     return NS_OK;
   }
 };
 
 
-bool AndroidDecoderModule::SupportsAudioMimeType(const char* aMimeType) {
+bool AndroidDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType) {
   return static_cast<bool>(CreateDecoder(aMimeType));
 }
 
 already_AddRefed<MediaDataDecoder>
 AndroidDecoderModule::CreateVideoDecoder(
                                 const mp4_demuxer::VideoDecoderConfig& aConfig,
                                 layers::LayersBackend aLayersBackend,
                                 layers::ImageContainer* aImageContainer,
@@ -295,21 +292,21 @@ AndroidDecoderModule::CreateAudioDecoder
   nsRefPtr<MediaDataDecoder> decoder =
     new AudioDataDecoder(aConfig, format, aCallback);
 
   return decoder.forget();
 
 }
 
 MediaCodecDataDecoder::MediaCodecDataDecoder(MediaData::Type aType,
-                                             const char* aMimeType,
+                                             const nsACString& aMimeType,
                                              MediaFormat::Param aFormat,
                                              MediaDataDecoderCallback* aCallback)
   : mType(aType)
-  , mMimeType(strdup(aMimeType))
+  , mMimeType(aMimeType)
   , mFormat(aFormat)
   , mCallback(aCallback)
   , mInputBuffers(nullptr)
   , mOutputBuffers(nullptr)
   , mMonitor("MediaCodecDataDecoder::mMonitor")
   , mFlushing(false)
   , mDraining(false)
   , mStopping(false)
--- a/dom/media/fmp4/android/AndroidDecoderModule.h
+++ b/dom/media/fmp4/android/AndroidDecoderModule.h
@@ -30,41 +30,41 @@ public:
   CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                      FlushableMediaTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
 
   AndroidDecoderModule() {}
   virtual ~AndroidDecoderModule() {}
 
-  virtual bool SupportsAudioMimeType(const char* aMimeType) override;
+  virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override;
 };
 
 class MediaCodecDataDecoder : public MediaDataDecoder {
 public:
 
   MediaCodecDataDecoder(MediaData::Type aType,
-                        const char* aMimeType,
+                        const nsACString& aMimeType,
                         widget::sdk::MediaFormat::Param aFormat,
                         MediaDataDecoderCallback* aCallback);
 
   virtual ~MediaCodecDataDecoder();
 
   virtual nsresult Init() override;
   virtual nsresult Flush() override;
   virtual nsresult Drain() override;
   virtual nsresult Shutdown() override;
   virtual nsresult Input(mp4_demuxer::MP4Sample* aSample);
 
 protected:
   friend class AndroidDecoderModule;
 
   MediaData::Type mType;
 
-  nsAutoPtr<char> mMimeType;
+  nsAutoCString mMimeType;
   widget::sdk::MediaFormat::GlobalRef mFormat;
 
   MediaDataDecoderCallback* mCallback;
 
   widget::sdk::MediaCodec::GlobalRef mDecoder;
 
   jni::ObjectArray::GlobalRef mInputBuffers;
   jni::ObjectArray::GlobalRef mOutputBuffers;
--- a/dom/media/fmp4/apple/AppleATDecoder.cpp
+++ b/dom/media/fmp4/apple/AppleATDecoder.cpp
@@ -30,24 +30,24 @@ AppleATDecoder::AppleATDecoder(const mp4
   , mTaskQueue(aAudioTaskQueue)
   , mCallback(aCallback)
   , mConverter(nullptr)
   , mStream(nullptr)
 {
   MOZ_COUNT_CTOR(AppleATDecoder);
   LOG("Creating Apple AudioToolbox decoder");
   LOG("Audio Decoder configuration: %s %d Hz %d channels %d bits per channel",
-      mConfig.mime_type,
+      mConfig.mime_type.get(),
       mConfig.samples_per_second,
       mConfig.channel_count,
       mConfig.bits_per_sample);
 
-  if (!strcmp(mConfig.mime_type, "audio/mpeg")) {
+  if (mConfig.mime_type.EqualsLiteral("audio/mpeg")) {
     mFormatID = kAudioFormatMPEGLayer3;
-  } else if (!strcmp(mConfig.mime_type, "audio/mp4a-latm")) {
+  } else if (mConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) {
     mFormatID = kAudioFormatMPEG4AAC;
   } else {
     mFormatID = 0;
   }
 }
 
 AppleATDecoder::~AppleATDecoder()
 {
--- a/dom/media/fmp4/apple/AppleDecoderModule.cpp
+++ b/dom/media/fmp4/apple/AppleDecoderModule.cpp
@@ -187,19 +187,19 @@ AppleDecoderModule::CreateAudioDecoder(c
                                        MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder =
     new AppleATDecoder(aConfig, aAudioTaskQueue, aCallback);
   return decoder.forget();
 }
 
 bool
-AppleDecoderModule::SupportsAudioMimeType(const char* aMimeType)
+AppleDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType)
 {
-  return !strcmp(aMimeType, "audio/mp4a-latm") || !strcmp(aMimeType, "audio/mpeg");
+  return aMimeType.EqualsLiteral("audio/mp4a-latm") || aMimeType.EqualsLiteral("audio/mpeg");
 }
 
 bool
 AppleDecoderModule::DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig)
 {
   return true;
 }
 
--- a/dom/media/fmp4/apple/AppleDecoderModule.h
+++ b/dom/media/fmp4/apple/AppleDecoderModule.h
@@ -27,17 +27,17 @@ public:
                      MediaDataDecoderCallback* aCallback) override;
 
   // Decode thread.
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                      FlushableMediaTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
-  virtual bool SupportsAudioMimeType(const char* aMimeType) override;
+  virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override;
   virtual bool
   DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) override;
 
   static void Init();
   static nsresult CanDecode();
 
 private:
   friend class InitTask;
--- a/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.cpp
+++ b/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.cpp
@@ -165,23 +165,23 @@ nsresult
 FFmpegAudioDecoder<LIBAV_VER>::Drain()
 {
   mTaskQueue->AwaitIdle();
   mCallback->DrainComplete();
   return Flush();
 }
 
 AVCodecID
-FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const char* aMimeType)
+FFmpegAudioDecoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType)
 {
-  if (!strcmp(aMimeType, "audio/mpeg")) {
+  if (aMimeType.EqualsLiteral("audio/mpeg")) {
     return AV_CODEC_ID_MP3;
   }
 
-  if (!strcmp(aMimeType, "audio/mp4a-latm")) {
+  if (aMimeType.EqualsLiteral("audio/mp4a-latm")) {
     return AV_CODEC_ID_AAC;
   }
 
   return AV_CODEC_ID_NONE;
 }
 
 FFmpegAudioDecoder<LIBAV_VER>::~FFmpegAudioDecoder()
 {
--- a/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.h
+++ b/dom/media/fmp4/ffmpeg/FFmpegAudioDecoder.h
@@ -3,17 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __FFmpegAACDecoder_h__
 #define __FFmpegAACDecoder_h__
 
 #include "FFmpegDataDecoder.h"
-#include "mp4_demuxer/DecoderData.h"
 
 namespace mozilla
 {
 
 template <int V> class FFmpegAudioDecoder
 {
 };
 
@@ -24,17 +23,17 @@ public:
   FFmpegAudioDecoder(FlushableMediaTaskQueue* aTaskQueue,
                      MediaDataDecoderCallback* aCallback,
                      const mp4_demuxer::AudioDecoderConfig& aConfig);
   virtual ~FFmpegAudioDecoder();
 
   virtual nsresult Init() override;
   virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) override;
   virtual nsresult Drain() override;
-  static AVCodecID GetCodecId(const char* aMimeType);
+  static AVCodecID GetCodecId(const nsACString& aMimeType);
 
 private:
   void DecodePacket(mp4_demuxer::MP4Sample* aSample);
 
   MediaDataDecoderCallback* mCallback;
 };
 
 } // namespace mozilla
--- a/dom/media/fmp4/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/fmp4/ffmpeg/FFmpegDecoderModule.h
@@ -46,22 +46,22 @@ public:
                      FlushableMediaTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override
   {
     nsRefPtr<MediaDataDecoder> decoder =
       new FFmpegAudioDecoder<V>(aAudioTaskQueue, aCallback, aConfig);
     return decoder.forget();
   }
 
-  virtual bool SupportsAudioMimeType(const char* aMimeType) override
+  virtual bool SupportsAudioMimeType(const nsACString& aMimeType) override
   {
     return FFmpegAudioDecoder<V>::GetCodecId(aMimeType) != AV_CODEC_ID_NONE;
   }
 
-  virtual bool SupportsVideoMimeType(const char* aMimeType) override
+  virtual bool SupportsVideoMimeType(const nsACString& aMimeType) override
   {
     return FFmpegH264Decoder<V>::GetCodecId(aMimeType) != AV_CODEC_ID_NONE;
   }
 
   virtual bool DecoderNeedsAVCC(const mp4_demuxer::VideoDecoderConfig& aConfig) override
   {
     return true;
   }
--- a/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
+++ b/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.cpp
@@ -282,22 +282,22 @@ FFmpegH264Decoder<LIBAV_VER>::Flush()
 }
 
 FFmpegH264Decoder<LIBAV_VER>::~FFmpegH264Decoder()
 {
   MOZ_COUNT_DTOR(FFmpegH264Decoder);
 }
 
 AVCodecID
-FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const char* aMimeType)
+FFmpegH264Decoder<LIBAV_VER>::GetCodecId(const nsACString& aMimeType)
 {
-  if (!strcmp(aMimeType, "video/avc") || !strcmp(aMimeType, "video/mp4")) {
+  if (aMimeType.EqualsLiteral("video/avc") || aMimeType.EqualsLiteral("video/mp4")) {
     return AV_CODEC_ID_H264;
   }
 
-  if (!strcmp(aMimeType, "video/x-vnd.on2.vp6")) {
+  if (aMimeType.EqualsLiteral("video/x-vnd.on2.vp6")) {
     return AV_CODEC_ID_VP6F;
   }
 
   return AV_CODEC_ID_NONE;
 }
 
 } // namespace mozilla
--- a/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.h
+++ b/dom/media/fmp4/ffmpeg/FFmpegH264Decoder.h
@@ -35,17 +35,17 @@ public:
                     const mp4_demuxer::VideoDecoderConfig& aConfig,
                     ImageContainer* aImageContainer);
   virtual ~FFmpegH264Decoder();
 
   virtual nsresult Init() override;
   virtual nsresult Input(mp4_demuxer::MP4Sample* aSample) override;
   virtual nsresult Drain() override;
   virtual nsresult Flush() override;
-  static AVCodecID GetCodecId(const char* aMimeType);
+  static AVCodecID GetCodecId(const nsACString& aMimeType);
 
 private:
   void DecodeFrame(mp4_demuxer::MP4Sample* aSample);
   DecodeResult DoDecodeFrame(mp4_demuxer::MP4Sample* aSample);
   void DoDrain();
   void OutputDelayedFrames();
 
   /**
--- a/dom/media/fmp4/gmp/GMPDecoderModule.cpp
+++ b/dom/media/fmp4/gmp/GMPDecoderModule.cpp
@@ -41,17 +41,17 @@ CreateDecoderWrapper(MediaDataDecoderCal
 
 already_AddRefed<MediaDataDecoder>
 GMPDecoderModule::CreateVideoDecoder(const mp4_demuxer::VideoDecoderConfig& aConfig,
                                      layers::LayersBackend aLayersBackend,
                                      layers::ImageContainer* aImageContainer,
                                      FlushableMediaTaskQueue* aVideoTaskQueue,
                                      MediaDataDecoderCallback* aCallback)
 {
-  if (strcmp(aConfig.mime_type, "video/avc") != 0) {
+  if (!aConfig.mime_type.EqualsLiteral("video/avc")) {
     return nullptr;
   }
 
   nsRefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
   wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig,
                                               aLayersBackend,
                                               aImageContainer,
                                               aVideoTaskQueue,
@@ -59,17 +59,17 @@ GMPDecoderModule::CreateVideoDecoder(con
   return wrapper.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 GMPDecoderModule::CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                                      FlushableMediaTaskQueue* aAudioTaskQueue,
                                      MediaDataDecoderCallback* aCallback)
 {
-  if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) {
+  if (!aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) {
     return nullptr;
   }
 
   nsRefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
   wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig,
                                               aAudioTaskQueue,
                                               wrapper->Callback()));
   return wrapper.forget();
--- a/dom/media/fmp4/gonk/GonkAudioDecoderManager.cpp
+++ b/dom/media/fmp4/gonk/GonkAudioDecoderManager.cpp
@@ -46,17 +46,17 @@ GonkAudioDecoderManager::GonkAudioDecode
   , mUseAdts(true)
   , mAudioBuffer(nullptr)
 {
   MOZ_COUNT_CTOR(GonkAudioDecoderManager);
   MOZ_ASSERT(mAudioChannels);
   mUserData.AppendElements(aConfig.audio_specific_config->Elements(),
                            aConfig.audio_specific_config->Length());
   // Pass through mp3 without applying an ADTS header.
-  if (strcmp(aConfig.mime_type, "audio/mp4a-latm") != 0) {
+  if (!aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) {
       mUseAdts = false;
   }
 }
 
 GonkAudioDecoderManager::~GonkAudioDecoderManager()
 {
   MOZ_COUNT_DTOR(GonkAudioDecoderManager);
 }
--- a/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp
+++ b/dom/media/fmp4/wmf/WMFAudioMFTManager.cpp
@@ -72,19 +72,19 @@ WMFAudioMFTManager::WMFAudioMFTManager(
   : mAudioChannels(aConfig.channel_count)
   , mAudioRate(aConfig.samples_per_second)
   , mAudioFrameOffset(0)
   , mAudioFrameSum(0)
   , mMustRecaptureAudioPosition(true)
 {
   MOZ_COUNT_CTOR(WMFAudioMFTManager);
 
-  if (!strcmp(aConfig.mime_type, "audio/mpeg")) {
+  if (aConfig.mime_type.EqualsLiteral("audio/mpeg")) {
     mStreamType = MP3;
-  } else if (!strcmp(aConfig.mime_type, "audio/mp4a-latm")) {
+  } else if (aConfig.mime_type.EqualsLiteral("audio/mp4a-latm")) {
     mStreamType = AAC;
     AACAudioSpecificConfigToUserData(aConfig.aac_profile,
                                      aConfig.audio_specific_config->Elements(),
                                      aConfig.audio_specific_config->Length(),
                                      mUserData);
   } else {
     mStreamType = Unknown;
   }
--- a/dom/media/fmp4/wmf/WMFDecoderModule.cpp
+++ b/dom/media/fmp4/wmf/WMFDecoderModule.cpp
@@ -113,29 +113,29 @@ bool
 WMFDecoderModule::SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig& aConfig) const
 {
   // If DXVA is enabled, but we're not going to use it for this specific config, then
   // we can't use the shared decoder.
   return !sDXVAEnabled || ShouldUseDXVA(aConfig);
 }
 
 bool
-WMFDecoderModule::SupportsVideoMimeType(const char* aMimeType)
+WMFDecoderModule::SupportsVideoMimeType(const nsACString& aMimeType)
 {
-  return !strcmp(aMimeType, "video/mp4") ||
-         !strcmp(aMimeType, "video/avc") ||
-         !strcmp(aMimeType, "video/webm; codecs=vp8") ||
-         !strcmp(aMimeType, "video/webm; codecs=vp9");
+  return aMimeType.EqualsLiteral("video/mp4") ||
+         aMimeType.EqualsLiteral("video/avc") ||
+         aMimeType.EqualsLiteral("video/webm; codecs=vp8") ||
+         aMimeType.EqualsLiteral("video/webm; codecs=vp9");
 }
 
 bool
-WMFDecoderModule::SupportsAudioMimeType(const char* aMimeType)
+WMFDecoderModule::SupportsAudioMimeType(const nsACString& aMimeType)
 {
-  return !strcmp(aMimeType, "audio/mp4a-latm") ||
-         !strcmp(aMimeType, "audio/mpeg");
+  return aMimeType.EqualsLiteral("audio/mp4a-latm") ||
+         aMimeType.EqualsLiteral("audio/mpeg");
 }
 
 static bool
 ClassesRootRegKeyExists(const nsAString& aRegKeyPath)
 {
   nsresult rv;
 
   nsCOMPtr<nsIWindowsRegKey> regKey =
--- a/dom/media/fmp4/wmf/WMFDecoderModule.h
+++ b/dom/media/fmp4/wmf/WMFDecoderModule.h
@@ -26,18 +26,18 @@ public:
                      FlushableMediaTaskQueue* aVideoTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                      FlushableMediaTaskQueue* aAudioTaskQueue,
                      MediaDataDecoderCallback* aCallback) override;
 
-  bool SupportsVideoMimeType(const char* aMimeType) override;
-  bool SupportsAudioMimeType(const char* aMimeType) override;
+  bool SupportsVideoMimeType(const nsACString& aMimeType) override;
+  bool SupportsAudioMimeType(const nsACString& aMimeType) override;
 
   virtual void DisableHardwareAcceleration() override
   {
     sDXVAEnabled = false;
   }
 
   virtual bool SupportsSharedDecoders(const mp4_demuxer::VideoDecoderConfig& aConfig) const override;
 
--- a/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/fmp4/wmf/WMFVideoMFTManager.cpp
@@ -79,22 +79,22 @@ WMFVideoMFTManager::WMFVideoMFTManager(
   // mVideoStride, mVideoWidth, mVideoHeight, mUseHwAccel are initialized in
   // Init().
 {
   NS_ASSERTION(!NS_IsMainThread(), "Should not be on main thread.");
   MOZ_ASSERT(mImageContainer);
   MOZ_COUNT_CTOR(WMFVideoMFTManager);
 
   // Need additional checks/params to check vp8/vp9
-  if (!strcmp(aConfig.mime_type, "video/mp4") ||
-      !strcmp(aConfig.mime_type, "video/avc")) {
+  if (aConfig.mime_type.EqualsLiteral("video/mp4") ||
+      aConfig.mime_type.EqualsLiteral("video/avc")) {
     mStreamType = H264;
-  } else if (!strcmp(aConfig.mime_type, "video/webm; codecs=vp8")) {
+  } else if (aConfig.mime_type.EqualsLiteral("video/webm; codecs=vp8")) {
     mStreamType = VP8;
-  } else if (!strcmp(aConfig.mime_type, "video/webm; codecs=vp9")) {
+  } else if (aConfig.mime_type.EqualsLiteral("video/webm; codecs=vp9")) {
     mStreamType = VP9;
   } else {
     mStreamType = Unknown;
   }
 }
 
 WMFVideoMFTManager::~WMFVideoMFTManager()
 {
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -66,17 +66,19 @@ MediaSourceReader::MediaSourceReader(Med
 
 void
 MediaSourceReader::PrepareInitialization()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MSE_DEBUG("trackBuffers=%u", mTrackBuffers.Length());
   mEssentialTrackBuffers.AppendElements(mTrackBuffers);
   mHasEssentialTrackBuffers = true;
-  mDecoder->NotifyWaitingForResourcesStatusChanged();
+  if (!IsWaitingMediaResources()) {
+    mDecoder->NotifyWaitingForResourcesStatusChanged();
+  }
 }
 
 bool
 MediaSourceReader::IsWaitingMediaResources()
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   for (uint32_t i = 0; i < mEssentialTrackBuffers.Length(); ++i) {
@@ -756,17 +758,20 @@ MediaSourceReader::OnTrackBufferConfigur
   if (aInfo.HasAudio() && !mAudioTrack) {
     MSE_DEBUG("%p audio", aTrackBuffer);
     mAudioTrack = aTrackBuffer;
   }
   if (aInfo.HasVideo() && !mVideoTrack) {
     MSE_DEBUG("%p video", aTrackBuffer);
     mVideoTrack = aTrackBuffer;
   }
-  mDecoder->NotifyWaitingForResourcesStatusChanged();
+
+  if (!IsWaitingMediaResources()) {
+    mDecoder->NotifyWaitingForResourcesStatusChanged();
+  }
 }
 
 bool
 MediaSourceReader::TrackBuffersContainTime(int64_t aTime)
 {
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   if (mAudioTrack && !mAudioTrack->ContainsTime(aTime, EOS_FUZZ_US)) {
     return false;
--- a/dom/media/webm/IntelWebMVideoDecoder.cpp
+++ b/dom/media/webm/IntelWebMVideoDecoder.cpp
@@ -107,20 +107,20 @@ IntelWebMVideoDecoder::Create(WebMReader
 
   decoder->mTaskQueue = aReader->GetVideoTaskQueue();
   NS_ENSURE_TRUE(decoder->mTaskQueue, nullptr);
 
   return decoder.forget();
 }
 
 bool
-IntelWebMVideoDecoder::IsSupportedVideoMimeType(const char* aMimeType)
+IntelWebMVideoDecoder::IsSupportedVideoMimeType(const nsACString& aMimeType)
 {
-  return (!strcmp(aMimeType, "video/webm; codecs=vp8") ||
-          !strcmp(aMimeType, "video/webm; codecs=vp9")) &&
+  return (aMimeType.EqualsLiteral("video/webm; codecs=vp8") ||
+          aMimeType.EqualsLiteral("video/webm; codecs=vp9")) &&
          mPlatform->SupportsVideoMimeType(aMimeType);
 }
 
 nsresult
 IntelWebMVideoDecoder::Init(unsigned int aWidth, unsigned int aHeight)
 {
   mPlatform = PlatformDecoderModule::Create();
   if (!mPlatform) {
--- a/dom/media/webm/IntelWebMVideoDecoder.h
+++ b/dom/media/webm/IntelWebMVideoDecoder.h
@@ -49,17 +49,17 @@ private:
   void InitLayersBackendType();
 
   bool Decode();
 
   bool Demux(nsAutoPtr<VP8Sample>& aSample, bool* aEOS);
 
   bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
 
-  bool IsSupportedVideoMimeType(const char* aMimeType);
+  bool IsSupportedVideoMimeType(const nsACString& aMimeType);
 
   VP8Sample* PopSample();
 
   nsRefPtr<WebMReader> mReader;
   nsRefPtr<PlatformDecoderModule> mPlatform;
   nsRefPtr<MediaDataDecoder> mMediaDataDecoder;
 
   // TaskQueue on which decoder can choose to decode.
--- a/dom/security/nsCORSListenerProxy.cpp
+++ b/dom/security/nsCORSListenerProxy.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Assertions.h"
 #include "mozilla/LinkedList.h"
 
 #include "nsCORSListenerProxy.h"
 #include "nsIChannel.h"
 #include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
 #include "nsError.h"
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsNetUtil.h"
 #include "nsMimeTypes.h"
 #include "nsIStreamConverterService.h"
 #include "nsStringStream.h"
 #include "nsGkAtoms.h"
@@ -814,16 +815,32 @@ nsCORSListenerProxy::UpdateChannel(nsICh
     bool dataScheme = false;
     rv = uri->SchemeIs("data", &dataScheme);
     NS_ENSURE_SUCCESS(rv, rv);
     if (dataScheme) {
       return NS_OK;
     }
   }
 
+  // Set CORS attributes on channel so that intercepted requests get correct
+  // values. We have to do this here because the CheckMayLoad checks may lead
+  // to early return. We can't be sure this is an http channel though, so we
+  // can't return early on failure.
+  nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aChannel);
+  if (internal) {
+    if (mIsPreflight) {
+      rv = internal->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT);
+    } else {
+      rv = internal->SetCorsMode(nsIHttpChannelInternal::CORS_MODE_CORS);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = internal->SetCorsIncludeCredentials(mWithCredentials);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   // Check that the uri is ok to load
   rv = nsContentUtils::GetSecurityManager()->
     CheckLoadURIWithPrincipal(mRequestingPrincipal, uri,
                               nsIScriptSecurityManager::STANDARD);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (originalURI != uri) {
     rv = nsContentUtils::GetSecurityManager()->
--- a/dom/tests/mochitest/fetch/test_request.js
+++ b/dom/tests/mochitest/fetch/test_request.js
@@ -2,48 +2,52 @@ function testDefaultCtor() {
   var req = new Request("");
   is(req.method, "GET", "Default Request method is GET");
   ok(req.headers instanceof Headers, "Request should have non-null Headers object");
   is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
   is(req.context, "fetch", "Default context is fetch.");
   is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client.");
   is(req.mode, "cors", "Request mode for string input is cors");
   is(req.credentials, "omit", "Default Request credentials is omit");
+  is(req.cache, "default", "Default Request cache is default");
 
   var req = new Request(req);
   is(req.method, "GET", "Default Request method is GET");
   ok(req.headers instanceof Headers, "Request should have non-null Headers object");
   is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
   is(req.context, "fetch", "Default context is fetch.");
   is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client.");
   is(req.mode, "cors", "Request mode string input is cors");
   is(req.credentials, "omit", "Default Request credentials is omit");
+  is(req.cache, "default", "Default Request cache is default");
 }
 
 function testClone() {
   var orig = new Request("./cloned_request.txt", {
               method: 'POST',
               headers: { "Content-Length": 5 },
               body: "Sample body",
               mode: "same-origin",
               credentials: "same-origin",
+              cache: "no-store",
             });
   var clone = orig.clone();
   ok(clone.method === "POST", "Request method is POST");
   ok(clone.headers instanceof Headers, "Request should have non-null Headers object");
 
   is(clone.headers.get('content-length'), "5", "Response content-length should be 5.");
   orig.headers.set('content-length', 6);
   is(clone.headers.get('content-length'), "5", "Request content-length should be 5.");
 
   ok(clone.url === (new URL("./cloned_request.txt", self.location.href)).href,
        "URL should be resolved with entry settings object's API base URL");
   ok(clone.referrer === "about:client", "Default referrer is `client` which serializes to about:client.");
   ok(clone.mode === "same-origin", "Request mode is same-origin");
   ok(clone.credentials === "same-origin", "Default credentials is same-origin");
+  ok(clone.cache === "no-store", "Default cache is no-store");
 
   ok(!orig.bodyUsed, "Original body is not consumed.");
   ok(!clone.bodyUsed, "Clone body is not consumed.");
 
   var origBody = null;
   var clone2 = null;
   return orig.text().then(function (body) {
     origBody = body;
--- a/dom/tests/mochitest/fetch/test_response.js
+++ b/dom/tests/mochitest/fetch/test_response.js
@@ -116,35 +116,16 @@ function testOk() {
 
   var r3 = new Response("", { status: 299});
   ok(r3.ok, "Response with status 299 should have ok true");
 
   var r4 = new Response("", { status: 302});
   ok(!r4.ok, "Response with status 302 should have ok false");
 }
 
-// It is not possible to test setting finalURL until we have ServiceWorker
-// interception. This is because synthetic Responses do not have a url, the url
-// is set based on the request, so a SW could initiate a fetch() on behalf of
-// a client and set the resulting Response's finalURL before returning it to
-// the client, in which case the "set response's url to request's url" from the
-// client's point of view would not happen. A test for this will be added by
-// Bug 1134352.
-function testFinalURL() {
-  var r1 = new Response();
-  ok(!r1.finalURL, "Response.finalURL is false by default.");
-
-  try {
-    r1.finalURL = true;
-    ok(false, "Setting Response.finalURL of Response with null url should fail.");
-  } catch(e) {
-    ok(true, "Setting Response.finalURL of Response with null url should fail.");
-  }
-}
-
 function testBodyUsed() {
   var res = new Response("Sample body");
   ok(!res.bodyUsed, "bodyUsed is initially false.");
   return res.text().then((v) => {
     is(v, "Sample body", "Body should match");
     ok(res.bodyUsed, "After reading body, bodyUsed should be true.");
   }).then(() => {
     return res.blob().then((v) => {
@@ -221,17 +202,16 @@ function testBodyExtraction() {
   })
 }
 
 function runTest() {
   testDefaultCtor();
   testError();
   testRedirect();
   testOk();
-  testFinalURL();
 
   return Promise.resolve()
     .then(testBodyCreation)
     .then(testBodyUsed)
     .then(testBodyExtraction)
     .then(testClone)
     // Put more promise based tests here.
 }
--- a/dom/webidl/Performance.webidl
+++ b/dom/webidl/Performance.webidl
@@ -47,16 +47,23 @@ partial interface Performance {
   [Pref="dom.enable_resource_timing"]
   void clearResourceTimings();
   [Pref="dom.enable_resource_timing"]
   void setResourceTimingBufferSize(unsigned long maxSize);
   [Pref="dom.enable_resource_timing"]
   attribute EventHandler onresourcetimingbufferfull;
 };
 
+// GC microbenchmarks, pref-guarded, not for general use (bug 1125412)
+[Exposed=Window]
+partial interface Performance {
+  [Pref="dom.enable_memory_stats"]
+  readonly attribute object mozMemory;
+};
+
 // http://www.w3.org/TR/user-timing/
 [Exposed=Window]
 partial interface Performance {
   [Pref="dom.enable_user_timing", Throws]
   void mark(DOMString markName);
   [Pref="dom.enable_user_timing"]
   void clearMarks(optional DOMString markName);
   [Pref="dom.enable_user_timing", Throws]
--- a/dom/webidl/Response.webidl
+++ b/dom/webidl/Response.webidl
@@ -12,18 +12,16 @@
 interface Response {
   [NewObject] static Response error();
   [Throws,
    NewObject] static Response redirect(USVString url, optional unsigned short status = 302);
 
   readonly attribute ResponseType type;
 
   readonly attribute USVString url;
-  [Throws]
-           attribute boolean finalURL;
   readonly attribute unsigned short status;
   readonly attribute boolean ok;
   readonly attribute ByteString statusText;
   [SameObject] readonly attribute Headers headers;
 
   [Throws,
    NewObject] Response clone();
 };
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ServiceWorkerEvents.h"
 #include "ServiceWorkerClient.h"
 
+#include "nsIHttpChannelInternal.h"
 #include "nsINetworkInterceptController.h"
 #include "nsIOutputStream.h"
 #include "nsContentUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStreamUtils.h"
 #include "nsNetCID.h"
 #include "nsSerializationHelper.h"
@@ -115,36 +116,39 @@ public:
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText());
 
     nsAutoTArray<InternalHeaders::Entry, 5> entries;
-    mInternalResponse->Headers()->GetEntries(entries);
+    mInternalResponse->UnfilteredHeaders()->GetEntries(entries);
     for (uint32_t i = 0; i < entries.Length(); ++i) {
        mChannel->SynthesizeHeader(entries[i].mName, entries[i].mValue);
     }
 
     rv = mChannel->FinishSynthesizedResponse();
     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to finish synthesized response");
     return rv;
   }
 };
 
 class RespondWithHandler final : public PromiseNativeHandler
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
+  RequestMode mRequestMode;
 public:
   RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
-                     nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
+                     nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
+                     RequestMode aRequestMode)
     : mInterceptedChannel(aChannel)
     , mServiceWorker(aServiceWorker)
+    , mRequestMode(aRequestMode)
   {
   }
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void CancelRequest();
@@ -208,18 +212,20 @@ RespondWithHandler::ResolvedCallback(JSC
   }
 
   nsRefPtr<Response> response;
   nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
   if (NS_FAILED(rv)) {
     return;
   }
 
-  // FIXME(nsm) Bug 1136200 deal with opaque and no-cors (fetch spec 4.2.2.2).
-  if (response->Type() == ResponseType::Error) {
+  // Section 4.2, step 2.2 "If either response's type is "opaque" and request's
+  // mode is not "no-cors" or response's type is error, return a network error."
+  if (((response->Type() == ResponseType::Opaque) && (mRequestMode != RequestMode::No_cors)) ||
+      response->Type() == ResponseType::Error) {
     return;
   }
 
   if (NS_WARN_IF(response->BodyUsed())) {
     return;
   }
 
   nsRefPtr<InternalResponse> ir = response->GetInternalResponse();
@@ -279,17 +285,18 @@ void
 FetchEvent::RespondWith(Promise& aPromise, ErrorResult& aRv)
 {
   if (mWaitToRespond) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   mWaitToRespond = true;
-  nsRefPtr<RespondWithHandler> handler = new RespondWithHandler(mChannel, mServiceWorker);
+  nsRefPtr<RespondWithHandler> handler =
+    new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode());
   aPromise.AppendNativeHandler(handler);
 }
 
 void
 FetchEvent::RespondWith(Response& aResponse, ErrorResult& aRv)
 {
   if (mWaitToRespond) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -55,16 +55,25 @@
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 BEGIN_WORKERS_NAMESPACE
 
+static_assert(nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN == static_cast<uint32_t>(RequestMode::Same_origin),
+              "RequestMode enumeration value should match Necko CORS mode value.");
+static_assert(nsIHttpChannelInternal::CORS_MODE_NO_CORS == static_cast<uint32_t>(RequestMode::No_cors),
+              "RequestMode enumeration value should match Necko CORS mode value.");
+static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(RequestMode::Cors),
+              "RequestMode enumeration value should match Necko CORS mode value.");
+static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight),
+              "RequestMode enumeration value should match Necko CORS mode value.");
+
 struct ServiceWorkerManager::PendingOperation
 {
   nsCOMPtr<nsIRunnable> mRunnable;
 
   ServiceWorkerJobQueue* mQueue;
   nsRefPtr<ServiceWorkerJob> mJob;
 
   ServiceWorkerRegistrationData mRegistration;
@@ -2126,25 +2135,33 @@ class FetchEventRunnable : public Worker
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
   nsTArray<nsCString> mHeaderNames;
   nsTArray<nsCString> mHeaderValues;
   nsAutoPtr<ServiceWorkerClientInfo> mClientInfo;
   nsCString mSpec;
   nsCString mMethod;
   bool mIsReload;
+  RequestMode mRequestMode;
+  RequestCredentials mRequestCredentials;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
-                     nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo)
+                     nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo,
+                     bool aIsReload)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mInterceptedChannel(aChannel)
     , mServiceWorker(aServiceWorker)
     , mClientInfo(aClientInfo)
+    , mIsReload(aIsReload)
+    , mRequestMode(RequestMode::No_cors)
+    // By default we set it to same-origin since normal HTTP fetches always
+    // send credentials to same-origin websites unless explicitly forbidden.
+    , mRequestCredentials(RequestCredentials::Same_origin)
   {
     MOZ_ASSERT(aWorkerPrivate);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_IMETHOD
   VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
@@ -2152,16 +2169,17 @@ public:
     mHeaderNames.AppendElement(aHeader);
     mHeaderValues.AppendElement(aValue);
     return NS_OK;
   }
 
   nsresult
   Init()
   {
+    AssertIsOnMainThread();
     nsCOMPtr<nsIChannel> channel;
     nsresult rv = mInterceptedChannel->GetChannel(getter_AddRefs(channel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIURI> uri;
     rv = channel->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2173,18 +2191,45 @@ public:
 
     rv = httpChannel->GetRequestMethod(mMethod);
     NS_ENSURE_SUCCESS(rv, rv);
 
     uint32_t loadFlags;
     rv = channel->GetLoadFlags(&loadFlags);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    //TODO(jdm): we should probably include reload-ness in the loadinfo or as a separate load flag
-    mIsReload = false;
+    nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
+    NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
+
+    uint32_t mode;
+    internalChannel->GetCorsMode(&mode);
+    switch (mode) {
+      case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
+        mRequestMode = RequestMode::Same_origin;
+        break;
+      case nsIHttpChannelInternal::CORS_MODE_NO_CORS:
+        mRequestMode = RequestMode::No_cors;
+        break;
+      case nsIHttpChannelInternal::CORS_MODE_CORS:
+      case nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT:
+        mRequestMode = RequestMode::Cors;
+        break;
+      default:
+        MOZ_CRASH("Unexpected CORS mode");
+    }
+
+    if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
+      mRequestCredentials = RequestCredentials::Omit;
+    } else {
+      bool includeCrossOrigin;
+      internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin);
+      if (includeCrossOrigin) {
+        mRequestCredentials = RequestCredentials::Include;
+      }
+    }
 
     rv = httpChannel->VisitRequestHeaders(this);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
   bool
@@ -2239,17 +2284,19 @@ private:
       }
     }
 
     nsRefPtr<Headers> headers = new Headers(globalObj.GetAsSupports(), internalHeaders);
     reqInit.mHeaders.Construct();
     reqInit.mHeaders.Value().SetAsHeaders() = headers;
 
     //TODO(jdm): set request body
-    //TODO(jdm): set request same-origin mode and credentials
+
+    reqInit.mMode.Construct(mRequestMode);
+    reqInit.mCredentials.Construct(mRequestCredentials);
 
     ErrorResult rv;
     nsRefPtr<Request> request = Request::Constructor(globalObj, requestInfo, reqInit, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return false;
     }
 
     RootedDictionary<FetchEventInit> init(aCx);
@@ -2275,17 +2322,18 @@ private:
     }
     return true;
   }
 };
 
 NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
 
 NS_IMETHODIMP
-ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChannel* aChannel)
+ServiceWorkerManager::DispatchFetchEvent(nsIDocument* aDoc, nsIInterceptedChannel* aChannel,
+                                         bool aIsReload)
 {
   MOZ_ASSERT(aChannel);
   nsCOMPtr<nsISupports> serviceWorker;
 
   bool isNavigation = false;
   nsresult rv = aChannel->GetIsNavigation(&isNavigation);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -2325,17 +2373,17 @@ ServiceWorkerManager::DispatchFetchEvent
     new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false));
 
   nsRefPtr<ServiceWorker> sw = static_cast<ServiceWorker*>(serviceWorker.get());
   nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
     new nsMainThreadPtrHolder<ServiceWorker>(sw));
 
   // clientInfo is null if we don't have a controlled document
   nsRefPtr<FetchEventRunnable> event =
-    new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, clientInfo);
+    new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, clientInfo, aIsReload);
   rv = event->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   AutoJSAPI api;
   api.Init();
   if (NS_WARN_IF(!event->Dispatch(api.cx()))) {
     return NS_ERROR_FAILURE;
   }
--- a/dom/workers/test/serviceworkers/fetch/fetch_tests.js
+++ b/dom/workers/test/serviceworkers/fetch/fetch_tests.js
@@ -1,105 +1,169 @@
-function fetch(name, onload, onerror, headers) {
+function fetchXHR(name, onload, onerror, headers) {
   expectAsyncResult();
 
   onload = onload || function() {
     my_ok(false, "XHR load should not complete successfully");
     finish();
   };
   onerror = onerror || function() {
-    my_ok(false, "XHR load should be intercepted successfully");
+    my_ok(false, "XHR load for " + name + " should be intercepted successfully");
     finish();
   };
 
   var x = new XMLHttpRequest();
   x.open('GET', name, true);
   x.onload = function() { onload(x) };
   x.onerror = function() { onerror(x) };
   headers = headers || [];
   headers.forEach(function(header) {
     x.setRequestHeader(header[0], header[1]);
   });
   x.send();
 }
 
-fetch('synthesized.txt', function(xhr) {
+fetchXHR('synthesized.txt', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.responseText == "synthesized response body", "load should have synthesized response");
   finish();
 });
 
-fetch('test-respondwith-response.txt', function(xhr) {
+fetchXHR('test-respondwith-response.txt', function(xhr) {
   my_ok(xhr.status == 200, "test-respondwith-response load should be successful");
   my_ok(xhr.responseText == "test-respondwith-response response body", "load should have response");
   finish();
 });
 
-fetch('synthesized-404.txt', function(xhr) {
+fetchXHR('synthesized-404.txt', function(xhr) {
   my_ok(xhr.status == 404, "load should 404");
   my_ok(xhr.responseText == "synthesized response body", "404 load should have synthesized response");
   finish();
 });
 
-fetch('synthesized-headers.txt', function(xhr) {
+fetchXHR('synthesized-headers.txt', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.getResponseHeader("X-Custom-Greeting") === "Hello", "custom header should be set");
   my_ok(xhr.responseText == "synthesized response body", "custom header load should have synthesized response");
   finish();
 });
 
-fetch('ignored.txt', function(xhr) {
+fetchXHR('ignored.txt', function(xhr) {
   my_ok(xhr.status == 404, "load should be uninterrupted");
   finish();
 });
 
-fetch('rejected.txt', null, function(xhr) {
+fetchXHR('rejected.txt', null, function(xhr) {
   my_ok(xhr.status == 0, "load should not complete");
   finish();
 });
 
-fetch('nonresponse.txt', null, function(xhr) {
+fetchXHR('nonresponse.txt', null, function(xhr) {
   my_ok(xhr.status == 0, "load should not complete");
   finish();
 });
 
-fetch('nonresponse2.txt', null, function(xhr) {
+fetchXHR('nonresponse2.txt', null, function(xhr) {
   my_ok(xhr.status == 0, "load should not complete");
   finish();
 });
 
-fetch('headers.txt', function(xhr) {
+fetchXHR('headers.txt', function(xhr) {
   my_ok(xhr.status == 200, "load should be successful");
   my_ok(xhr.responseText == "1", "request header checks should have passed");
   finish();
 }, null, [["X-Test1", "header1"], ["X-Test2", "header2"]]);
 
 var expectedUncompressedResponse = "";
 for (var i = 0; i < 10; ++i) {
   expectedUncompressedResponse += "hello";
 }
 expectedUncompressedResponse += "\n";
 
 // ServiceWorker does not intercept, at which point the network request should
 // be correctly decoded.
-fetch('deliver-gzip.sjs', function(xhr) {
+fetchXHR('deliver-gzip.sjs', function(xhr) {
   my_ok(xhr.status == 200, "network gzip load should be successful");
   my_ok(xhr.responseText == expectedUncompressedResponse, "network gzip load should have synthesized response.");
   my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "network Content-Encoding should be gzip.");
   my_ok(xhr.getResponseHeader("Content-Length") == "35", "network Content-Length should be of original gzipped file.");
   finish();
 });
 
-fetch('hello.gz', function(xhr) {
+fetchXHR('hello.gz', function(xhr) {
+  my_ok(xhr.status == 200, "gzip load should be successful");
+  my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response.");
+  my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip.");
+  my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file.");
+  finish();
+});
+
+fetchXHR('hello-after-extracting.gz', function(xhr) {
   my_ok(xhr.status == 200, "gzip load should be successful");
   my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response.");
   my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip.");
   my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file.");
   finish();
 });
 
-fetch('hello-after-extracting.gz', function(xhr) {
-  my_ok(xhr.status == 200, "gzip load should be successful");
-  my_ok(xhr.responseText == expectedUncompressedResponse, "gzip load should have synthesized response.");
-  my_ok(xhr.getResponseHeader("Content-Encoding") == "gzip", "Content-Encoding should be gzip.");
-  my_ok(xhr.getResponseHeader("Content-Length") == "35", "Content-Length should be of original gzipped file.");
+fetchXHR('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*', function(xhr) {
+  my_ok(xhr.status == 200, "cross origin load with correct headers should be successful");
+  my_ok(xhr.getResponseHeader("access-control-allow-origin") == null, "cors headers should be filtered out");
+  finish();
+});
+
+expectAsyncResult();
+fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*')
+.then(function(res) {
+  my_ok(res.ok, "Valid CORS request should receive valid response");
+  my_ok(res.type == "cors", "Response type should be CORS");
+  res.text().then(function(body) {
+    my_ok(body === "<res>hello pass</res>\n", "cors response body should match");
+    finish();
+  });
+}, function(e) {
+  my_ok(false, "CORS Fetch failed");
   finish();
 });
+
+expectAsyncResult();
+fetch('http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200', { mode: 'no-cors' })
+.then(function(res) {
+  my_ok(res.type == "opaque", "Response type should be opaque");
+  my_ok(res.status == 0, "Status should be 0");
+  res.text().then(function(body) {
+    my_ok(body === "", "opaque response body should be empty");
+    finish();
+  });
+}, function(e) {
+  my_ok(false, "no-cors Fetch failed");
+  finish();
+});
+
+expectAsyncResult();
+fetch('opaque-on-same-origin')
+.then(function(res) {
+  my_ok(false, "intercepted opaque response for non no-cors request should fail.");
+  finish();
+}, function(e) {
+  my_ok(true, "intercepted opaque response for non no-cors request should fail.");
+  finish();
+});
+
+expectAsyncResult();
+fetch('http://example.com/opaque-no-cors', { mode: "no-cors" })
+.then(function(res) {
+  my_ok(res.type == "opaque", "intercepted opaque response for no-cors request should have type opaque.");
+  finish();
+}, function(e) {
+  my_ok(false, "intercepted opaque response for no-cors request should pass.");
+  finish();
+});
+
+expectAsyncResult();
+fetch('http://example.com/cors-for-no-cors', { mode: "no-cors" })
+.then(function(res) {
+  my_ok(res.type == "opaque", "intercepted non-opaque response for no-cors request should resolve to opaque response.");
+  finish();
+}, function(e) {
+  my_ok(false, "intercepted non-opaque response for no-cors request should resolve to opaque response. It should not fail.");
+  finish();
+});
--- a/dom/workers/test/serviceworkers/fetch/index.html
+++ b/dom/workers/test/serviceworkers/fetch/index.html
@@ -19,17 +19,17 @@
     window.opener.postMessage({status: "ok", result: result, message: msg}, "*");
   }
 
   function check_intercepted_script() {
     document.getElementById('intercepted-script').test_result =
       document.currentScript == document.getElementById('intercepted-script');
   }
 
-  function fetch(name, onload, onerror, headers) {
+  function fetchXHR(name, onload, onerror, headers) {
     gExpected++;
 
     onload = onload || function() {
       my_ok(false, "load should not complete successfully");
       finish();
     };
     onerror = onerror || function() {
       my_ok(false, "load should be intercepted successfully");
@@ -130,17 +130,17 @@
   gExpected++;
   var worker = new Worker('fetch_worker_script.js');
   worker.onmessage = function(e) {
     if (e.data == "finish") {
       finish();
     } else if (e.data == "expect") {
       gExpected++;
     } else if (e.data.type == "ok") {
-      my_ok(e.data.value, e.data.msg);
+      my_ok(e.data.value, "Fetch test on worker: " + e.data.msg);
     }
   };
   worker.onerror = function() {
     my_ok(false, "worker should not cause any errors");
   };
 </script>
 </pre>
 </body>
--- a/dom/workers/test/serviceworkers/fetch_event_worker.js
+++ b/dom/workers/test/serviceworkers/fetch_event_worker.js
@@ -1,8 +1,10 @@
+var seenIndex = false;
+
 onfetch = function(ev) {
   if (ev.request.url.contains("synthesized.txt")) {
     ev.respondWith(Promise.resolve(
       new Response("synthesized response body", {})
     ));
   }
 
   else if (ev.request.url.contains("synthesized-404.txt")) {
@@ -110,9 +112,52 @@ onfetch = function(ev) {
 
   else if (ev.request.url.contains("hello-after-extracting.gz")) {
     ev.respondWith(fetch("fetch/deliver-gzip.sjs").then(function(res) {
       return res.text().then(function(body) {
         return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
       });
     }));
   }
+
+  else if (ev.request.url.contains('opaque-on-same-origin')) {
+    var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200';
+    ev.respondWith(fetch(url, { mode: 'no-cors' }));
+  }
+
+  else if (ev.request.url.contains('opaque-no-cors')) {
+    if (ev.request.mode != "no-cors") {
+      ev.respondWith(Promise.reject());
+      return;
+    }
+
+    var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200';
+    ev.respondWith(fetch(url, { mode: ev.request.mode }));
+  }
+
+  else if (ev.request.url.contains('cors-for-no-cors')) {
+    if (ev.request.mode != "no-cors") {
+      ev.respondWith(Promise.reject());
+      return;
+    }
+
+    var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*';
+    ev.respondWith(fetch(url));
+  }
+
+  else if (ev.request.url.contains('example.com')) {
+    ev.respondWith(fetch(ev.request));
+  }
+
+  else if (ev.request.url.contains("index.html")) {
+    if (seenIndex) {
+        var body = "<script>" +
+                     "opener.postMessage({status: 'ok', result: " + ev.isReload + "," +
+                                         "message: 'reload status should be indicated'}, '*');" +
+                     "opener.postMessage({status: 'done'}, '*');" +
+                   "</script>";
+        ev.respondWith(new Response(body, {headers: {'Content-Type': 'text/html'}}));
+    } else {
+      seenIndex = true;
+      ev.respondWith(fetch(ev.request.url));
+    }
+  }
 }
--- a/dom/workers/test/serviceworkers/test_fetch_event.html
+++ b/dom/workers/test/serviceworkers/test_fetch_event.html
@@ -22,23 +22,29 @@
       return new Promise(function(resolve) {
         swr.installing.onstatechange = resolve;
       });
     });
   }
 
   function testController() {
     var p = new Promise(function(resolve, reject) {
+      var reloaded = false;
       window.onmessage = function(e) {
         if (e.data.status == "ok") {
           ok(e.data.result, e.data.message);
         } else if (e.data.status == "done") {
-          window.onmessage = null;
-          w.close();
-          resolve();
+          if (reloaded) {
+            window.onmessage = null;
+            w.close();
+            resolve();
+          } else {
+            w.location.reload();
+            reloaded = true;
+          }
         }
       }
     });
 
     var w = window.open("fetch/index.html");
     return p;
   }
 
--- a/dom/workers/test/serviceworkers/test_https_fetch.html
+++ b/dom/workers/test/serviceworkers/test_https_fetch.html
@@ -41,16 +41,15 @@
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
-      ["dom.fetch.enabled", true],
       ["dom.caches.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html
+++ b/dom/workers/test/serviceworkers/test_https_fetch_cloned_response.html
@@ -41,16 +41,15 @@
     };
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
-      ["dom.fetch.enabled", true],
       ["dom.caches.enabled", true]
     ]}, runTest);
   };
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -114,16 +114,17 @@ static const char *sExtensionNames[] = {
     "GL_EXT_framebuffer_object",
     "GL_EXT_framebuffer_sRGB",
     "GL_EXT_gpu_shader4",
     "GL_EXT_occlusion_query_boolean",
     "GL_EXT_packed_depth_stencil",
     "GL_EXT_read_format_bgra",
     "GL_EXT_robustness",
     "GL_EXT_sRGB",
+    "GL_EXT_sRGB_write_control",
     "GL_EXT_shader_texture_lod",
     "GL_EXT_texture3D",
     "GL_EXT_texture_compression_dxt1",
     "GL_EXT_texture_compression_s3tc",
     "GL_EXT_texture_filter_anisotropic",
     "GL_EXT_texture_format_BGRA8888",
     "GL_EXT_texture_sRGB",
     "GL_EXT_texture_storage",
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -114,17 +114,18 @@ enum class GLFeature {
     occlusion_query_boolean,
     occlusion_query2,
     packed_depth_stencil,
     query_objects,
     read_buffer,
     renderbuffer_color_float,
     renderbuffer_color_half_float,
     robustness,
-    sRGB,
+    sRGB_framebuffer,
+    sRGB_texture,
     sampler_objects,
     standard_derivatives,
     texture_3D,
     texture_3D_compressed,
     texture_3D_copy,
     texture_float,
     texture_float_linear,
     texture_half_float,
@@ -410,16 +411,17 @@ public:
         EXT_framebuffer_object,
         EXT_framebuffer_sRGB,
         EXT_gpu_shader4,
         EXT_occlusion_query_boolean,
         EXT_packed_depth_stencil,
         EXT_read_format_bgra,
         EXT_robustness,
         EXT_sRGB,
+        EXT_sRGB_write_control,
         EXT_shader_texture_lod,
         EXT_texture3D,
         EXT_texture_compression_dxt1,
         EXT_texture_compression_s3tc,
         EXT_texture_filter_anisotropic,
         EXT_texture_format_BGRA8888,
         EXT_texture_sRGB,
         EXT_texture_storage,
@@ -1032,18 +1034,26 @@ public:
             // See bug 737182 and the comment in IsTextureSizeSafeToPassToDriver.
             level = -1;
             width = -1;
             height = -1;
             border = -1;
         }
 
         BeforeGLReadCall();
-        raw_fCopyTexImage2D(target, level, internalformat,
-                            x, y, width, height, border);
+        bool didCopyTexImage2D = false;
+        if (mScreen) {
+            didCopyTexImage2D = mScreen->CopyTexImage2D(target, level, internalformat, x,
+                                                        y, width, height, border);
+        }
+
+        if (!didCopyTexImage2D) {
+            raw_fCopyTexImage2D(target, level, internalformat, x, y, width, height,
+                                border);
+        }
         AfterGLReadCall();
     }
 
     void fCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) {
         BeforeGLReadCall();
         raw_fCopyTexSubImage2D(target, level, xoffset, yoffset,
                                x, y, width, height);
         AfterGLReadCall();
@@ -1927,16 +1937,19 @@ public:
 
     void fCompileShader(GLuint shader) {
         BEFORE_GL_CALL;
         mSymbols.fCompileShader(shader);
         AFTER_GL_CALL;
     }
 
 private:
+
+    friend class SharedSurface_IOSurface;
+
     void raw_fCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border)
     {
         BEFORE_GL_CALL;
         mSymbols.fCopyTexImage2D(target, level, internalformat, x, y, width, height, border);
         AFTER_GL_CALL;
     }
 
     void raw_fCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height)
--- a/gfx/gl/GLContextFeatures.cpp
+++ b/gfx/gl/GLContextFeatures.cpp
@@ -1,14 +1,13 @@
 /* -*- Mode: C++; tab-width: 20; 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/. */
 
-
 #include "GLContext.h"
 #include "nsPrintfCString.h"
 
 #ifdef XP_MACOSX
 #include "nsCocoaFeatures.h"
 #endif
 
 namespace mozilla {
@@ -446,22 +445,34 @@ static const FeatureInfo sFeatureInfoArr
         GLContext::Extension_None,
         {
             GLContext::ARB_robustness,
             GLContext::EXT_robustness,
             GLContext::Extensions_End
         }
     },
     {
-        "sRGB",
+        "sRGB_framebuffer",
         GLVersion::GL3,
         GLESVersion::ES3,
+        GLContext::ARB_framebuffer_sRGB,
+        {
+            GLContext::EXT_framebuffer_sRGB,
+            GLContext::EXT_sRGB_write_control,
+            GLContext::Extensions_End
+        }
+    },
+    {
+        "sRGB_texture",
+        GLVersion::GL2_1,
+        GLESVersion::ES3,
         GLContext::Extension_None,
         {
             GLContext::EXT_sRGB,
+            GLContext::EXT_texture_sRGB,
             GLContext::Extensions_End
         }
     },
     {
         "sampler_objects",
         GLVersion::GL3_3,
         GLESVersion::ES3,
         GLContext::ARB_sampler_objects,
@@ -648,21 +659,20 @@ GetFeatureInfo(GLFeature feature)
 static inline uint32_t
 ProfileVersionForFeature(GLFeature feature, ContextProfile profile)
 {
     MOZ_ASSERT(profile != ContextProfile::Unknown,
                "GLContext::ProfileVersionForFeature : unknown <profile>");
 
     const FeatureInfo& featureInfo = GetFeatureInfo(feature);
 
-    if (profile == ContextProfile::OpenGLES) {
-        return (uint32_t) featureInfo.mOpenGLESVersion;
-    }
+    if (profile == ContextProfile::OpenGLES)
+        return (uint32_t)featureInfo.mOpenGLESVersion;
 
-    return (uint32_t) featureInfo.mOpenGLVersion;
+    return (uint32_t)featureInfo.mOpenGLVersion;
 }
 
 bool
 IsFeaturePartOfProfileVersion(GLFeature feature,
                               ContextProfile profile, unsigned int version)
 {
     unsigned int profileVersion = ProfileVersionForFeature(feature, profile);
 
@@ -686,101 +696,77 @@ GLContext::IsFeatureProvidedByCoreSymbol
 }
 
 const char*
 GLContext::GetFeatureName(GLFeature feature)
 {
     return GetFeatureInfo(feature).mName;
 }
 
-static bool
-CanReadSRGBFromFBOTexture(GLContext* gl)
-{
-    if (!gl->WorkAroundDriverBugs())
-        return true;
-
-#ifdef XP_MACOSX
-    // Bug 843668:
-    // MacOSX 10.6 reports to support EXT_framebuffer_sRGB and
-    // EXT_texture_sRGB but fails to convert from sRGB to linear
-    // when writing to an sRGB texture attached to an FBO.
-    if (!nsCocoaFeatures::OnLionOrLater()) {
-        return false;
-    }
-#endif // XP_MACOSX
-    return true;
-}
-
 void
 GLContext::InitFeatures()
 {
-    for (size_t feature_index = 0; feature_index < size_t(GLFeature::EnumMax); feature_index++)
-    {
-        GLFeature feature = GLFeature(feature_index);
+    for (size_t featureId = 0; featureId < size_t(GLFeature::EnumMax); featureId++) {
+        GLFeature feature = GLFeature(featureId);
 
         if (IsFeaturePartOfProfileVersion(feature, mProfile, mVersion)) {
-            mAvailableFeatures[feature_index] = true;
+            mAvailableFeatures[featureId] = true;
             continue;
         }
 
-        mAvailableFeatures[feature_index] = false;
+        mAvailableFeatures[featureId] = false;
 
         const FeatureInfo& featureInfo = GetFeatureInfo(feature);
 
-        if (IsExtensionSupported(featureInfo.mARBExtensionWithoutARBSuffix))
-        {
-            mAvailableFeatures[feature_index] = true;
+        if (IsExtensionSupported(featureInfo.mARBExtensionWithoutARBSuffix)) {
+            mAvailableFeatures[featureId] = true;
             continue;
         }
 
-        for (size_t j = 0; true; j++)
-        {
-            MOZ_ASSERT(j < kMAX_EXTENSION_GROUP_SIZE, "kMAX_EXTENSION_GROUP_SIZE too small");
+        for (size_t j = 0; true; j++) {
+            MOZ_ASSERT(j < kMAX_EXTENSION_GROUP_SIZE,
+                       "kMAX_EXTENSION_GROUP_SIZE too small");
 
-            if (featureInfo.mExtensions[j] == GLContext::Extensions_End) {
+            if (featureInfo.mExtensions[j] == GLContext::Extensions_End)
                 break;
-            }
 
             if (IsExtensionSupported(featureInfo.mExtensions[j])) {
-                mAvailableFeatures[feature_index] = true;
+                mAvailableFeatures[featureId] = true;
                 break;
             }
         }
     }
 
-    // Bug 843668: Work around limitation of the feature system.
-    // For sRGB support under OpenGL to match OpenGL ES spec, check for both
-    // EXT_texture_sRGB and EXT_framebuffer_sRGB is required.
-    const bool aresRGBExtensionsAvailable =
-        IsExtensionSupported(EXT_texture_sRGB) &&
-        (IsExtensionSupported(ARB_framebuffer_sRGB) ||
-         IsExtensionSupported(EXT_framebuffer_sRGB));
-
-    mAvailableFeatures[size_t(GLFeature::sRGB)] =
-        aresRGBExtensionsAvailable &&
-        CanReadSRGBFromFBOTexture(this);
+    if (WorkAroundDriverBugs()) {
+#ifdef XP_MACOSX
+        // MacOSX 10.6 reports to support EXT_framebuffer_sRGB and EXT_texture_sRGB but
+        // fails to convert from sRGB to linear when reading from an sRGB texture attached
+        // to an FBO. (bug 843668)
+        if (!nsCocoaFeatures::OnLionOrLater())
+            MarkUnsupported(GLFeature::sRGB_framebuffer);
+#endif // XP_MACOSX
+    }
 }
 
 void
 GLContext::MarkUnsupported(GLFeature feature)
 {
     mAvailableFeatures[size_t(feature)] = false;
 
     const FeatureInfo& featureInfo = GetFeatureInfo(feature);
 
-    for (size_t i = 0; true; i++)
-    {
+    for (size_t i = 0; true; i++) {
         MOZ_ASSERT(i < kMAX_EXTENSION_GROUP_SIZE, "kMAX_EXTENSION_GROUP_SIZE too small");
 
-        if (featureInfo.mExtensions[i] == GLContext::Extensions_End) {
+        if (featureInfo.mExtensions[i] == GLContext::Extensions_End)
             break;
-        }
 
         MarkExtensionUnsupported(featureInfo.mExtensions[i]);
     }
 
     MOZ_ASSERT(!IsSupported(feature), "GLContext::MarkUnsupported has failed!");
 
-    NS_WARNING(nsPrintfCString("%s marked as unsupported", GetFeatureName(feature)).get());
+    NS_WARNING(nsPrintfCString("%s marked as unsupported",
+                               GetFeatureName(feature)).get());
 }
 
 } /* namespace gl */
 } /* namespace mozilla */
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -309,16 +309,33 @@ GLScreenBuffer::BeforeReadCall()
 {
     if (mUserReadFB != 0)
         return;
 
     AssureBlitted();
 }
 
 bool
+GLScreenBuffer::CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x,
+                               GLint y, GLsizei width, GLsizei height, GLint border)
+{
+    SharedSurface* surf;
+    if (GetReadFB() == 0) {
+        surf = SharedSurf();
+    } else {
+        surf = mGL->mFBOMapping[GetReadFB()];
+    }
+    if (surf) {
+        return surf->CopyTexImage2D(target, level, internalformat,  x, y, width, height, border);
+    }
+
+    return false;
+}
+
+bool
 GLScreenBuffer::ReadPixels(GLint x, GLint y,
                            GLsizei width, GLsizei height,
                            GLenum format, GLenum type,
                            GLvoid* pixels)
 {
     // If the currently bound framebuffer is backed by a SharedSurface
     // then it might want to override how we read pixel data from it.
     // This is normally only the default framebuffer, but we can also
--- a/gfx/gl/GLScreenBuffer.h
+++ b/gfx/gl/GLScreenBuffer.h
@@ -223,16 +223,19 @@ public:
 
     void BindAsFramebuffer(GLContext* const gl, GLenum target) const;
 
     void RequireBlit();
     void AssureBlitted();
     void AfterDrawCall();
     void BeforeReadCall();
 
+    bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x,
+                        GLint y, GLsizei width, GLsizei height, GLint border);
+
     void SetReadBuffer(GLenum userMode);
 
     /**
      * Attempts to read pixels from the current bound framebuffer, if
      * it is backed by a SharedSurface.
      *
      * Returns true if the pixel data has been read back, false
      * otherwise.
--- a/gfx/gl/SharedSurface.h
+++ b/gfx/gl/SharedSurface.h
@@ -151,16 +151,22 @@ public:
         MOZ_CRASH("Did you forget to override this function?");
     }
 
     virtual GLuint ProdRenderbuffer() {
         MOZ_ASSERT(mAttachType == AttachmentType::GLRenderbuffer);
         MOZ_CRASH("Did you forget to override this function?");
     }
 
+    virtual bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x,
+                                GLint y, GLsizei width, GLsizei height, GLint border)
+    {
+        return false;
+    }
+
     virtual bool ReadPixels(GLint x, GLint y,
                             GLsizei width, GLsizei height,
                             GLenum format, GLenum type,
                             GLvoid* pixels)
     {
         return false;
     }
 
--- a/gfx/gl/SharedSurfaceIO.cpp
+++ b/gfx/gl/SharedSurfaceIO.cpp
@@ -31,16 +31,59 @@ SharedSurface_IOSurface::Create(const Re
 void
 SharedSurface_IOSurface::Fence()
 {
     mGL->MakeCurrent();
     mGL->fFlush();
 }
 
 bool
+SharedSurface_IOSurface::CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+                                        GLint x, GLint y, GLsizei width, GLsizei height,
+                                        GLint border)
+{
+    /* Bug 896693 - OpenGL framebuffers that are backed by IOSurface on OSX expose a bug
+     * in glCopyTexImage2D --- internalformats GL_ALPHA, GL_LUMINANCE, GL_LUMINANCE_ALPHA
+     * return the wrong results. To work around, copy framebuffer to a temporary texture
+     * using GL_RGBA (which works), attach as read framebuffer and glCopyTexImage2D
+     * instead.
+     */
+
+    // https://www.opengl.org/sdk/docs/man3/xhtml/glCopyTexImage2D.xml says that width or
+    // height set to 0 results in a NULL texture. Lets not do any work and punt to
+    // original glCopyTexImage2D, since the FBO below will fail when trying to attach a
+    // texture of 0 width or height.
+    if (width == 0 || height == 0)
+        return false;
+
+    MOZ_ASSERT(mGL->IsCurrent());
+
+    ScopedTexture destTex(mGL);
+    {
+        ScopedBindTexture bindTex(mGL, destTex.Texture());
+        mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
+                            LOCAL_GL_NEAREST);
+        mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
+                            LOCAL_GL_NEAREST);
+        mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+                            LOCAL_GL_CLAMP_TO_EDGE);
+        mGL->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+                            LOCAL_GL_CLAMP_TO_EDGE);
+        mGL->raw_fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, x, y, width,
+                                 height, 0);
+    }
+
+    ScopedFramebufferForTexture tmpFB(mGL, destTex.Texture(), LOCAL_GL_TEXTURE_2D);
+    ScopedBindFramebuffer bindFB(mGL, tmpFB.FB());
+    mGL->raw_fCopyTexImage2D(target, level, internalformat, x, y, width, height, border);
+
+    return true;
+}
+
+bool
 SharedSurface_IOSurface::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                                     GLenum format, GLenum type, GLvoid* pixels)
 {
     // Calling glReadPixels when an IOSurface is bound to the current framebuffer
     // can cause corruption in following glReadPixel calls (even if they aren't
     // reading from an IOSurface).
     // We workaround this by copying to a temporary texture, and doing the readback
     // from that.
--- a/gfx/gl/SharedSurfaceIO.h
+++ b/gfx/gl/SharedSurfaceIO.h
@@ -25,16 +25,19 @@ public:
 
     virtual void LockProdImpl() override { }
     virtual void UnlockProdImpl() override { }
 
     virtual void Fence() override;
     virtual bool WaitSync() override { return true; }
     virtual bool PollSync() override { return true; }
 
+    virtual bool CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+                                GLint x, GLint y, GLsizei width, GLsizei height,
+                                GLint border) override;
     virtual bool ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                             GLenum format, GLenum type, GLvoid *pixels) override;
 
     virtual GLuint ProdTexture() override {
         return mProdTex;
     }
 
     virtual GLenum ProdTextureTarget() const override {
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -367,18 +367,18 @@ static bool IsCloseToHorizontal(float aA
 static bool IsCloseToVertical(float aAngle, float aThreshold)
 {
   return (fabs(aAngle - (M_PI / 2)) < aThreshold);
 }
 
 template <typename Units>
 static bool IsZero(const gfx::PointTyped<Units>& aPoint)
 {
-  return FuzzyEqualsMultiplicative(aPoint.x, 0.0f)
-      && FuzzyEqualsMultiplicative(aPoint.y, 0.0f);
+  return FuzzyEqualsAdditive(aPoint.x, 0.0f)
+      && FuzzyEqualsAdditive(aPoint.y, 0.0f);
 }
 
 static inline void LogRendertraceRect(const ScrollableLayerGuid& aGuid, const char* aDesc, const char* aColor, const CSSRect& aRect)
 {
 #ifdef APZC_ENABLE_RENDERTRACE
   static const TimeStamp sRenderStart = TimeStamp::Now();
   TimeDuration delta = TimeStamp::Now() - sRenderStart;
   printf_stderr("(%llu,%lu,%llu)%s RENDERTRACE %f rect %s %f %f %f %f\n",
@@ -1282,32 +1282,32 @@ nsEventStatus AsyncPanZoomController::On
   mLastZoomFocus = aEvent.mLocalFocusPoint - mFrameMetrics.mCompositionBounds.TopLeft();
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale in state %d\n", this, mState);
 
+  if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
+    return nsEventStatus_eIgnore;
+  }
+
+  if (mState != PINCHING) {
+    return nsEventStatus_eConsumeNoDefault;
+  }
+
   // Only the root APZC is zoomable, and the root APZC is not allowed to have
   // different x and y scales. If it did, the calculations in this function
   // would have to be adjusted (as e.g. it would no longer be valid to take
   // the minimum or maximum of the ratios of the widths and heights of the
   // page rect and the composition bounds).
   MOZ_ASSERT(mFrameMetrics.IsRootScrollable());
   MOZ_ASSERT(mFrameMetrics.GetZoom().AreScalesSame());
 
-  if (HasReadyTouchBlock() && !CurrentTouchBlock()->TouchActionAllowsPinchZoom()) {
-    return nsEventStatus_eIgnore;
-  }
-
-  if (mState != PINCHING) {
-    return nsEventStatus_eConsumeNoDefault;
-  }
-
   float prevSpan = aEvent.mPreviousSpan;
   if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
     // We're still handling it; we've just decided to throw this event away.
     return nsEventStatus_eConsumeNoDefault;
   }
 
   float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
 
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -25,16 +25,22 @@
 #include "gfxPrefs.h"                   // for the preferences
 
 #define AXIS_LOG(...)
 // #define AXIS_LOG(...) printf_stderr("AXIS: " __VA_ARGS__)
 
 namespace mozilla {
 namespace layers {
 
+bool FuzzyEqualsCoordinate(float aValue1, float aValue2)
+{
+  return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON)
+      || FuzzyEqualsMultiplicative(aValue1, aValue2);
+}
+
 extern StaticAutoPtr<ComputedTimingFunction> gVelocityCurveFunction;
 
 Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
   : mPos(0),
     mPosTimeMs(0),
     mVelocity(0.0f),
     mAxisLocked(false),
     mAsyncPanZoomController(aAsyncPanZoomController),
@@ -175,28 +181,28 @@ ParentLayerCoord Axis::ApplyResistance(P
 }
 
 void Axis::OverscrollBy(ParentLayerCoord aOverscroll) {
   MOZ_ASSERT(CanScroll());
   StopSamplingOverscrollAnimation();
   aOverscroll = ApplyResistance(aOverscroll);
   if (aOverscroll > 0) {
 #ifdef DEBUG
-    if (!FuzzyEqualsAdditive(GetCompositionEnd().value, GetPageEnd().value, COORDINATE_EPSILON)) {
-      nsPrintfCString message("composition end (%f) is not within COORDINATE_EPISLON of page end (%f)\n",
+    if (!FuzzyEqualsCoordinate(GetCompositionEnd().value, GetPageEnd().value)) {
+      nsPrintfCString message("composition end (%f) is not equal (within error) to page end (%f)\n",
                               GetCompositionEnd().value, GetPageEnd().value);
       NS_ASSERTION(false, message.get());
       MOZ_CRASH();
     }
 #endif
     MOZ_ASSERT(mOverscroll >= 0);
   } else if (aOverscroll < 0) {
 #ifdef DEBUG
-    if (!FuzzyEqualsAdditive(GetOrigin().value, GetPageStart().value, COORDINATE_EPSILON)) {
-      nsPrintfCString message("composition origin (%f) is not within COORDINATE_EPISLON of page origin (%f)\n",
+    if (!FuzzyEqualsCoordinate(GetOrigin().value, GetPageStart().value)) {
+      nsPrintfCString message("composition origin (%f) is not equal (within error) to page origin (%f)\n",
                               GetOrigin().value, GetPageStart().value);
       NS_ASSERTION(false, message.get());
       MOZ_CRASH();
     }
 #endif
     MOZ_ASSERT(mOverscroll <= 0);
   }
   mOverscroll += aOverscroll;
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -20,16 +20,26 @@ const float EPSILON = 0.0001f;
 // Epsilon to be used when comparing 'float' coordinate values
 // with FuzzyEqualsAdditive. The rationale is that 'float' has 7 decimal
 // digits of precision, and coordinate values should be no larger than in the
 // ten thousands. Note also that the smallest legitimate difference in page
 // coordinates is 1 app unit, which is 1/60 of a (CSS pixel), so this epsilon
 // isn't too large.
 const float COORDINATE_EPSILON = 0.01f;
 
+/**
+ * Compare two coordinates for equality, accounting for rounding error.
+ * Use both FuzzyEqualsAdditive() with COORDINATE_EPISLON, which accounts for
+ * things like the error introduced by rounding during a round-trip to app
+ * units, and FuzzyEqualsMultiplicative(), which accounts for accumulated error
+ * due to floating-point operations (which can be larger than COORDINATE_EPISLON
+ * for sufficiently large coordinate values).
+ */
+bool FuzzyEqualsCoordinate(float aValue1, float aValue2);
+
 struct FrameMetrics;
 class AsyncPanZoomController;
 
 /**
  * Helper class to maintain each axis of movement (X,Y) for panning and zooming.
  * Note that everything here is specific to one axis; that is, the X axis knows
  * nothing about the Y axis and vice versa.
  */
--- a/hal/cocoa/CocoaGamepad.cpp
+++ b/hal/cocoa/CocoaGamepad.cpp
@@ -18,105 +18,184 @@
 namespace {
 
 using mozilla::dom::GamepadService;
 
 using std::vector;
 
 struct Button {
   int id;
+  bool analog;
   IOHIDElementRef element;
+  CFIndex min;
+  CFIndex max;
+
+  Button(int aId, IOHIDElementRef aElement, CFIndex aMin, CFIndex aMax) :
+    id(aId),
+    analog((aMax - aMin) > 1),
+    element(aElement),
+    min(aMin),
+    max(aMax) {}
 };
 
 struct Axis {
   int id;
   IOHIDElementRef element;
+  uint32_t usagePage;
+  uint32_t usage;
   CFIndex min;
   CFIndex max;
 };
 
+typedef bool dpad_buttons[4];
+
 // These values can be found in the USB HID Usage Tables:
 // http://www.usb.org/developers/hidpage
-#define GENERIC_DESKTOP_USAGE_PAGE 0x01
-#define JOYSTICK_USAGE_NUMBER 0x04
-#define GAMEPAD_USAGE_NUMBER 0x05
-#define AXIS_MIN_USAGE_NUMBER 0x30
-#define AXIS_MAX_USAGE_NUMBER 0x35
-#define BUTTON_USAGE_PAGE 0x09
+const unsigned kDesktopUsagePage = 0x01;
+const unsigned kSimUsagePage = 0x02;
+const unsigned kAcceleratorUsage = 0xC4;
+const unsigned kBrakeUsage = 0xC5;
+const unsigned kJoystickUsage = 0x04;
+const unsigned kGamepadUsage = 0x05;
+const unsigned kAxisUsageMin = 0x30;
+const unsigned kAxisUsageMax = 0x35;
+const unsigned kDpadUsage = 0x39;
+const unsigned kButtonUsagePage = 0x09;
+const unsigned kConsumerPage = 0x0C;
+const unsigned kHomeUsage = 0x223;
+const unsigned kBackUsage = 0x224;
+
 
 class Gamepad {
  private:
   IOHIDDeviceRef mDevice;
-  vector<Button> buttons;
-  vector<Axis> axes;
+  nsTArray<Button> buttons;
+  nsTArray<Axis> axes;
+  IOHIDElementRef mDpad;
+  dpad_buttons mDpadState;
 
  public:
-  Gamepad() : mDevice(nullptr), mSuperIndex(-1) {}
+  Gamepad() : mDevice(nullptr), mDpad(nullptr), mSuperIndex(-1) {}
   bool operator==(IOHIDDeviceRef device) const { return mDevice == device; }
   bool empty() const { return mDevice == nullptr; }
-  void clear() {
+  void clear()
+  {
     mDevice = nullptr;
-    buttons.clear();
-    axes.clear();
+    buttons.Clear();
+    axes.Clear();
+    mDpad = nullptr;
     mSuperIndex = -1;
   }
   void init(IOHIDDeviceRef device);
-  size_t numButtons() { return buttons.size(); }
-  size_t numAxes() { return axes.size(); }
+  size_t numButtons() { return buttons.Length() + (mDpad ? 4 : 0); }
+  size_t numAxes() { return axes.Length(); }
 
   // Index given by our superclass.
   uint32_t mSuperIndex;
 
-  const Button* lookupButton(IOHIDElementRef element) const {
-    for (size_t i = 0; i < buttons.size(); i++) {
+  const bool isDpad(IOHIDElementRef element) const
+  {
+    return element == mDpad;
+  }
+
+  const dpad_buttons& getDpadState() const
+  {
+    return mDpadState;
+  }
+
+  void setDpadState(const dpad_buttons& dpadState)
+  {
+    for (unsigned i = 0; i < ArrayLength(mDpadState); i++) {
+      mDpadState[i] = dpadState[i];
+    }
+  }
+
+  const Button* lookupButton(IOHIDElementRef element) const
+  {
+    for (unsigned i = 0; i < buttons.Length(); i++) {
       if (buttons[i].element == element)
         return &buttons[i];
     }
     return nullptr;
   }
 
-  const Axis* lookupAxis(IOHIDElementRef element) const {
-    for (size_t i = 0; i < axes.size(); i++) {
+  const Axis* lookupAxis(IOHIDElementRef element) const
+  {
+    for (unsigned i = 0; i < axes.Length(); i++) {
       if (axes[i].element == element)
         return &axes[i];
     }
     return nullptr;
   }
 };
 
-void Gamepad::init(IOHIDDeviceRef device) {
+class AxisComparator {
+public:
+  bool Equals(const Axis& a1, const Axis& a2) const
+  {
+    return a1.usagePage == a2.usagePage && a1.usage == a2.usage;
+  }
+  bool LessThan(const Axis& a1, const Axis& a2) const
+  {
+    if (a1.usagePage == a2.usagePage) {
+      return a1.usage < a2.usage;
+    }
+    return a1.usagePage < a2.usagePage;
+  }
+};
+
+void Gamepad::init(IOHIDDeviceRef device)
+{
   clear();
   mDevice = device;
 
   CFArrayRef elements = IOHIDDeviceCopyMatchingElements(device,
                                                         nullptr,
                                                         kIOHIDOptionsTypeNone);
   CFIndex n = CFArrayGetCount(elements);
   for (CFIndex i = 0; i < n; i++) {
     IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements,
                                                                       i);
     uint32_t usagePage = IOHIDElementGetUsagePage(element);
     uint32_t usage = IOHIDElementGetUsage(element);
 
-    if (usagePage == GENERIC_DESKTOP_USAGE_PAGE &&
-        usage >= AXIS_MIN_USAGE_NUMBER &&
-        usage <= AXIS_MAX_USAGE_NUMBER)
+    if (usagePage == kDesktopUsagePage &&
+        usage >= kAxisUsageMin &&
+        usage <= kAxisUsageMax)
     {
-      Axis axis = { int(axes.size()),
+      Axis axis = { int(axes.Length()),
                     element,
+                    usagePage,
+                    usage,
                     IOHIDElementGetLogicalMin(element),
                     IOHIDElementGetLogicalMax(element) };
-      axes.push_back(axis);
-    } else if (usagePage == BUTTON_USAGE_PAGE) {
-      Button button = { int(usage) - 1, element };
-      buttons.push_back(button);
+      axes.AppendElement(axis);
+    } else if (usagePage == kDesktopUsagePage && usage == kDpadUsage &&
+               // Don't know how to handle d-pads that return weird values.
+               IOHIDElementGetLogicalMax(element) - IOHIDElementGetLogicalMin(element) == 7) {
+      mDpad = element;
+    } else if ((usagePage == kSimUsagePage &&
+                 (usage == kAcceleratorUsage ||
+                  usage == kBrakeUsage)) ||
+               (usagePage == kButtonUsagePage) ||
+               (usagePage == kConsumerPage &&
+                 (usage == kHomeUsage ||
+                  usage == kBackUsage))) {
+      Button button(int(buttons.Length()), element, IOHIDElementGetLogicalMin(element), IOHIDElementGetLogicalMax(element));
+      buttons.AppendElement(button);
     } else {
       //TODO: handle other usage pages
     }
   }
+
+  AxisComparator comparator;
+  axes.Sort(comparator);
+  for (unsigned i = 0; i < axes.Length(); i++) {
+    axes[i].id = i;
+  }
 }
 
 class DarwinGamepadService {
  private:
   IOHIDManagerRef mManager;
   vector<Gamepad> mGamepads;
 
   static void DeviceAddedCallback(void* data, IOReturn result,
@@ -184,33 +263,100 @@ DarwinGamepadService::DeviceRemoved(IOHI
     if (mGamepads[i] == device) {
       service->RemoveGamepad(mGamepads[i].mSuperIndex);
       mGamepads[i].clear();
       return;
     }
   }
 }
 
+/*
+ * Given a value from a d-pad (POV hat in USB HID terminology),
+ * represent it as 4 buttons, one for each cardinal direction.
+ */
+static void
+UnpackDpad(int dpad_value, int min, int max, dpad_buttons& buttons)
+{
+  const unsigned kUp = 0;
+  const unsigned kDown = 1;
+  const unsigned kLeft = 2;
+  const unsigned kRight = 3;
+
+  // Different controllers have different ways of representing
+  // "nothing is pressed", but they're all outside the range of values.
+  if (dpad_value < min || dpad_value > max) {
+    // Nothing is pressed.
+    return;
+  }
+
+  // Normalize value to start at 0.
+  int value = dpad_value - min;
+
+  // Value will be in the range 0-7. The value represents the
+  // position of the d-pad around a circle, with 0 being straight up,
+  // 2 being right, 4 being straight down, and 6 being left.
+  if (value < 2 || value > 6) {
+    buttons[kUp] = true;
+  }
+  if (value > 2 && value < 6) {
+    buttons[kDown] = true;
+  }
+  if (value > 4) {
+    buttons[kLeft] = true;
+  }
+  if (value > 0 && value < 4) {
+    buttons[kRight] = true;
+  }
+}
+
 void
 DarwinGamepadService::InputValueChanged(IOHIDValueRef value)
 {
+  uint32_t value_length = IOHIDValueGetLength(value);
+  if (value_length > 4) {
+    // Workaround for bizarre issue with PS3 controllers that try to return
+    // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
+    return;
+  }
   nsRefPtr<GamepadService> service(GamepadService::GetService());
   IOHIDElementRef element = IOHIDValueGetElement(value);
   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
-  for (size_t i = 0; i < mGamepads.size(); i++) {
-    const Gamepad &gamepad = mGamepads[i];
+  for (unsigned i = 0; i < mGamepads.size(); i++) {
+    Gamepad &gamepad = mGamepads[i];
     if (gamepad == device) {
-      if (const Axis* axis = gamepad.lookupAxis(element)) {
+      if (gamepad.isDpad(element)) {
+        const dpad_buttons& oldState = gamepad.getDpadState();
+        dpad_buttons newState = { false, false, false, false };
+        UnpackDpad(IOHIDValueGetIntegerValue(value),
+                   IOHIDElementGetLogicalMin(element),
+                   IOHIDElementGetLogicalMax(element),
+                   newState);
+        const int numButtons = gamepad.numButtons();
+        for (unsigned b = 0; b < ArrayLength(newState); b++) {
+          if (newState[b] != oldState[b]) {
+            service->NewButtonEvent(i, numButtons - 4 + b, newState[b]);
+          }
+        }
+        gamepad.setDpadState(newState);
+      } else if (const Axis* axis = gamepad.lookupAxis(element)) {
         double d = IOHIDValueGetIntegerValue(value);
         double v = 2.0f * (d - axis->min) /
           (double)(axis->max - axis->min) - 1.0f;
         service->NewAxisMoveEvent(i, axis->id, v);
       } else if (const Button* button = gamepad.lookupButton(element)) {
-        bool pressed = IOHIDValueGetIntegerValue(value) != 0;
-        service->NewButtonEvent(i, button->id, pressed);
+        int iv = IOHIDValueGetIntegerValue(value);
+        bool pressed = iv != 0;
+        double v = 0;
+        if (button->analog) {
+          double dv = iv;
+          v = (dv - button->min) / (double)(button->max - button->min);
+        } else {
+          v = pressed ? 1.0 : 0.0;
+        }
+        service->NewButtonEvent(i, button->id, pressed, v);
       }
       return;
     }
   }
 }
 
 void
 DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
@@ -281,25 +427,25 @@ void DarwinGamepadService::Startup()
 {
   if (mManager != nullptr)
     return;
 
   IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault,
                                                kIOHIDOptionsTypeNone);
 
   CFMutableDictionaryRef criteria_arr[2];
-  criteria_arr[0] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE,
-                                       JOYSTICK_USAGE_NUMBER);
+  criteria_arr[0] = MatchingDictionary(kDesktopUsagePage,
+                                       kJoystickUsage);
   if (!criteria_arr[0]) {
     CFRelease(manager);
     return;
   }
 
-  criteria_arr[1] = MatchingDictionary(GENERIC_DESKTOP_USAGE_PAGE,
-                                       GAMEPAD_USAGE_NUMBER);
+  criteria_arr[1] = MatchingDictionary(kDesktopUsagePage,
+                                       kGamepadUsage);
   if (!criteria_arr[1]) {
     CFRelease(criteria_arr[0]);
     CFRelease(manager);
     return;
   }
 
   CFArrayRef criteria =
     CFArrayCreate(kCFAllocatorDefault, (const void**)criteria_arr, 2, nullptr);
--- a/image/src/ProgressTracker.cpp
+++ b/image/src/ProgressTracker.cpp
@@ -487,25 +487,17 @@ ProgressTracker::OnDiscard()
   MOZ_ASSERT(NS_IsMainThread());
   NOTIFY_IMAGE_OBSERVERS(mObservers,
                          Notify(imgINotificationObserver::DISCARD));
 }
 
 void
 ProgressTracker::OnImageAvailable()
 {
-  if (!NS_IsMainThread()) {
-    // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter
-    // so subsequent calls or dispatches which Unlock or Decrement~ should
-    // be issued after this to avoid race conditions.
-    NS_DispatchToMainThread(
-      NS_NewRunnableMethod(this, &ProgressTracker::OnImageAvailable));
-    return;
-  }
-
+  MOZ_ASSERT(NS_IsMainThread());
   // Notify any imgRequestProxys that are observing us that we have an Image.
   ObserverArray::ForwardIterator iter(mObservers);
   while (iter.HasMore()) {
     nsRefPtr<IProgressObserver> observer = iter.GetNext().get();
     if (observer) {
       observer->SetHasImage();
     }
   }
--- a/image/src/ProgressTracker.h
+++ b/image/src/ProgressTracker.h
@@ -120,17 +120,17 @@ public:
   // not threadsafe.
   void SyncNotify(IProgressObserver* aObserver);
 
   // Get this ProgressTracker ready for a new request. This resets all the
   // state that doesn't persist between requests.
   void ResetForNewRequest();
 
   // Stateless notifications. These are dispatched and immediately forgotten
-  // about. All except OnImageAvailable are main thread only.
+  // about. All of these notifications are main thread only.
   void OnDiscard();
   void OnUnlockedDraw();
   void OnImageAvailable();
 
   // Compute the difference between this our progress and aProgress. This allows
   // callers to predict whether SyncNotifyProgress will send any notifications.
   Progress Difference(Progress aProgress) const
   {
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1431,21 +1431,16 @@ RasterImage::RequestDecode()
 
   return RequestDecodeForSize(mSize, DECODE_FLAGS_DEFAULT);
 }
 
 /* void startDecode() */
 NS_IMETHODIMP
 RasterImage::StartDecoding()
 {
-  if (!NS_IsMainThread()) {
-    return NS_DispatchToMainThread(
-      NS_NewRunnableMethod(this, &RasterImage::StartDecoding));
-  }
-
   // For decode-on-draw images, we only act on RequestDecodeForSize.
   if (mDecodeOnDraw) {
     return NS_OK;
   }
 
   return RequestDecodeForSize(mSize, FLAG_SYNC_DECODE_IF_FAST);
 }
 
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -23,17 +23,16 @@
 #include "nsIHttpChannel.h"
 #include "nsICachingChannel.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIChannelEventSink.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIFileURL.h"
 #include "nsCRT.h"
-#include "nsIDocument.h"
 #include "nsINetworkPredictor.h"
 
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 
 #include "nsIMemoryReporter.h"
 #include "Image.h"
 #include "gfxPrefs.h"
@@ -1442,34 +1441,34 @@ bool imgLoader::ValidateRequestWithNewCh
   // now we need to insert a new channel request object inbetween the real
   // request and the proxy that basically delays loading the image until it
   // gets a 304 or figures out that this needs to be a new request
 
   nsresult rv;
 
   // If we're currently in the middle of validating this request, just hand
   // back a proxy to it; the required work will be done for us.
-  if (request->mValidator) {
+  if (request->GetValidator()) {
     rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
                                   aLoadFlags, aProxyRequest);
     if (NS_FAILED(rv)) {
       return false;
     }
 
     if (*aProxyRequest) {
       imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
 
       // We will send notifications from imgCacheValidator::OnStartRequest().
       // In the mean time, we must defer notifications because we are added to
       // the imgRequest's proxy list, and we can get extra notifications
       // resulting from methods such as RequestDecode(). See bug 579122.
       proxy->SetNotificationsDeferred(true);
 
       // Attach the proxy without notifying
-      request->mValidator->AddProxy(proxy);
+      request->GetValidator()->AddProxy(proxy);
     }
 
     return NS_SUCCEEDED(rv);
 
   } else {
     // We will rely on Necko to cache this request when it's possible, and to
     // tell imgCacheValidator::OnStartRequest whether the request came from its
     // cache.
@@ -1524,17 +1523,17 @@ bool imgLoader::ValidateRequestWithNewCh
       rv = corsproxy->Init(newChannel);
       if (NS_FAILED(rv)) {
         return false;
       }
 
       listener = corsproxy;
     }
 
-    request->mValidator = hvc;
+    request->SetValidator(hvc);
 
     // We will send notifications from imgCacheValidator::OnStartRequest().
     // In the mean time, we must defer notifications because we are added to
     // the imgRequest's proxy list, and we can get extra notifications
     // resulting from methods such as RequestDecode(). See bug 579122.
     req->SetNotificationsDeferred(true);
 
     // Add the proxy without notifying
@@ -1623,17 +1622,17 @@ bool imgLoader::ValidateEntry(imgCacheEn
 
   // If the request's loadId is the same as the aCX, then it is ok to use
   // this one because it has already been validated for this context.
   //
   // XXX: nullptr seems to be a 'special' key value that indicates that NO
   //      validation is required.
   //
   void *key = (void *)aCX;
-  if (request->mLoadId != key) {
+  if (request->LoadId() != key) {
     // If we would need to revalidate this entry, but we're being told to
     // bypass the cache, we don't allow this entry to be used.
     if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)
       return false;
 
     // Determine whether the cache aEntry must be revalidated...
     validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
 
@@ -1652,20 +1651,22 @@ bool imgLoader::ValidateEntry(imgCacheEn
   }
 #endif
 
   // We can't use a cached request if it comes from a different
   // application cache than this load is expecting.
   nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
   nsCOMPtr<nsIApplicationCache> requestAppCache;
   nsCOMPtr<nsIApplicationCache> groupAppCache;
-  if ((appCacheContainer = do_GetInterface(request->mRequest)))
+  if ((appCacheContainer = do_GetInterface(request->GetRequest()))) {
     appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
-  if ((appCacheContainer = do_QueryInterface(aLoadGroup)))
+  }
+  if ((appCacheContainer = do_QueryInterface(aLoadGroup))) {
     appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
+  }
 
   if (requestAppCache != groupAppCache) {
     PR_LOG(GetImgLog(), PR_LOG_DEBUG,
            ("imgLoader::ValidateEntry - Unable to use cached imgRequest "
             "[request=%p] because of mismatched application caches\n",
             address_of(request)));
     return false;
   }
@@ -2041,22 +2042,16 @@ nsresult imgLoader::LoadImage(nsIURI *aU
                   aLoadingPrincipal, corsmode, aReferrerPolicy);
 
     // Add the initiator type for this image load
     nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel);
     if (timedChannel) {
       timedChannel->SetInitiatorType(initiatorType);
     }
 
-    // Pass the inner window ID of the loading document, if possible.
-    nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
-    if (doc) {
-      request->SetInnerWindowID(doc->InnerWindowID());
-    }
-
     // create the proxy listener
     nsCOMPtr<nsIStreamListener> pl = new ProxyListener(request.get());
 
     // See if we need to insert a CORS proxy between the proxy listener and the
     // request.
     nsCOMPtr<nsIStreamListener> listener = pl;
     if (corsmode != imgIRequest::CORS_NONE) {
       PR_LOG(GetImgLog(), PR_LOG_DEBUG,
@@ -2510,30 +2505,31 @@ ProxyListener::CheckListenerChain()
 
 NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
                   nsIThreadRetargetableStreamListener,
                   nsIChannelEventSink, nsIInterfaceRequestor,
                   nsIAsyncVerifyRedirectCallback)
 
 imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
                                      imgLoader* loader, imgRequest *request,
-                                     void *aContext, bool forcePrincipalCheckForCacheEntry)
+                                     nsISupports* aContext,
+                                     bool forcePrincipalCheckForCacheEntry)
  : mProgressProxy(progress),
    mRequest(request),
    mContext(aContext),
    mImgLoader(loader)
 {
   NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader, getter_AddRefs(mNewRequest),
                      getter_AddRefs(mNewEntry));
 }
 
 imgCacheValidator::~imgCacheValidator()
 {
   if (mRequest) {
-    mRequest->mValidator = nullptr;
+    mRequest->SetValidator(nullptr);
   }
 }
 
 void imgCacheValidator::AddProxy(imgRequestProxy *aProxy)
 {
   // aProxy needs to be in the loadgroup since we're validating from
   // the network.
   aProxy->AddToLoadGroup();
@@ -2551,20 +2547,25 @@ NS_IMETHODIMP imgCacheValidator::OnStart
   // we have to do is tell them to notify their listeners.
   nsCOMPtr<nsICachingChannel> cacheChan(do_QueryInterface(aRequest));
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
   if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) {
     bool isFromCache = false;
     cacheChan->IsFromCache(&isFromCache);
 
     nsCOMPtr<nsIURI> channelURI;
+    channel->GetURI(getter_AddRefs(channelURI));
+
+    nsCOMPtr<nsIURI> currentURI;
+    mRequest->GetCurrentURI(getter_AddRefs(currentURI));
+
     bool sameURI = false;
-    channel->GetURI(getter_AddRefs(channelURI));
-    if (channelURI)
-      channelURI->Equals(mRequest->mCurrentURI, &sameURI);
+    if (channelURI && currentURI) {
+      channelURI->Equals(currentURI, &sameURI);
+    }
 
     if (isFromCache && sameURI) {
       uint32_t count = mProxies.Count();
       for (int32_t i = count-1; i>=0; i--) {
         imgRequestProxy *proxy = static_cast<imgRequestProxy *>(mProxies[i]);
 
         // Proxies waiting on cache validation should be deferring notifications.
         // Undefer them.
@@ -2577,17 +2578,17 @@ NS_IMETHODIMP imgCacheValidator::OnStart
         // asynchronously-called function.
         proxy->SyncNotifyListener();
       }
 
       // We don't need to load this any more.
       aRequest->Cancel(NS_BINDING_ABORTED);
 
       mRequest->SetLoadId(mContext);
-      mRequest->mValidator = nullptr;
+      mRequest->SetValidator(nullptr);
 
       mRequest = nullptr;
 
       mNewRequest = nullptr;
       mNewEntry = nullptr;
 
       return NS_OK;
     }
@@ -2610,17 +2611,17 @@ NS_IMETHODIMP imgCacheValidator::OnStart
 
   int32_t corsmode = mRequest->GetCORSMode();
   ReferrerPolicy refpol = mRequest->GetReferrerPolicy();
   nsCOMPtr<nsIPrincipal> loadingPrincipal = mRequest->GetLoadingPrincipal();
 
   // Doom the old request's cache entry
   mRequest->RemoveFromCache();
 
-  mRequest->mValidator = nullptr;
+  mRequest->SetValidator(nullptr);
   mRequest = nullptr;
 
   // We use originalURI here to fulfil the imgIRequest contract on GetURI.
   nsCOMPtr<nsIURI> originalURI;
   channel->GetOriginalURI(getter_AddRefs(originalURI));
   mNewRequest->Init(originalURI, uri, aRequest, channel, mNewEntry,
                     mContext, loadingPrincipal,
                     corsmode, refpol);
--- a/image/src/imgLoader.h
+++ b/image/src/imgLoader.h
@@ -492,17 +492,18 @@ class nsProgressNotificationProxy final
 class imgCacheValidator : public nsIStreamListener,
                           public nsIThreadRetargetableStreamListener,
                           public nsIChannelEventSink,
                           public nsIInterfaceRequestor,
                           public nsIAsyncVerifyRedirectCallback
 {
 public:
   imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader,
-                    imgRequest *request, void *aContext, bool forcePrincipalCheckForCacheEntry);
+                    imgRequest* aRequest, nsISupports* aContext,
+                    bool forcePrincipalCheckForCacheEntry);
 
   void AddProxy(imgRequestProxy *aProxy);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
@@ -518,14 +519,14 @@ private:
   nsCOMPtr<nsIChannel> mRedirectChannel;
 
   nsRefPtr<imgRequest> mRequest;
   nsCOMArray<imgIRequest> mProxies;
 
   nsRefPtr<imgRequest> mNewRequest;
   nsRefPtr<imgCacheEntry> mNewEntry;
 
-  void *mContext;
+  nsCOMPtr<nsISupports> mContext;
 
   imgLoader* mImgLoader;
 };
 
 #endif  // imgLoader_h__
--- a/image/src/imgRequest.cpp
+++ b/image/src/imgRequest.cpp
@@ -13,16 +13,17 @@
 #include "ProgressTracker.h"
 #include "ImageFactory.h"
 #include "Image.h"
 #include "MultipartImage.h"
 #include "RasterImage.h"
 
 #include "nsIChannel.h"
 #include "nsICachingChannel.h"
+#include "nsIDocument.h"
 #include "nsIThreadRetargetableRequest.h"
 #include "nsIInputStream.h"
 #include "nsIMultiPartChannel.h"
 #include "nsIHttpChannel.h"
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheChannel.h"
 #include "nsMimeTypes.h"
 
@@ -59,27 +60,27 @@ NS_IMPL_ISUPPORTS(imgRequest,
                   nsIStreamListener, nsIRequestObserver,
                   nsIThreadRetargetableStreamListener,
                   nsIChannelEventSink,
                   nsIInterfaceRequestor,
                   nsIAsyncVerifyRedirectCallback)
 
 imgRequest::imgRequest(imgLoader* aLoader)
  : mLoader(aLoader)
- , mProgressTracker(new ProgressTracker())
  , mValidator(nullptr)
- , mMutex("imgRequest")
  , mInnerWindowId(0)
  , mCORSMode(imgIRequest::CORS_NONE)
  , mReferrerPolicy(mozilla::net::RP_Default)
  , mImageErrorCode(NS_OK)
- , mDecodeRequested(false)
+ , mMutex("imgRequest")
+ , mProgressTracker(new ProgressTracker())
  , mIsMultiPartChannel(false)
  , mGotData(false)
  , mIsInCache(false)
+ , mDecodeRequested(false)
  , mNewPartPending(false)
 { }
 
 imgRequest::~imgRequest()
 {
   if (mLoader) {
     mLoader->RemoveFromUncachedImages(this);
   }
@@ -91,17 +92,17 @@ imgRequest::~imgRequest()
     LOG_FUNC(GetImgLog(), "imgRequest::~imgRequest()");
 }
 
 nsresult imgRequest::Init(nsIURI *aURI,
                           nsIURI *aCurrentURI,
                           nsIRequest *aRequest,
                           nsIChannel *aChannel,
                           imgCacheEntry *aCacheEntry,
-                          void *aLoadId,
+                          nsISupports* aCX,
                           nsIPrincipal* aLoadingPrincipal,
                           int32_t aCORSMode,
                           ReferrerPolicy aReferrerPolicy)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
 
   LOG_FUNC(GetImgLog(), "imgRequest::Init");
 
@@ -128,66 +129,49 @@ nsresult imgRequest::Init(nsIURI *aURI,
 
   NS_ASSERTION(mPrevChannelSink != this,
                "Initializing with a channel that already calls back to us!");
 
   mChannel->SetNotificationCallbacks(this);
 
   mCacheEntry = aCacheEntry;
 
-  SetLoadId(aLoadId);
+  SetLoadId(aCX);
+
+  // Grab the inner window ID of the loading document, if possible.
+  nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
+  if (doc) {
+    mInnerWindowId = doc->InnerWindowID();
+  }
 
   return NS_OK;
 }
 
 void imgRequest::ClearLoader() {
   mLoader = nullptr;
 }
 
-already_AddRefed<Image>
-imgRequest::GetImage()
-{
-  MutexAutoLock lock(mMutex);
-
-  nsRefPtr<Image> image = mImage;
-  return image.forget();
-}
-
 already_AddRefed<ProgressTracker>
-imgRequest::GetProgressTracker()
+imgRequest::GetProgressTracker() const
 {
   MutexAutoLock lock(mMutex);
 
   if (mImage) {
     MOZ_ASSERT(!mProgressTracker,
                "Should have given mProgressTracker to mImage");
     return mImage->GetProgressTracker();
   } else {
     MOZ_ASSERT(mProgressTracker,
                "Should have mProgressTracker until we create mImage");
     nsRefPtr<ProgressTracker> progressTracker = mProgressTracker;
     MOZ_ASSERT(progressTracker);
     return progressTracker.forget();
   }
 }
 
-void
-imgRequest::SetImage(Image* aImage)
-{
-  MutexAutoLock lock(mMutex);
-  mImage = aImage;
-}
-
-void
-imgRequest::SetProgressTracker(ProgressTracker* aProgressTracker)
-{
-  MutexAutoLock lock(mMutex);
-  mProgressTracker = aProgressTracker;
-}
-
 void imgRequest::SetCacheEntry(imgCacheEntry *entry)
 {
   mCacheEntry = entry;
 }
 
 bool imgRequest::HasCacheEntry() const
 {
   return mCacheEntry != nullptr;
@@ -377,16 +361,30 @@ void imgRequest::EvictFromCache()
 // Helper-method used by EvictFromCache()
 void imgRequest::ContinueEvict()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   RemoveFromCache();
 }
 
+void
+imgRequest::RequestDecode()
+{
+  MutexAutoLock lock(mMutex);
+  mDecodeRequested = true;
+}
+
+bool
+imgRequest::IsDecodeRequested() const
+{
+  MutexAutoLock lock(mMutex);
+  return mDecodeRequested;
+}
+
 nsresult imgRequest::GetURI(ImageURL **aURI)
 {
   MOZ_ASSERT(aURI);
 
   LOG_FUNC(GetImgLog(), "imgRequest::GetURI");
 
   if (mURI) {
     *aURI = mURI;
@@ -422,38 +420,55 @@ nsresult imgRequest::GetSecurityInfo(nsI
   LOG_FUNC(GetImgLog(), "imgRequest::GetSecurityInfo");
 
   // Missing security info means this is not a security load
   // i.e. it is not an error when security info is missing
   NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
   return NS_OK;
 }
 
-void imgRequest::RemoveFromCache()
+void
+imgRequest::RemoveFromCache()
 {
   LOG_SCOPE(GetImgLog(), "imgRequest::RemoveFromCache");
 
-  if (mIsInCache && mLoader) {
+  bool isInCache = false;
+
+  {
+    MutexAutoLock lock(mMutex);
+    isInCache = mIsInCache;
+  }
+
+  if (isInCache && mLoader) {
     // mCacheEntry is nulled out when we have no more observers.
     if (mCacheEntry) {
       mLoader->RemoveFromCache(mCacheEntry);
     } else {
       mLoader->RemoveFromCache(mURI);
     }
   }
 
   mCacheEntry = nullptr;
 }
 
-bool imgRequest::HasConsumers()
+bool
+imgRequest::HasConsumers() const
 {
   nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
   return progressTracker && progressTracker->ObserverCount() > 0;
 }
 
+already_AddRefed<Image>
+imgRequest::GetImage() const
+{
+  MutexAutoLock lock(mMutex);
+  nsRefPtr<Image> image = mImage;
+  return image.forget();
+}
+
 int32_t imgRequest::Priority() const
 {
   int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
   if (p)
     p->GetPriority(&priority);
   return priority;
 }
@@ -471,29 +486,42 @@ void imgRequest::AdjustPriority(imgReque
   if (!progressTracker->FirstObserverIs(proxy))
     return;
 
   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
   if (p)
     p->AdjustPriority(delta);
 }
 
-void imgRequest::SetIsInCache(bool incache)
+bool
+imgRequest::HasTransferredData() const
 {
-  LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequest::SetIsCacheable", "incache", incache);
-  mIsInCache = incache;
+  MutexAutoLock lock(mMutex);
+  return mGotData;
 }
 
-void imgRequest::UpdateCacheEntrySize()
+void
+imgRequest::SetIsInCache(bool aInCache)
 {
-  if (mCacheEntry) {
-    nsRefPtr<Image> image = GetImage();
-    size_t size = image->SizeOfSourceWithComputedFallback(moz_malloc_size_of);
-    mCacheEntry->SetDataSize(size);
+  LOG_FUNC_WITH_PARAM(GetImgLog(),
+                      "imgRequest::SetIsCacheable", "aInCache", aInCache);
+  MutexAutoLock lock(mMutex);
+  mIsInCache = aInCache;
+}
+
+void
+imgRequest::UpdateCacheEntrySize()
+{
+  if (!mCacheEntry) {
+    return;
   }
+
+  nsRefPtr<Image> image = GetImage();
+  size_t size = image->SizeOfSourceWithComputedFallback(moz_malloc_size_of);
+  mCacheEntry->SetDataSize(size);
 }
 
 void imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest)
 {
   /* get the expires info */
   if (aCacheEntry) {
     nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(aRequest));
     if (cacheChannel) {
@@ -600,123 +628,87 @@ imgRequest::CacheChanged(nsIRequest* aNe
   }
 
   // When we get here, app caches differ or app cache is involved
   // just in one of the loads what we also consider as a change
   // in a loading cache.
   return true;
 }
 
-nsresult
-imgRequest::LockImage()
-{
-  nsRefPtr<Image> image = GetImage();
-  return image->LockImage();
-}
-
-nsresult
-imgRequest::UnlockImage()
-{
-  nsRefPtr<Image> image = GetImage();
-  return image->UnlockImage();
-}
-
-nsresult
-imgRequest::RequestDecode()
+bool
+imgRequest::GetMultipart() const
 {
-  // If we've initialized our image, we can request a decode.
-  nsRefPtr<Image> image = GetImage();
-  if (image) {
-    return image->RequestDecode();
-  }
-
-  // Otherwise, flag to do it when we get the image
-  mDecodeRequested = true;
-
-  return NS_OK;
-}
-
-nsresult
-imgRequest::StartDecoding()
-{
-  // If we've initialized our image, we can request a decode.
-  nsRefPtr<Image> image = GetImage();
-  if (image) {
-    return image->StartDecoding();
-  }
-
-  // Otherwise, flag to do it when we get the image
-  mDecodeRequested = true;
-
-  return NS_OK;
+  MutexAutoLock lock(mMutex);
+  return mIsMultiPartChannel;
 }
 
 /** nsIRequestObserver methods **/
 
 /* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */
 NS_IMETHODIMP imgRequest::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt)
 {
   LOG_SCOPE(GetImgLog(), "imgRequest::OnStartRequest");
 
-  mNewPartPending = true;
+  nsRefPtr<Image> image;
 
   // Figure out if we're multipart.
-  nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
-  nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
-  if (mpchan) {
-    mIsMultiPartChannel = true;
-  } else {
-    MOZ_ASSERT(!mIsMultiPartChannel, "Something went wrong");
+  nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+  MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel,
+             "Stopped being multipart?");
+
+  {
+    MutexAutoLock lock(mMutex);
+    mNewPartPending = true;
+    image = mImage;
+    mIsMultiPartChannel = bool(multiPartChannel);
   }
 
   // If we're not multipart, we shouldn't have an image yet.
-  nsRefPtr<Image> image = GetImage();
-  if (image && !mIsMultiPartChannel) {
+  if (image && !multiPartChannel) {
     MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request");
     Cancel(NS_IMAGELIB_ERROR_FAILURE);
     return NS_ERROR_FAILURE;
   }
 
   /*
    * If mRequest is null here, then we need to set it so that we'll be able to
    * cancel it if our Cancel() method is called.  Note that this can only
    * happen for multipart channels.  We could simply not null out mRequest for
    * non-last parts, if GetIsLastPart() were reliable, but it's not.  See
    * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
    */
   if (!mRequest) {
-    NS_ASSERTION(mpchan,
-                 "We should have an mRequest here unless we're multipart");
-    nsCOMPtr<nsIChannel> chan;
-    mpchan->GetBaseChannel(getter_AddRefs(chan));
-    mRequest = chan;
+    nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+    MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart");
+    nsCOMPtr<nsIChannel> baseChannel;
+    multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
+    mRequest = baseChannel;
   }
 
   nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
-  if (channel)
+  if (channel) {
     channel->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
 
-  /* Get our principal */
-  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
-  if (chan) {
+    /* Get our principal */
     nsCOMPtr<nsIScriptSecurityManager> secMan = nsContentUtils::GetSecurityManager();
     if (secMan) {
-      nsresult rv = secMan->GetChannelResultPrincipal(chan,
+      nsresult rv = secMan->GetChannelResultPrincipal(channel,
                                                       getter_AddRefs(mPrincipal));
       if (NS_FAILED(rv)) {
         return rv;
       }
     }
   }
 
   SetCacheValidation(mCacheEntry, aRequest);
 
   mApplicationCache = GetApplicationCache(aRequest);
 
   // Shouldn't we be dead already if this gets hit?  Probably multipart/x-mixed-replace...
+  nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
   if (progressTracker->ObserverCount() == 0) {
     this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
   }
 
   // Try to retarget OnDataAvailable to a decode thread.
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
   nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
     do_QueryInterface(aRequest);
@@ -739,16 +731,18 @@ NS_IMETHODIMP imgRequest::OnStartRequest
 }
 
 /* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */
 NS_IMETHODIMP imgRequest::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status)
 {
   LOG_FUNC(GetImgLog(), "imgRequest::OnStopRequest");
   MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread");
 
+  nsRefPtr<Image> image = GetImage();
+
   // XXXldb What if this is a non-last part of a multipart request?
   // xxx before we release our reference to mRequest, lets
   // save the last status that we saw so that the
   // imgRequestProxy will have access to it.
   if (mRequest) {
     mRequest = nullptr;  // we no longer need the request
   }
 
@@ -760,17 +754,16 @@ NS_IMETHODIMP imgRequest::OnStopRequest(
   }
 
   bool lastPart = true;
   nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
   if (mpchan)
     mpchan->GetIsLastPart(&lastPart);
 
   bool isPartial = false;
-  nsRefPtr<Image> image = GetImage();
   if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) {
     isPartial = true;
     status = NS_OK; // fake happy face
   }
 
   // Tell the image that it has all of the source data. Note that this can
   // trigger a failure, since the image might be waiting for more non-optional
   // data and this is the point where we break the news that it's not coming.
@@ -833,201 +826,246 @@ imgRequest::CheckListenerChain()
 {
   // TODO Might need more checking here.
   NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
   return NS_OK;
 }
 
 /** nsIStreamListener methods **/
 
-/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */
+struct NewPartResult final
+{
+  explicit NewPartResult(Image* aExistingImage)
+    : mImage(aExistingImage)
+    , mIsFirstPart(!aExistingImage)
+    , mSucceeded(false)
+  { }
+
+  nsAutoCString mContentType;
+  nsAutoCString mContentDisposition;
+  nsRefPtr<Image> mImage;
+  const bool mIsFirstPart;
+  bool mSucceeded;
+};
+
+static NewPartResult
+PrepareForNewPart(nsIRequest* aRequest, nsIInputStream* aInStr, uint32_t aCount,
+                  ImageURL* aURI, bool aIsMultipart, Image* aExistingImage,
+                  ProgressTracker* aProgressTracker, uint32_t aInnerWindowId)
+{
+  NewPartResult result(aExistingImage);
+
+  mimetype_closure closure;
+  closure.newType = &result.mContentType;
+
+  // Look at the first few bytes and see if we can tell what the data is from
+  // that since servers tend to lie. :(
+  uint32_t out;
+  aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out);
+
+  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
+  if (result.mContentType.IsEmpty()) {
+    nsresult rv = NS_ERROR_FAILURE;
+    if (chan) {
+      rv = chan->GetContentType(result.mContentType);
+      chan->GetContentDispositionHeader(result.mContentDisposition);
+    }
+
+    if (NS_FAILED(rv)) {
+      PR_LOG(GetImgLog(), PR_LOG_ERROR,
+             ("imgRequest::PrepareForNewPart -- Content type unavailable from the channel\n"));
+      return result;
+    }
+  }
+
+  PR_LOG(GetImgLog(), PR_LOG_DEBUG,
+         ("imgRequest::PrepareForNewPart -- Got content type %s\n",
+          result.mContentType.get()));
+
+  // XXX If server lied about mimetype and it's SVG, we may need to copy
+  // the data and dispatch back to the main thread, AND tell the channel to
+  // dispatch there in the future.
+
+  // Create the new image and give it ownership of our ProgressTracker.
+  if (aIsMultipart) {
+    // Create the ProgressTracker and image for this part.
+    nsRefPtr<ProgressTracker> progressTracker = new ProgressTracker();
+    nsRefPtr<Image> partImage =
+      ImageFactory::CreateImage(aRequest, progressTracker, result.mContentType,
+                                aURI, /* aIsMultipart = */ true,
+                                aInnerWindowId);
+
+    if (result.mIsFirstPart) {
+      // First part for a multipart channel. Create the MultipartImage wrapper.
+      MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
+      result.mImage = new MultipartImage(partImage, aProgressTracker);
+    } else {
+      // Transition to the new part.
+      auto multipartImage = static_cast<MultipartImage*>(aExistingImage);
+      multipartImage->BeginTransitionToPart(partImage);
+    }
+  } else {
+    MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?");
+    MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
+
+    // Create an image using our progress tracker.
+    result.mImage =
+      ImageFactory::CreateImage(aRequest, aProgressTracker, result.mContentType,
+                                aURI, /* aIsMultipart = */ false,
+                                aInnerWindowId);
+  }
+
+  MOZ_ASSERT(result.mImage);
+  if (!result.mImage->HasError() || aIsMultipart) {
+    // We allow multipart images to fail to initialize (which generally
+    // indicates a bad content type) without cancelling the load, because
+    // subsequent parts might be fine.
+    result.mSucceeded = true;
+  }
+
+  return result;
+}
+
+class FinishPreparingForNewPartRunnable final : public nsRunnable
+{
+public:
+  FinishPreparingForNewPartRunnable(imgRequest* aImgRequest,
+                                    NewPartResult&& aResult)
+    : mImgRequest(aImgRequest)
+    , mResult(aResult)
+  {
+    MOZ_ASSERT(aImgRequest);
+  }
+
+  NS_IMETHOD Run() override
+  {
+    mImgRequest->FinishPreparingForNewPart(mResult);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<imgRequest> mImgRequest;
+  NewPartResult mResult;
+};
+
+void
+imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mContentType = aResult.mContentType;
+
+  SetProperties(aResult.mContentType, aResult.mContentDisposition);
+
+  if (aResult.mIsFirstPart) {
+    // Notify listeners that we have an image.
+    nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+    progressTracker->OnImageAvailable();
+    MOZ_ASSERT(progressTracker->HasImage());
+  }
+
+  if (IsDecodeRequested()) {
+    aResult.mImage->RequestDecode();
+  }
+}
+
 NS_IMETHODIMP
-imgRequest::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt,
-                            nsIInputStream *inStr, uint64_t sourceOffset,
-                            uint32_t count)
+imgRequest::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
+                            nsIInputStream* aInStr, uint64_t aOffset,
+                            uint32_t aCount)
 {
-  LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", count);
+  LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "count", aCount);
 
   NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
 
-  nsresult rv;
-  mGotData = true;
-  nsRefPtr<Image> image = GetImage();
+  nsRefPtr<Image> image;
+  nsRefPtr<ProgressTracker> progressTracker;
+  bool isMultipart = false;
+  bool newPartPending = false;
 
-  if (mNewPartPending) {
-    LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |New part; finding MIME type|");
-
+  // Retrieve and update our state.
+  {
+    MutexAutoLock lock(mMutex);
+    mGotData = true;
+    image = mImage;
+    progressTracker = mProgressTracker;
+    isMultipart = mIsMultiPartChannel;
+    newPartPending = mNewPartPending;
     mNewPartPending = false;
-
-    mimetype_closure closure;
-    nsAutoCString newType;
-    closure.newType = &newType;
+  }
 
-    /* look at the first few bytes and see if we can tell what the data is from that
-     * since servers tend to lie. :(
-     */
-    uint32_t out;
-    inStr->ReadSegments(sniff_mimetype_callback, &closure, count, &out);
+  // If this is a new part, we need to sniff its content type and create an
+  // appropriate image.
+  if (newPartPending) {
+    NewPartResult result = PrepareForNewPart(aRequest, aInStr, aCount, mURI,
+                                             isMultipart, image,
+                                             progressTracker, mInnerWindowId);
+    bool succeeded = result.mSucceeded;
 
-    nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
-    if (newType.IsEmpty()) {
-      LOG_SCOPE(GetImgLog(), "imgRequest::OnDataAvailable |sniffing of mimetype failed|");
+    if (result.mImage) {
+      image = result.mImage;
 
-      rv = NS_ERROR_FAILURE;
-      if (chan) {
-        rv = chan->GetContentType(newType);
+      // Update our state to reflect this new part.
+      {
+        MutexAutoLock lock(mMutex);
+        mImage = image;
+        mProgressTracker = nullptr;
       }
 
-      if (NS_FAILED(rv)) {
-        PR_LOG(GetImgLog(), PR_LOG_ERROR,
-               ("[this=%p] imgRequest::OnDataAvailable -- Content type unavailable from the channel\n",
-                this));
-
-        this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
-
-        return NS_BINDING_ABORTED;
+      // Some property objects are not threadsafe, and we need to send
+      // OnImageAvailable on the main thread, so finish on the main thread.
+      if (NS_IsMainThread()) {
+        FinishPreparingForNewPart(result);
+      } else {
+        nsCOMPtr<nsIRunnable> runnable =
+          new FinishPreparingForNewPartRunnable(this, Move(result));
+        NS_DispatchToMainThread(runnable);
       }
-
-      LOG_MSG(GetImgLog(), "imgRequest::OnDataAvailable", "Got content type from the channel");
     }
 
-    mContentType = newType;
-    SetProperties(chan);
-    bool firstPart = !image;
-
-    LOG_MSG_WITH_PARAM(GetImgLog(), "imgRequest::OnDataAvailable", "content type", mContentType.get());
-
-    // XXX If server lied about mimetype and it's SVG, we may need to copy
-    // the data and dispatch back to the main thread, AND tell the channel to
-    // dispatch there in the future.
-
-    nsRefPtr<ProgressTracker> progressTracker;
-    {
-      MutexAutoLock lock(mMutex);
-      progressTracker = mProgressTracker;
-    }
-
-    // Create the new image and give it ownership of our ProgressTracker.
-    if (mIsMultiPartChannel) {
-      // Create the ProgressTracker and image for this part.
-      nsRefPtr<ProgressTracker> partProgressTracker = new ProgressTracker();
-      nsRefPtr<Image> partImage =
-        ImageFactory::CreateImage(aRequest, partProgressTracker, mContentType,
-                                  mURI, /* aIsMultipart = */ true,
-                                  static_cast<uint32_t>(mInnerWindowId));
-
-      if (!image) {
-        // First part for a multipart channel. Create the MultipartImage wrapper.
-        MOZ_ASSERT(progressTracker, "Shouldn't have given away tracker yet");
-        image = new MultipartImage(partImage, progressTracker);
-        SetImage(image);
-        progressTracker = nullptr;
-        SetProgressTracker(nullptr);
-      } else {
-        // Transition to the new part.
-        static_cast<MultipartImage*>(image.get())->BeginTransitionToPart(partImage);
-      }
-    } else {
-      MOZ_ASSERT(!image, "New part for non-multipart channel?");
-      MOZ_ASSERT(progressTracker, "Shouldn't have given away tracker yet");
-
-      // Create an image using our progress tracker.
-      image =
-        ImageFactory::CreateImage(aRequest, progressTracker, mContentType,
-                                  mURI, /* aIsMultipart = */ false,
-                                  static_cast<uint32_t>(mInnerWindowId));
-      SetImage(image);
-      progressTracker = nullptr;
-      SetProgressTracker(nullptr);
-    }
-
-    if (firstPart) {
-      // Notify listeners that we have an image.
-      nsRefPtr<ProgressTracker> progressTracker = GetProgressTracker();
-      progressTracker->OnImageAvailable();
-      MOZ_ASSERT(progressTracker->HasImage());
-    }
-
-    if (image->HasError() && !mIsMultiPartChannel) { // Probably bad mimetype
-      // We allow multipart images to fail to initialize without cancelling the
-      // load because subsequent images might be fine; thus only single part
-      // images end up here.
-      this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
+    if (!succeeded) {
+      // Something went wrong; probably a content type issue.
+      Cancel(NS_IMAGELIB_ERROR_FAILURE);
       return NS_BINDING_ABORTED;
     }
-
-    MOZ_ASSERT(!progressTracker, "Should've given tracker to image");
-    MOZ_ASSERT(image, "Should have image");
-
-    if (mDecodeRequested) {
-      image->StartDecoding();
-    }
   }
 
   // Notify the image that it has new data.
-  rv = image->OnImageDataAvailable(aRequest, ctxt, inStr, sourceOffset, count);
+  nsresult rv =
+    image->OnImageDataAvailable(aRequest, aContext, aInStr, aOffset, aCount);
 
   if (NS_FAILED(rv)) {
     PR_LOG(GetImgLog(), PR_LOG_WARNING,
            ("[this=%p] imgRequest::OnDataAvailable -- "
             "copy to RasterImage failed\n", this));
-    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
+    Cancel(NS_IMAGELIB_ERROR_FAILURE);
     return NS_BINDING_ABORTED;
   }
 
   return NS_OK;
 }
 
-class SetPropertiesEvent : public nsRunnable
+void
+imgRequest::SetProperties(const nsACString& aContentType,
+                          const nsACString& aContentDisposition)
 {
-public:
-  SetPropertiesEvent(imgRequest* aImgRequest, nsIChannel* aChan)
-    : mImgRequest(aImgRequest)
-    , mChan(aChan)
-  {
-    MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread");
-    MOZ_ASSERT(aImgRequest, "aImgRequest cannot be null");
-  }
-  NS_IMETHOD Run()
-  {
-    MOZ_ASSERT(NS_IsMainThread(), "Should run on the main thread only");
-    MOZ_ASSERT(mImgRequest, "mImgRequest cannot be null");
-    mImgRequest->SetProperties(mChan);
-    return NS_OK;
-  }
-private:
-  nsRefPtr<imgRequest> mImgRequest;
-  nsCOMPtr<nsIChannel> mChan;
-};
-
-void
-imgRequest::SetProperties(nsIChannel* aChan)
-{
-  // Force execution on main thread since some property objects are non
-  // threadsafe.
-  if (!NS_IsMainThread()) {
-    NS_DispatchToMainThread(new SetPropertiesEvent(this, aChan));
-    return;
-  }
   /* set our mimetype as a property */
-  nsCOMPtr<nsISupportsCString> contentType(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+  nsCOMPtr<nsISupportsCString> contentType =
+    do_CreateInstance("@mozilla.org/supports-cstring;1");
   if (contentType) {
-    contentType->SetData(mContentType);
+    contentType->SetData(aContentType);
     mProperties->Set("type", contentType);
   }
 
   /* set our content disposition as a property */
-  nsAutoCString disposition;
-  if (aChan) {
-    aChan->GetContentDispositionHeader(disposition);
-  }
-  if (!disposition.IsEmpty()) {
-    nsCOMPtr<nsISupportsCString> contentDisposition(do_CreateInstance("@mozilla.org/supports-cstring;1"));
+  if (!aContentDisposition.IsEmpty()) {
+    nsCOMPtr<nsISupportsCString> contentDisposition =
+      do_CreateInstance("@mozilla.org/supports-cstring;1");
     if (contentDisposition) {
-      contentDisposition->SetData(disposition);
+      contentDisposition->SetData(aContentDisposition);
       mProperties->Set("content-disposition", contentDisposition);
     }
   }
 }
 
 static NS_METHOD sniff_mimetype_callback(nsIInputStream* in,
                                          void* data,
                                          const char* fromRawSegment,
--- a/image/src/imgRequest.h
+++ b/image/src/imgRequest.h
@@ -21,56 +21,60 @@
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 class imgCacheValidator;
 class imgLoader;
 class imgRequestProxy;
 class imgCacheEntry;
-class imgMemoryReporter;
-class imgRequestNotifyRunnable;
 class nsIApplicationCache;
 class nsIProperties;
 class nsIRequest;
 class nsITimedChannel;
 class nsIURI;
 
 namespace mozilla {
 namespace image {
 class Image;
 class ImageURL;
 class ProgressTracker;
 } // namespace image
 } // namespace mozilla
 
+struct NewPartResult;
+
 class imgRequest final : public nsIStreamListener,
                              public nsIThreadRetargetableStreamListener,
                              public nsIChannelEventSink,
                              public nsIInterfaceRequestor,
                              public nsIAsyncVerifyRedirectCallback
 {
-  virtual ~imgRequest();
-
-public:
   typedef mozilla::image::Image Image;
   typedef mozilla::image::ImageURL ImageURL;
   typedef mozilla::image::ProgressTracker ProgressTracker;
   typedef mozilla::net::ReferrerPolicy ReferrerPolicy;
 
+public:
   explicit imgRequest(imgLoader* aLoader);
 
   NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSICHANNELEVENTSINK
+  NS_DECL_NSIINTERFACEREQUESTOR
+  NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
 
   nsresult Init(nsIURI *aURI,
                 nsIURI *aCurrentURI,
                 nsIRequest *aRequest,
                 nsIChannel *aChannel,
                 imgCacheEntry *aCacheEntry,
-                void *aLoadId,
+                nsISupports* aCX,
                 nsIPrincipal* aLoadingPrincipal,
                 int32_t aCORSMode,
                 ReferrerPolicy aReferrerPolicy);
 
   void ClearLoader();
 
   // Callers must call imgRequestProxy::Notify later.
   void AddProxy(imgRequestProxy *proxy);
@@ -83,26 +87,18 @@ public:
   void CancelAndAbort(nsresult aStatus);
 
   // Called or dispatched by cancel for main thread only execution.
   void ContinueCancel(nsresult aStatus);
 
   // Called or dispatched by EvictFromCache for main thread only execution.
   void ContinueEvict();
 
-  // Methods that get forwarded to the Image, or deferred until it's
-  // instantiated.
-  nsresult LockImage();
-  nsresult UnlockImage();
-  nsresult StartDecoding();
-  nsresult RequestDecode();
-
-  inline void SetInnerWindowID(uint64_t aInnerWindowId) {
-    mInnerWindowId = aInnerWindowId;
-  }
+  // Request that we start decoding the image as soon as data becomes available.
+  void RequestDecode();
 
   inline uint64_t InnerWindowID() const {
     return mInnerWindowId;
   }
 
   // Set the cache validation information (expiry time, whether we must
   // validate, etc) on the cache entry based on the request information.
   // If this function is called multiple times, the information set earliest
@@ -110,141 +106,131 @@ public:
   static void SetCacheValidation(imgCacheEntry* aEntry, nsIRequest* aRequest);
 
   // Check if application cache of the original load is different from
   // application cache of the new load.  Also lack of application cache
   // on one of the loads is considered a change of a loading cache since
   // HTTP cache may contain a different data then app cache.
   bool CacheChanged(nsIRequest* aNewRequest);
 
-  bool GetMultipart() const { return mIsMultiPartChannel; }
+  bool GetMultipart() const;
 
   // The CORS mode for which we loaded this image.
   int32_t GetCORSMode() const { return mCORSMode; }
 
   // The Referrer Policy in effect when loading this image.
   ReferrerPolicy GetReferrerPolicy() const { return mReferrerPolicy; }
 
   // The principal for the document that loaded this image. Used when trying to
   // validate a CORS image load.
   already_AddRefed<nsIPrincipal> GetLoadingPrincipal() const
   {
     nsCOMPtr<nsIPrincipal> principal = mLoadingPrincipal;
     return principal.forget();
   }
 
-  already_AddRefed<Image> GetImage();
-
   // Return the ProgressTracker associated with this imgRequest. It may live
   // in |mProgressTracker| or in |mImage.mProgressTracker|, depending on whether
   // mImage has been instantiated yet.
-  already_AddRefed<ProgressTracker> GetProgressTracker();
+  already_AddRefed<ProgressTracker> GetProgressTracker() const;
+
+  /// Returns the Image associated with this imgRequest, if it's ready.
+  already_AddRefed<Image> GetImage() const;
 
   // Get the current principal of the image. No AddRefing.
   inline nsIPrincipal* GetPrincipal() const { return mPrincipal.get(); }
 
   // Resize the cache entry to 0 if it exists
   void ResetCacheEntry();
 
   // OK to use on any thread.
   nsresult GetURI(ImageURL **aURI);
   nsresult GetCurrentURI(nsIURI **aURI);
 
   nsresult GetImageErrorCode(void);
 
-private:
-  friend class imgCacheEntry;
-  friend class imgRequestProxy;
-  friend class imgLoader;
-  friend class imgCacheValidator;
-  friend class imgCacheExpirationTracker;
-  friend class imgRequestNotifyRunnable;
-  friend class mozilla::image::ProgressTracker;
+  /// Returns true if we've received any data.
+  bool HasTransferredData() const;
+
+  /// Returns a non-owning pointer to this imgRequest's MIME type.
+  const char* GetMimeType() const { return mContentType.get(); }
+
+  /// @return the priority of the underlying network request, or
+  /// PRIORITY_NORMAL if it doesn't support nsISupportsPriority.
+  int32_t Priority() const;
+
+  /// Adjust the priority of the underlying network request by @aDelta on behalf
+  /// of @aProxy.
+  void AdjustPriority(imgRequestProxy* aProxy, int32_t aDelta);
+
+  /// Returns a weak pointer to the underlying request.
+  nsIRequest* GetRequest() const { return mRequest; }
+
+  nsITimedChannel* GetTimedChannel() const { return mTimedChannel; }
 
-  void SetImage(Image* aImage);
-  void SetProgressTracker(ProgressTracker* aProgressTracker);
+  nsresult GetSecurityInfo(nsISupports** aSecurityInfoOut);
+
+  imgCacheValidator* GetValidator() const { return mValidator; }
+  void SetValidator(imgCacheValidator* aValidator) { mValidator = aValidator; }
+
+  void* LoadId() const { return mLoadId; }
+  void SetLoadId(void* aLoadId) { mLoadId = aLoadId; }
 
-  inline void SetLoadId(void *aLoadId) {
-    mLoadId = aLoadId;
-  }
-  void Cancel(nsresult aStatus);
+  /// Reset the cache entry after we've dropped our reference to it. Used by
+  /// imgLoader when our cache entry is re-requested after we've dropped our
+  /// reference to it.
+  void SetCacheEntry(imgCacheEntry* aEntry);
+
+  /// Returns whether we've got a reference to the cache entry.
+  bool HasCacheEntry() const;
+
+  /// Set whether this request is stored in the cache. If it isn't, regardless
+  /// of whether this request has a non-null mCacheEntry, this imgRequest won't
+  /// try to update or modify the image cache.
+  void SetIsInCache(bool aCacheable);
+
   void EvictFromCache();
   void RemoveFromCache();
 
-  nsresult GetSecurityInfo(nsISupports **aSecurityInfo);
+  // Sets properties for this image; will dispatch to main thread if needed.
+  void SetProperties(const nsACString& aContentType,
+                     const nsACString& aContentDisposition);
+
+  nsIProperties* Properties() const { return mProperties; }
+
+  bool HasConsumers() const;
 
-  inline const char *GetMimeType() const {
-    return mContentType.get();
-  }
-  inline nsIProperties *Properties() {
-    return mProperties;
-  }
+private:
+  friend class FinishPreparingForNewPartRunnable;
 
-  // Reset the cache entry after we've dropped our reference to it. Used by the
-  // imgLoader when our cache entry is re-requested after we've dropped our
-  // reference to it.
-  void SetCacheEntry(imgCacheEntry *entry);
+  virtual ~imgRequest();
 
-  // Returns whether we've got a reference to the cache entry.
-  bool HasCacheEntry() const;
+  void FinishPreparingForNewPart(const NewPartResult& aResult);
+
+  void Cancel(nsresult aStatus);
 
   // Update the cache entry size based on the image container.
   void UpdateCacheEntrySize();
 
-  // Return the priority of the underlying network request, or return
-  // PRIORITY_NORMAL if it doesn't support nsISupportsPriority.
-  int32_t Priority() const;
-
-  // Adjust the priority of the underlying network request by the given delta
-  // on behalf of the given proxy.
-  void AdjustPriority(imgRequestProxy *aProxy, int32_t aDelta);
-
-  // Return whether we've seen some data at this point
-  bool HasTransferredData() const { return mGotData; }
-
-  // Set whether this request is stored in the cache. If it isn't, regardless
-  // of whether this request has a non-null mCacheEntry, this imgRequest won't
-  // try to update or modify the image cache.
-  void SetIsInCache(bool cacheable);
-
-  bool IsBlockingOnload() const;
-  void SetBlockingOnload(bool block) const;
-
-  bool HasConsumers();
-
-public:
-  NS_DECL_NSISTREAMLISTENER
-  NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
-  NS_DECL_NSIREQUESTOBSERVER
-  NS_DECL_NSICHANNELEVENTSINK
-  NS_DECL_NSIINTERFACEREQUESTOR
-  NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
-
-  // Sets properties for this image; will dispatch to main thread if needed.
-  void SetProperties(nsIChannel* aChan);
-
-private:
-  friend class imgMemoryReporter;
+  /// Returns true if RequestDecode() was called.
+  bool IsDecodeRequested() const;
 
   // Weak reference to parent loader; this request cannot outlive its owner.
   imgLoader* mLoader;
   nsCOMPtr<nsIRequest> mRequest;
   // The original URI we were loaded with. This is the same as the URI we are
   // keyed on in the cache. We store a string here to avoid off main thread
   // refcounting issues with nsStandardURL.
   nsRefPtr<ImageURL> mURI;
   // The URI of the resource we ended up loading after all redirects, etc.
   nsCOMPtr<nsIURI> mCurrentURI;
   // The principal of the document which loaded this image. Used when validating for CORS.
   nsCOMPtr<nsIPrincipal> mLoadingPrincipal;
   // The principal of this image.
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  // Progress tracker -- transferred to mImage, when it gets instantiated.
-  nsRefPtr<ProgressTracker> mProgressTracker;
-  nsRefPtr<Image> mImage;
   nsCOMPtr<nsIProperties> mProperties;
   nsCOMPtr<nsISupports> mSecurityInfo;
   nsCOMPtr<nsIChannel> mChannel;
   nsCOMPtr<nsIInterfaceRequestor> mPrevChannelSink;
   nsCOMPtr<nsIApplicationCache> mApplicationCache;
 
   nsCOMPtr<nsITimedChannel> mTimedChannel;
 
@@ -253,34 +239,35 @@ private:
   nsRefPtr<imgCacheEntry> mCacheEntry; /* we hold on to this to this so long as we have observers */
 
   void *mLoadId;
 
   imgCacheValidator *mValidator;
   nsCOMPtr<nsIAsyncVerifyRedirectCallback> mRedirectCallback;
   nsCOMPtr<nsIChannel> mNewRedirectChannel;
 
-  mozilla::Mutex mMutex;
-
   // The ID of the inner window origin, used for error reporting.
   uint64_t mInnerWindowId;
 
   // The CORS mode (defined in imgIRequest) this image was loaded with. By
   // default, imgIRequest::CORS_NONE.
   int32_t mCORSMode;
 
   // The Referrer Policy (defined in ReferrerPolicy.h) used for this image.
   ReferrerPolicy mReferrerPolicy;
 
   nsresult mImageErrorCode;
 
-  // Sometimes consumers want to do things before the image is ready. Let them,
-  // and apply the action when the image becomes available.
-  bool mDecodeRequested : 1;
+  mutable mozilla::Mutex mMutex;
 
+  // Member variables protected by mMutex. Note that *all* flags in our bitfield
+  // are protected by mMutex; if you're adding a new flag that isn'protected, it
+  // must not be a part of this bitfield.
+  nsRefPtr<ProgressTracker> mProgressTracker;
+  nsRefPtr<Image> mImage;
   bool mIsMultiPartChannel : 1;
   bool mGotData : 1;
   bool mIsInCache : 1;
-  bool mBlockingOnload : 1;
+  bool mDecodeRequested : 1;
   bool mNewPartPending : 1;
 };
 
 #endif
--- a/image/src/imgRequestProxy.cpp
+++ b/image/src/imgRequestProxy.cpp
@@ -219,18 +219,19 @@ nsresult imgRequestProxy::ChangeOwner(im
   // (see bug 601723).
   for (uint32_t i = 0; i < oldAnimationConsumers; i++)
     IncrementAnimationConsumers();
 
   GetOwner()->AddProxy(this);
 
   // If we were decoded, or if we'd previously requested a decode, request a
   // decode on the new image
-  if (wasDecoded || mDecodeRequested)
-    GetOwner()->StartDecoding();
+  if (wasDecoded || mDecodeRequested) {
+    StartDecoding();
+  }
 
   return NS_OK;
 }
 
 void imgRequestProxy::AddToLoadGroup()
 {
   NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
 
@@ -347,38 +348,48 @@ NS_IMETHODIMP imgRequestProxy::CancelAnd
 
   return NS_OK;
 }
 
 /* void startDecode (); */
 NS_IMETHODIMP
 imgRequestProxy::StartDecoding()
 {
-  if (!GetOwner())
-    return NS_ERROR_FAILURE;
-
   // Flag this, so we know to transfer the request if our owner changes
   mDecodeRequested = true;
 
-  // Forward the request
-  return GetOwner()->StartDecoding();
+  nsRefPtr<Image> image = GetImage();
+  if (image) {
+    return image->StartDecoding();
+  }
+
+  if (GetOwner()) {
+    GetOwner()->RequestDecode();
+  }
+
+  return NS_OK;
 }
 
 /* void requestDecode (); */
 NS_IMETHODIMP
 imgRequestProxy::RequestDecode()
 {
-  if (!GetOwner())
-    return NS_ERROR_FAILURE;
-
   // Flag this, so we know to transfer the request if our owner changes
   mDecodeRequested = true;
 
-  // Forward the request
-  return GetOwner()->RequestDecode();
+  nsRefPtr<Image> image = GetImage();
+  if (image) {
+    return image->RequestDecode();
+  }
+
+  if (GetOwner()) {
+    GetOwner()->RequestDecode();
+  }
+
+  return NS_OK;
 }
 
 
 /* void lockImage (); */
 NS_IMETHODIMP
 imgRequestProxy::LockImage()
 {
   mLockCount++;
@@ -489,24 +500,25 @@ NS_IMETHODIMP imgRequestProxy::GetImage(
 {
   NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
   // It's possible that our owner has an image but hasn't notified us of it -
   // that'll happen if we get Canceled before the owner instantiates its image
   // (because Canceling unregisters us as a listener on mOwner). If we're
   // in that situation, just grab the image off of mOwner.
   nsRefPtr<Image> image = GetImage();
   nsCOMPtr<imgIContainer> imageToReturn;
-  if (image)
+  if (image) {
     imageToReturn = do_QueryInterface(image);
+  }
   if (!imageToReturn && GetOwner()) {
     imageToReturn = GetOwner()->GetImage();
   }
-
-  if (!imageToReturn)
+  if (!imageToReturn) {
     return NS_ERROR_FAILURE;
+  }
 
   imageToReturn.swap(*aImage);
 
   return NS_OK;
 }
 
 /* readonly attribute unsigned long imageStatus; */
 NS_IMETHODIMP imgRequestProxy::GetImageStatus(uint32_t *aStatus)
--- a/image/src/imgRequestProxy.h
+++ b/image/src/imgRequestProxy.h
@@ -25,17 +25,16 @@
 { /* 20557898-1dd2-11b2-8f65-9c462ee2bc95 */         \
      0x20557898,                                     \
      0x1dd2,                                         \
      0x11b2,                                         \
     {0x8f, 0x65, 0x9c, 0x46, 0x2e, 0xe2, 0xbc, 0x95} \
 }
 
 class imgINotificationObserver;
-class imgRequestNotifyRunnable;
 class imgStatusNotifyRunnable;
 struct nsIntRect;
 class ProxyBehaviour;
 
 namespace mozilla {
 namespace image {
 class Image;
 class ImageURL;
@@ -128,17 +127,16 @@ public:
   virtual nsresult Clone(imgINotificationObserver* aObserver, imgRequestProxy** aClone);
   nsresult GetStaticRequest(imgRequestProxy** aReturn);
 
   nsresult GetURI(ImageURL **aURI);
 
 protected:
   friend class mozilla::image::ProgressTracker;
   friend class imgStatusNotifyRunnable;
-  friend class imgRequestNotifyRunnable;
 
   class imgCancelRunnable;
   friend class imgCancelRunnable;
 
   class imgCancelRunnable : public nsRunnable
   {
     public:
       imgCancelRunnable(imgRequestProxy* owner, nsresult status)
@@ -168,19 +166,20 @@ protected:
   // Return the ProgressTracker associated with mOwner and/or mImage. It may
   // live either on mOwner or mImage, depending on whether
   //   (a) we have an mOwner at all
   //   (b) whether mOwner has instantiated its image yet
   already_AddRefed<ProgressTracker> GetProgressTracker() const;
 
   nsITimedChannel* TimedChannel()
   {
-    if (!GetOwner())
+    if (!GetOwner()) {
       return nullptr;
-    return GetOwner()->mTimedChannel;
+    }
+    return GetOwner()->GetTimedChannel();
   }
 
   already_AddRefed<Image> GetImage() const;
   bool HasImage() const;
   imgRequest* GetOwner() const;
 
   nsresult PerformClone(imgINotificationObserver* aObserver,
                         imgRequestProxy* (aAllocFn)(imgRequestProxy*),
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -378,12 +378,20 @@ IsIncrementalBarrierNeededOnTenuredGCThi
     MOZ_ASSERT(thing);
     MOZ_ASSERT(!js::gc::IsInsideNursery(thing.asCell()));
     if (!rt->needsIncrementalBarrier())
         return false;
     JS::Zone *zone = JS::GetTenuredGCThingZone(thing.asCell());
     return JS::shadow::Zone::asShadowZone(zone)->needsIncrementalBarrier();
 }
 
+/*
+ * Create an object providing access to the garbage collector's internal notion
+ * of the current state of memory (both GC heap memory and GCthing-controlled
+ * malloc memory.
+ */
+extern JS_PUBLIC_API(JSObject *)
+NewMemoryInfoObject(JSContext *cx);
+
 } /* namespace gc */
 } /* namespace js */
 
 #endif /* js_HeapAPI_h */
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1490,16 +1490,73 @@ js::testingFunc_bailout(JSContext *cx, u
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // NOP when not in IonMonkey
     args.rval().setUndefined();
     return true;
 }
 
 bool
+js::testingFunc_inJit(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (!IsBaselineEnabled(cx)) {
+        JSString *error = JS_NewStringCopyZ(cx, "Baseline is disabled.");
+        if(!error)
+            return false;
+
+        args.rval().setString(error);
+        return true;
+    }
+
+    JSScript *script = cx->currentScript();
+    if (script && script->getWarmUpResetCount() >= 20) {
+        JSString *error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+        if (!error)
+            return false;
+
+        args.rval().setString(error);
+        return true;
+    }
+
+    args.rval().setBoolean(cx->currentlyRunningInJit());
+    return true;
+}
+
+bool
+js::testingFunc_inIon(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (!IsIonEnabled(cx)) {
+        JSString *error = JS_NewStringCopyZ(cx, "Ion is disabled.");
+        if (!error)
+            return false;
+
+        args.rval().setString(error);
+        return true;
+    }
+
+    JSScript *script = cx->currentScript();
+    if (script && script->getWarmUpResetCount() >= 20) {
+        JSString *error = JS_NewStringCopyZ(cx, "Compilation is being repeatedly prevented. Giving up.");
+        if (!error)
+            return false;
+
+        args.rval().setString(error);
+        return true;
+    }
+
+    // false when not in ionMonkey
+    args.rval().setBoolean(false);
+    return true;
+}
+
+bool
 js::testingFunc_assertFloat32(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // NOP when not in IonMonkey
     args.rval().setUndefined();
     return true;
 }
@@ -2711,16 +2768,30 @@ gc::ZealModeHelpText),
     JS_FN_HELP("getObjectMetadata", GetObjectMetadata, 1, 0,
 "getObjectMetadata(obj)",
 "  Get the metadata for an object."),
 
     JS_FN_HELP("bailout", testingFunc_bailout, 0, 0,
 "bailout()",
 "  Force a bailout out of ionmonkey (if running in ionmonkey)."),
 
+    JS_FN_HELP("inJit", testingFunc_inJit, 0, 0,
+"inJit()",
+"  Returns true when called within (jit-)compiled code. When jit compilation is disabled this\n"
+"  function returns an error string. This function returns false in all other cases.\n"
+"  Depending on truthiness, you should continue to wait for compilation to happen or stop execution.\n"),
+
+    JS_FN_HELP("inIon", testingFunc_inIon, 0, 0,
+"inIon()",
+"  Returns true when called within ion. When ion is disabled or when compilation is abnormally\n"
+"  slow to start, this function returns an error string. Otherwise, this function returns false.\n"
+"  This behaviour ensures that a falsy value means that we are not in ion, but expect a\n"
+"  compilation to occur in the future. Conversely, a truthy value means that we are either in\n"
+"  ion or that there is litle or no chance of ion ever compiling the current script."),
+
     JS_FN_HELP("assertJitStackInvariants", TestingFunc_assertJitStackInvariants, 0, 0,
 "assertJitStackInvariants()",
 "  Iterates the Jit stack and check that stack invariants hold."),
 
     JS_FN_HELP("setJitCompilerOption", SetJitCompilerOption, 2, 0,
 "setCompilerOption(<option>, <number>)",
 "  Set a compiler option indexed in JSCompileOption enum to a number.\n"),
 
--- a/js/src/builtin/TestingFunctions.h
+++ b/js/src/builtin/TestingFunctions.h
@@ -15,11 +15,17 @@ bool
 DefineTestingFunctions(JSContext *cx, HandleObject obj, bool fuzzingSafe);
 
 bool
 testingFunc_bailout(JSContext *cx, unsigned argc, Value *vp);
 
 bool
 testingFunc_assertFloat32(JSContext *cx, unsigned argc, Value *vp);
 
+bool
+testingFunc_inJit(JSContext *cx, unsigned argc, Value *vp);
+
+bool
+testingFunc_inIon(JSContext *cx, unsigned argc, Value *vp);
+
 } /* namespace js */
 
 #endif /* builtin_TestingFunctions_h */
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -1559,16 +1559,17 @@ case "$target" in
     esac
     if test "$COMPILE_ENVIRONMENT"; then
         MOZ_CHECK_HEADERS(sys/inttypes.h)
     fi
     AC_DEFINE(NSCAP_DISABLE_DEBUG_PTR_TYPES)
     ;;
 
 *-darwin*)
+    AC_DEFINE(XP_DARWIN)
     MKSHLIB='$(CXX) $(CXXFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@'
     MKCSHLIB='$(CC) $(CFLAGS) $(DSO_PIC_CFLAGS) $(DSO_LDOPTS) -o $@'
     MOZ_OPTIMIZE_FLAGS="-O3 -fno-stack-protector"
     CFLAGS="$CFLAGS -fno-common"
     CXXFLAGS="$CXXFLAGS -fno-common"
     DLL_SUFFIX=".dylib"
     DSO_LDOPTS=''
     STRIP="$STRIP -x -S"
--- a/js/src/devtools/gc-ubench/harness.js
+++ b/js/src/devtools/gc-ubench/harness.js
@@ -1,128 +1,316 @@
 // Per-frame time sampling infra. Also GC'd: hopefully will not perturb things too badly.
 var numSamples = 500;
 var delays = new Array(numSamples);
+var gcs = new Array(numSamples);
+var minorGCs = new Array(numSamples);
+var gcBytes = new Array(numSamples);
+var mallocBytes = new Array(numSamples);
 var sampleIndex = 0;
 var sampleTime = 16; // ms
 var gHistogram = new Map(); // {ms: count}
 
 // Draw state.
 var stopped = 0;
 var start;
 var prev;
+var latencyGraph;
+var memoryGraph;
 var ctx;
+var memoryCtx;
 
 // Current test state.
 var activeTest = undefined;
 var testDuration = undefined; // ms
 var testState = 'idle';  // One of 'idle' or 'running'.
 var testStart = undefined; // ms
 var testQueue = [];
 
 // Global defaults
 var globalDefaultGarbageTotal = "8M";
 var globalDefaultGarbagePerFrame = "8K";
 
-function xpos(index)
+function Graph(ctx) {
+    this.ctx = ctx;
+
+    var { width, height } = ctx.canvas;
+    this.layout = {
+        xAxisLabel_Y: height - 20,
+    };
+}
+
+Graph.prototype.xpos = index => index * 2;
+
+Graph.prototype.clear = function () {
+    var { width, height } = this.ctx.canvas;
+    this.ctx.clearRect(0, 0, width, height);
+};
+
+Graph.prototype.drawScale = function (delay)
 {
-    return index * 2;
+    this.drawHBar(delay, `${delay}ms`, 'rgb(150,150,150)');
+}
+
+Graph.prototype.draw60fps = function () {
+    this.drawHBar(1000/60, '60fps', '#00cf61', 25);
+}
+
+Graph.prototype.draw30fps = function () {
+    this.drawHBar(1000/30, '30fps', '#cf0061', 25);
 }
 
-function ypos(delay)
+Graph.prototype.drawAxisLabels = function (x_label, y_label)
 {
-    var r = 525 - Math.log(delay) * 64;
+    var ctx = this.ctx;
+    var { width, height } = ctx.canvas;
+
+    ctx.fillText(x_label, width / 2, this.layout.xAxisLabel_Y);
+
+    ctx.save();
+    ctx.rotate(Math.PI/2);
+    var start = height / 2 - ctx.measureText(y_label).width / 2;
+    ctx.fillText(y_label, start, -width+20);
+    ctx.restore();
+}
+
+Graph.prototype.drawFrame = function () {
+    var ctx = this.ctx;
+    var { width, height } = ctx.canvas;
+
+    // Draw frame to show size
+    ctx.strokeStyle = 'rgb(0,0,0)';
+    ctx.fillStyle = 'rgb(0,0,0)';
+    ctx.beginPath();
+    ctx.moveTo(0, 0);
+    ctx.lineTo(width, 0);
+    ctx.lineTo(width, height);
+    ctx.lineTo(0, height);
+    ctx.closePath();
+    ctx.stroke();
+}
+
+function LatencyGraph(ctx) {
+    Graph.call(this, ctx);
+    console.log(this.ctx);
+}
+
+LatencyGraph.prototype = Object.create(Graph.prototype);
+
+Object.defineProperty(LatencyGraph.prototype, 'constructor', {
+    enumerable: false,
+    value: LatencyGraph });
+
+LatencyGraph.prototype.ypos = function (delay) {
+    var { height } = this.ctx.canvas;
+
+    var r = height + 100 - Math.log(delay) * 64;
     if (r < 5) return 5;
     return r;
 }
 
-function drawHBar(delay, color, label)
+LatencyGraph.prototype.drawHBar = function (delay, label, color='rgb(0,0,0)', label_offset=0)
 {
+    var ctx = this.ctx;
+
     ctx.fillStyle = color;
     ctx.strokeStyle = color;
-    ctx.fillText(label, xpos(numSamples) + 4, ypos(delay) + 3);
+    ctx.fillText(label, this.xpos(numSamples) + 4 + label_offset, this.ypos(delay) + 3);
 
     ctx.beginPath();
-    ctx.moveTo(xpos(0), ypos(delay));
-    ctx.lineTo(xpos(numSamples), ypos(delay));
+    ctx.moveTo(this.xpos(0), this.ypos(delay));
+    ctx.lineTo(this.xpos(numSamples) + label_offset, this.ypos(delay));
     ctx.stroke();
     ctx.strokeStyle = 'rgb(0,0,0)';
     ctx.fillStyle = 'rgb(0,0,0)';
 }
 
-function drawScale(delay)
-{
-    drawHBar(delay, 'rgb(150,150,150)', `${delay}ms`);
-}
-
-function draw60fps() {
-    drawHBar(1000/60, '#00cf61', '60fps');
-}
-
-function draw30fps() {
-    drawHBar(1000/30, '#cf0061', '30fps');
-}
+LatencyGraph.prototype.draw = function () {
+    var ctx = this.ctx;
 
-function drawGraph()
-{
-    ctx.clearRect(0, 0, 1100, 550);
+    this.clear();
+    this.drawFrame();
 
-    drawScale(10);
-    draw60fps();
-    drawScale(20);
-    drawScale(30);
-    draw30fps();
-    drawScale(50);
-    drawScale(100);
-    drawScale(200);
-    drawScale(400);
-    drawScale(800);
+    for (var delay of [ 10, 20, 30, 50, 100, 200, 400, 800 ])
+        this.drawScale(delay);
+    this.draw60fps();
+    this.draw30fps();
 
     var worst = 0, worstpos = 0;
     ctx.beginPath();
     for (var i = 0; i < numSamples; i++) {
-        ctx.lineTo(xpos(i), ypos(delays[i]));
+        ctx.lineTo(this.xpos(i), this.ypos(delays[i]));
         if (delays[i] >= worst) {
             worst = delays[i];
             worstpos = i;
         }
     }
     ctx.stroke();
 
+    // Draw vertical lines marking minor and major GCs
+    if (performance.mozMemory) {
+        var { width, height } = ctx.canvas;
+
+        ctx.strokeStyle = 'rgb(255,100,0)';
+        var idx = sampleIndex % numSamples;
+        var gcCount = gcs[idx];
+        for (var i = 0; i < numSamples; i++) {
+            idx = (sampleIndex + i) % numSamples;
+            if (gcCount < gcs[idx]) {
+                ctx.beginPath();
+                ctx.moveTo(this.xpos(idx), 0);
+                ctx.lineTo(this.xpos(idx), this.layout.xAxisLabel_Y);
+                ctx.stroke();
+            }
+            gcCount = gcs[idx];
+        }
+
+        ctx.strokeStyle = 'rgb(0,255,100)';
+        idx = sampleIndex % numSamples;
+        gcCount = gcs[idx];
+        for (var i = 0; i < numSamples; i++) {
+            idx = (sampleIndex + i) % numSamples;
+            if (gcCount < minorGCs[idx]) {
+                ctx.beginPath();
+                ctx.moveTo(this.xpos(idx), 0);
+                ctx.lineTo(this.xpos(idx), 20);
+                ctx.stroke();
+            }
+            gcCount = minorGCs[idx];
+        }
+    }
+
     ctx.fillStyle = 'rgb(255,0,0)';
     if (worst)
-        ctx.fillText(''+worst.toFixed(2)+'ms', xpos(worstpos) - 10, ypos(worst) - 14);
+        ctx.fillText(`${worst.toFixed(2)}ms`, this.xpos(worstpos) - 10, this.ypos(worst) - 14);
+
+    // Mark and label the slowest frame
+    ctx.beginPath();
+    var where = sampleIndex % numSamples;
+    ctx.arc(this.xpos(where), this.ypos(delays[where]), 5, 0, Math.PI*2, true);
+    ctx.fill();
+    ctx.fillStyle = 'rgb(0,0,0)';
+
+    this.drawAxisLabels('Time', 'Pause between frames (log scale)');
+}
+
+function MemoryGraph(ctx) {
+    Graph.call(this, ctx);
+    this.worstEver = this.bestEver = performance.mozMemory.zone.gcBytes;
+    this.limit = Math.max(this.worstEver, performance.mozMemory.zone.gcAllocTrigger);
+}
+
+MemoryGraph.prototype = Object.create(Graph.prototype);
+
+Object.defineProperty(MemoryGraph.prototype, 'constructor', {
+    enumerable: false,
+    value: MemoryGraph });
+
+MemoryGraph.prototype.ypos = function (size) {
+    var { height } = this.ctx.canvas;
+
+    var range = this.limit - this.bestEver;
+    var percent = (size - this.bestEver) / range;
+
+    return (1 - percent) * height * 0.9 + 20;
+}
+
+MemoryGraph.prototype.drawHBar = function (size, label, color='rgb(150,150,150)')
+{
+    var ctx = this.ctx;
+
+    var y = this.ypos(size);
+
+    ctx.fillStyle = color;
+    ctx.strokeStyle = color;
+    ctx.fillText(label, this.xpos(numSamples) + 4, y + 3);
+
+    ctx.beginPath();
+    ctx.moveTo(this.xpos(0), y);
+    ctx.lineTo(this.xpos(numSamples), y);
+    ctx.stroke();
+    ctx.strokeStyle = 'rgb(0,0,0)';
+    ctx.fillStyle = 'rgb(0,0,0)';
+}
+
+function format_gcBytes(bytes) {
+    if (bytes < 4000)
+        return `${bytes} bytes`;
+    else if (bytes < 4e6)
+        return `${(bytes / 1024).toFixed(2)} KB`;
+    else if (bytes < 4e9)
+        return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
+    else
+        return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`;
+};
+
+MemoryGraph.prototype.draw = function () {
+    var ctx = this.ctx;
+
+    this.clear();
+    this.drawFrame();
+
+    var worst = 0, worstpos = 0;
+    for (var i = 0; i < numSamples; i++) {
+        if (gcBytes[i] >= worst) {
+            worst = gcBytes[i];
+            worstpos = i;
+        }
+        if (gcBytes[i] < this.bestEver) {
+            this.bestEver = gcBytes[i];
+        }
+    }
+
+    if (this.worstEver < worst) {
+        this.worstEver = worst;
+        this.limit = Math.max(this.worstEver, performance.mozMemory.zone.gcAllocTrigger);
+    }
+
+    this.drawHBar(this.bestEver, `${format_gcBytes(this.bestEver)} min`, '#00cf61');
+    this.drawHBar(this.worstEver, `${format_gcBytes(this.worstEver)} max`, '#cc1111');
+    this.drawHBar(performance.mozMemory.zone.gcAllocTrigger, `${format_gcBytes(performance.mozMemory.zone.gcAllocTrigger)} trigger`, '#cc11cc');
+
+    ctx.fillStyle = 'rgb(255,0,0)';
+    if (worst)
+        ctx.fillText(format_gcBytes(worst), this.xpos(worstpos) - 10, this.ypos(worst) - 14);
 
     ctx.beginPath();
     var where = sampleIndex % numSamples;
-    ctx.arc(xpos(where), ypos(delays[where]), 5, 0, Math.PI*2, true);
+    ctx.arc(this.xpos(where), this.ypos(gcBytes[where]), 5, 0, Math.PI*2, true);
     ctx.fill();
-    ctx.fillStyle = 'rgb(0,0,0)';
 
-    ctx.fillText('Time', 550, 420);
-    ctx.save();
-    ctx.rotate(Math.PI/2);
-    ctx.fillText('Pause between frames (log scale)', 150, -1060);
-    ctx.restore();
+    ctx.beginPath();
+    for (var i = 0; i < numSamples; i++) {
+        if (i == (sampleIndex + 1) % numSamples)
+            ctx.moveTo(this.xpos(i), this.ypos(gcBytes[i]));
+        else
+            ctx.lineTo(this.xpos(i), this.ypos(gcBytes[i]));
+        if (i == where)
+            ctx.stroke();
+    }
+    ctx.stroke();
+
+    this.drawAxisLabels('Time', 'Heap Memory Usage');
 }
 
 function stopstart()
 {
     if (stopped) {
         window.requestAnimationFrame(handler);
         prev = performance.now();
         start += prev - stopped;
         document.getElementById('stop').value = 'Pause';
         stopped = 0;
     } else {
         document.getElementById('stop').value = 'Resume';
         stopped = performance.now();
     }
 }
 
+var previous = 0;
 function handler(timestamp)
 {
     if (stopped)
         return;
 
     if (testState === 'running' && (timestamp - testStart) > testDuration)
         end_test(timestamp);
 
@@ -139,22 +327,52 @@ function handler(timestamp)
     // a 16.66[666] target with adequate accuracy.
     update_histogram(gHistogram, Math.round(delay * 100));
 
     var t = timestamp - start;
     var newIndex = Math.round(t / sampleTime);
     while (sampleIndex < newIndex) {
         sampleIndex++;
         delays[sampleIndex % numSamples] = delay;
+        if (performance.mozMemory) {
+            gcBytes[sampleIndex % numSamples] = performance.mozMemory.gcBytes;
+            gcs[sampleIndex % numSamples] = performance.mozMemory.gcNumber;
+            minorGCs[sampleIndex % numSamples] = performance.mozMemory.minorGCCount;
+        }
     }
 
-    drawGraph();
+    latencyGraph.draw();
+    if (memoryGraph)
+        memoryGraph.draw();
     window.requestAnimationFrame(handler);
 }
 
+function summarize(arr) {
+    if (arr.length == 0)
+        return [];
+
+    var result = [];
+    var run_start = 0;
+    var prev = arr[0];
+    for (var i = 1; i <= arr.length; i++) {
+        if (i == arr.length || arr[i] != prev) {
+            if (i == run_start + 1) {
+                result.push(arr[i]);
+            } else {
+                result.push(prev + " x " + (i - run_start));
+            }
+            run_start = i;
+        }
+        if (i != arr.length)
+            prev = arr[i];
+    }
+
+    return result;
+}
+
 function update_histogram(histogram, delay)
 {
     var current = histogram.has(delay) ? histogram.get(delay) : 0;
     histogram.set(delay, ++current);
 }
 
 function reset_draw_state()
 {
@@ -197,17 +415,22 @@ function onload()
     // Polyfill rAF.
     var requestAnimationFrame =
         window.requestAnimationFrame || window.mozRequestAnimationFrame ||
         window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
     window.requestAnimationFrame = requestAnimationFrame;
 
     // Acquire our canvas.
     var canvas = document.getElementById('graph');
-    ctx = canvas.getContext('2d');
+    latencyGraph = new LatencyGraph(canvas.getContext('2d'));
+
+    if (performance.mozMemory) {
+        var canvas = document.getElementById('memgraph');
+        memoryGraph = new MemoryGraph(canvas.getContext('2d'));
+    }
 
     // Start drawing.
     reset_draw_state();
     window.requestAnimationFrame(handler);
 }
 
 function run_one_test()
 {
@@ -327,19 +550,19 @@ function reload_active_test()
 
 function change_active_test(new_test_name)
 {
     if (activeTest)
         activeTest.unload();
     activeTest = tests.get(new_test_name);
 
     if (!activeTest.garbagePerFrame)
-        activeTest.garbagePerFrame = activeTest.defaultGarbagePerFrame || globalDefaultGarbagePerFrame;
+        activeTest.garbagePerFrame = parse_units(activeTest.defaultGarbagePerFrame || globalDefaultGarbagePerFrame);
     if (!activeTest.garbageTotal)
-        activeTest.garbageTotal = activeTest.defaultGarbageTotal || globalDefaultGarbageTotal;
+        activeTest.garbageTotal = parse_units(activeTest.defaultGarbageTotal || globalDefaultGarbageTotal);
 
     document.getElementById("garbage-per-frame").value = format_units(activeTest.garbagePerFrame);
     document.getElementById("garbage-total").value = format_units(activeTest.garbageTotal);
 
     activeTest.load(activeTest.garbageTotal);
 }
 
 function duration_changed()
--- a/js/src/devtools/gc-ubench/index.html
+++ b/js/src/devtools/gc-ubench/index.html
@@ -22,17 +22,18 @@
   <script src="benchmarks/expandoEvents.js"></script>
 
   <script src="harness.js"></script>
 
 </head>
 
 <body onload="onload()" onunload="onunload()">
 
-<canvas id="graph" width="1080" height="550" style="padding-left:10px"></canvas>
+<canvas id="graph" width="1080" height="400" style="padding-left:10px"></canvas>
+<canvas id="memgraph" width="1080" height="400" style="padding-left:10px"></canvas>
 
 <div>
     <input type="button" id="stop" value="Pause" onclick="stopstart()"></input>
 </div>
 
 <div>
     Duration: <input type="text" id="test-duration" size="3" value="8" onchange="duration_changed()"></input>s
     <input type="button" id="test-one" value="Run Test" onclick="run_one_test()"></input>
@@ -44,16 +45,22 @@
     <select id="test-selection" required onchange="test_changed()"></select>
 </div>
 
 <div>
     &nbsp;&nbsp;&nbsp;&nbsp;Time remaining: <span id="test-progress">(not running)</span>
 </div
 
 <div>
+    &nbsp;&nbsp;&nbsp;&nbsp;60 fps: <span id="pct60">n/a</span>
+    &nbsp;&nbsp;&nbsp;&nbsp;45 fps: <span id="pct45">n/a</span>
+    &nbsp;&nbsp;&nbsp;&nbsp;30 fps: <span id="pct30">n/a</span>
+</div
+
+<div>
     &nbsp;&nbsp;&nbsp;&nbsp;Garbage items per frame:
     <input type="text" id="garbage-per-frame" size="5" value="8K"
            onchange="garbage_per_frame_changed()"></input>
 </div>
 <div>
     &nbsp;&nbsp;&nbsp;&nbsp;Garbage piles:
     <input type="text" id="garbage-total" size="5" value="8M"
            onchange="garbage_total_changed()"></input>
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -2218,20 +2218,20 @@ BytecodeEmitter::emitNewInit(JSProtoKey 
     updateDepth(offset);
     checkTypeSet(JSOP_NEWINIT);
     return true;
 }
 
 static bool
 IteratorResultShape(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned *shape)
 {
-    MOZ_ASSERT(bce->script->compileAndGo());
-
     RootedPlainObject obj(cx);
-    gc::AllocKind kind = GuessObjectGCKind(2);
+    // No need to do any guessing for the object kind, since we know exactly how
+    // many properties we plan to have.
+    gc::AllocKind kind = gc::GetGCObjectKind(2);
     obj = NewBuiltinClassInstance<PlainObject>(cx, kind);
     if (!obj)
         return false;
 
     Rooted<jsid> value_id(cx, AtomToId(cx->names().value));
     Rooted<jsid> done_id(cx, AtomToId(cx->names().done));
     if (!NativeDefineProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr,
                               JSPROP_ENUMERATE))
@@ -2251,24 +2251,20 @@ IteratorResultShape(ExclusiveContext *cx
     *shape = bce->objectList.add(objbox);
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitPrepareIteratorResult()
 {
-    if (script->compileAndGo()) {
-        unsigned shape;
-        if (!IteratorResultShape(cx, this, &shape))
-            return false;
-        return emitIndex32(JSOP_NEWOBJECT, shape);
-    }
-
-    return emitNewInit(JSProto_Object);
+    unsigned shape;
+    if (!IteratorResultShape(cx, this, &shape))
+        return false;
+    return emitIndex32(JSOP_NEWOBJECT, shape);
 }
 
 bool
 BytecodeEmitter::emitFinishIteratorResult(bool done)
 {
     jsatomid value_id;
     if (!makeAtomIndex(cx->names().value, &value_id))
         return false;
@@ -6580,22 +6576,22 @@ BytecodeEmitter::emitObject(ParseNode *p
     if (!emitNewInit(JSProto_Object))
         return false;
 
     /*
      * Try to construct the shape of the object as we go, so we can emit a
      * JSOP_NEWOBJECT with the final shape instead.
      */
     RootedPlainObject obj(cx);
-    if (script->compileAndGo()) {
-        gc::AllocKind kind = GuessObjectGCKind(pn->pn_count);
-        obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject);
-        if (!obj)
-            return false;
-    }
+    // No need to do any guessing for the object kind, since we know exactly
+    // how many properties we plan to have.
+    gc::AllocKind kind = gc::GetGCObjectKind(pn->pn_count);
+    obj = NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject);
+    if (!obj)
+        return false;
 
     if (!emitPropertyList(pn, &obj, ObjectLiteral))
         return false;
 
     if (obj) {
         /*
          * The object survived and has a predictable shape: update the original
          * bytecode.
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -162,30 +162,16 @@ GCRuntime::tryNewNurseryObject(JSContext
             JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
             MOZ_ASSERT(obj);
             return obj;
         }
     }
     return nullptr;
 }
 
-typedef mozilla::UniquePtr<HeapSlot, JS::FreePolicy> UniqueSlots;
-
-static inline UniqueSlots
-MakeSlotArray(ExclusiveContext *cx, size_t count)
-{
-    HeapSlot *slots = nullptr;
-    if (count) {
-        slots = cx->zone()->pod_malloc<HeapSlot>(count);
-        if (slots)
-            Debug_SetSlotRangeToCrashOnTouch(slots, count);
-    }
-    return UniqueSlots(slots);
-}
-
 template <AllowGC allowGC>
 JSObject *
 GCRuntime::tryNewTenuredObject(ExclusiveContext *cx, AllocKind kind, size_t thingSize,
                                size_t nDynamicSlots)
 {
     HeapSlot *slots = nullptr;
     if (nDynamicSlots) {
         slots = cx->zone()->pod_malloc<HeapSlot>(nDynamicSlots);
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -747,16 +747,17 @@ class GCRuntime
     void enableCompactingGC();
     bool isCompactingGCEnabled();
 
     void setGrayRootsTracer(JSTraceDataOp traceOp, void *data);
     bool addBlackRootsTracer(JSTraceDataOp traceOp, void *data);
     void removeBlackRootsTracer(JSTraceDataOp traceOp, void *data);
 
     void setMaxMallocBytes(size_t value);
+    int32_t getMallocBytes() const { return mallocBytesUntilGC; }
     void resetMallocBytes();
     bool isTooMuchMalloc() const { return mallocBytesUntilGC <= 0; }
     void updateMallocCounter(JS::Zone *zone, size_t nbytes);
     void onTooMuchMalloc();
 
     void setGCCallback(JSGCCallback callback, void *data);
     bool addFinalizeCallback(JSFinalizeCallback callback, void *data);
     void removeFinalizeCallback(JSFinalizeCallback func);
@@ -776,16 +777,22 @@ class GCRuntime
     }
 
     JS::Zone *getCurrentZoneGroup() { return currentZoneGroup; }
     void setFoundBlackGrayEdges() { foundBlackGrayEdges = true; }
 
     uint64_t gcNumber() { return number; }
     void incGcNumber() { ++number; }
 
+    uint64_t minorGCCount() { return minorGCNumber; }
+    void incMinorGcNumber() { ++minorGCNumber; }
+
+    uint64_t majorGCCount() { return majorGCNumber; }
+    void incMajorGcNumber() { ++majorGCNumber; }
+
     bool isIncrementalGc() { return isIncremental; }
     bool isFullGc() { return isFull; }
 
     bool shouldCleanUpEverything() { return cleanUpEverything; }
 
     bool areGrayBitsValid() { return grayBitsValid; }
     void setGrayBitsInvalid() { grayBitsValid = false; }
 
@@ -1026,17 +1033,16 @@ class GCRuntime
     bool cleanUpEverything;
 
     // Gray marking must be done after all black marking is complete. However,
     // we do not have write barriers on XPConnect roots. Therefore, XPConnect
     // roots must be accumulated in the first slice of incremental GC. We
     // accumulate these roots in each zone's gcGrayRoots vector and then mark
     // them later, after black marking is complete for each compartment. This
     // accumulation can fail, but in that case we switch to non-incremental GC.
-    friend class js::GCMarker;
     enum class GrayBufferState {
         Unused,
         Okay,
         Failed
     };
     GrayBufferState grayBufferState;
     bool hasBufferedGrayRoots() const { return grayBufferState == GrayBufferState::Okay; }
 
@@ -1057,16 +1063,19 @@ class GCRuntime
 
     mozilla::Atomic<JS::gcreason::Reason, mozilla::Relaxed> majorGCTriggerReason;
 
     JS::gcreason::Reason minorGCTriggerReason;
 
     /* Perform full GC if rt->keepAtoms() becomes false. */
     bool fullGCForAtomsRequested_;
 
+    /* Incremented at the start of every minor GC. */
+    uint64_t minorGCNumber;
+
     /* Incremented at the start of every major GC. */
     uint64_t majorGCNumber;
 
     /* The major GC number at which to release observed type information. */
     uint64_t jitReleaseNumber;
 
     /* Incremented on every GC slice. */
     uint64_t number;
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -189,19 +189,23 @@ CheckMarkedThing(JSTracer *trc, T **thin
     MOZ_ASSERT_IF(!MovingTracer::IsMovingTracer(trc), CurrentThreadCanAccessRuntime(rt));
 
     MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime());
     MOZ_ASSERT(trc->hasTracingDetails());
 
     MOZ_ASSERT(thing->isAligned());
     MOZ_ASSERT(MapTypeToTraceKind<T>::kind == GetGCThingTraceKind(thing));
 
-    bool isGcMarkingTracer = IsMarkingGray(trc) || IsMarkingTracer(trc);
+    /*
+     * Do not check IsMarkingTracer directly -- it should only be used in paths
+     * where we cannot be the gray buffering tracer.
+     */
+    bool isGcMarkingTracer = (trc->callback == nullptr);
 
-    MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer);
+    MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer || IsBufferingGrayRoots(trc));
 
     if (isGcMarkingTracer) {
         GCMarker *gcMarker = static_cast<GCMarker *>(trc);
         MOZ_ASSERT_IF(gcMarker->shouldCheckCompartments(),
                       zone->isCollecting() || rt->isAtomsZone(zone));
 
         MOZ_ASSERT_IF(gcMarker->markColor() == GRAY,
                       !zone->isGCMarkingBlack() || rt->isAtomsZone(zone));
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -802,16 +802,18 @@ js::Nursery::collect(JSRuntime *rt, JS::
          * storebuffer even when the nursery is disabled or empty. It's not
          * safe to keep these entries as they may refer to tenured cells which
          * may be freed after this point.
          */
         sb.clear();
         return;
     }
 
+    rt->gc.incMinorGcNumber();
+
     rt->gc.stats.count(gcstats::STAT_MINOR_GC);
 
     TraceMinorGCStart();
 
     TIME_START(total);
 
     AutoStopVerifyingBarriers av(rt, false);
 
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -413,17 +413,16 @@ js::gc::MarkPersistentRootedChains(JSTra
 
 void
 js::gc::GCRuntime::markRuntime(JSTracer *trc,
                                TraceOrMarkRuntime traceOrMark,
                                TraceRootsOrUsedSaved rootsSource)
 {
     gcstats::AutoPhase ap(stats, gcstats::PHASE_MARK_ROOTS);
 
-    MOZ_ASSERT(trc->callback != GCMarker::GrayCallback);
     MOZ_ASSERT(traceOrMark == TraceRuntime || traceOrMark == MarkRuntime);
     MOZ_ASSERT(rootsSource == TraceRoots || rootsSource == UseSavedRoots);
 
     MOZ_ASSERT(!rt->mainThread.suppressGC);
 
     if (traceOrMark == MarkRuntime) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_MARK_CCWS);
 
@@ -557,34 +556,80 @@ void
 js::gc::GCRuntime::bufferGrayRoots()
 {
     // Precondition: the state has been reset to "unused" after the last GC
     //               and the zone's buffers have been cleared.
     MOZ_ASSERT(grayBufferState == GrayBufferState::Unused);
     for (GCZonesIter zone(rt); !zone.done(); zone.next())
         MOZ_ASSERT(zone->gcGrayRoots.empty());
 
-    // Transform the GCMarker into an unholy CallbackTracer doppleganger.
-    MOZ_ASSERT(!IsMarkingGray(&marker));
-    MOZ_ASSERT(IsMarkingTracer(&marker));
-    MOZ_ASSERT(!marker.callback);
-    MOZ_ASSERT(!marker.bufferingGrayRootsFailed);
-    marker.callback = GCMarker::GrayCallback;
-    MOZ_ASSERT(IsMarkingGray(&marker));
 
+    BufferGrayRootsTracer grayBufferer(rt);
     if (JSTraceDataOp op = grayRootTracer.op)
-        (*op)(&marker, grayRootTracer.data);
+        (*op)(&grayBufferer, grayRootTracer.data);
 
     // Propagate the failure flag from the marker to the runtime.
-    if (marker.bufferingGrayRootsFailed) {
+    if (grayBufferer.failed()) {
       grayBufferState = GrayBufferState::Failed;
       resetBufferedGrayRoots();
     } else {
       grayBufferState = GrayBufferState::Okay;
     }
+}
 
-    // Restore the GCMarker to its former correctness.
-    MOZ_ASSERT(IsMarkingGray(&marker));
-    marker.bufferingGrayRootsFailed = false;
-    marker.callback = nullptr;
-    MOZ_ASSERT(!IsMarkingGray(&marker));
-    MOZ_ASSERT(IsMarkingTracer(&marker));
+void
+BufferGrayRootsTracer::appendGrayRoot(void *thing, JSGCTraceKind kind)
+{
+    MOZ_ASSERT(runtime()->isHeapBusy());
+
+    if (bufferingGrayRootsFailed)
+        return;
+
+    GrayRoot root(thing, kind);
+#ifdef DEBUG
+    root.debugPrinter = debugPrinter();
+    root.debugPrintArg = debugPrintArg();
+    root.debugPrintIndex = debugPrintIndex();
+#endif
+
+    Zone *zone = TenuredCell::fromPointer(thing)->zone();
+    if (zone->isCollecting()) {
+        // See the comment on SetMaybeAliveFlag to see why we only do this for
+        // objects and scripts. We rely on gray root buffering for this to work,
+        // but we only need to worry about uncollected dead compartments during
+        // incremental GCs (when we do gray root buffering).
+        switch (kind) {
+          case JSTRACE_OBJECT:
+            static_cast<JSObject *>(thing)->compartment()->maybeAlive = true;
+            break;
+          case JSTRACE_SCRIPT:
+            static_cast<JSScript *>(thing)->compartment()->maybeAlive = true;
+            break;
+          default:
+            break;
+        }
+        if (!zone->gcGrayRoots.append(root))
+            bufferingGrayRootsFailed = true;
+    }
 }
+
+void
+GCRuntime::markBufferedGrayRoots(JS::Zone *zone)
+{
+    MOZ_ASSERT(grayBufferState == GrayBufferState::Okay);
+    MOZ_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting());
+
+    for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) {
+#ifdef DEBUG
+        marker.setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex);
+#endif
+        MarkKind(&marker, &elem->thing, elem->kind);
+    }
+}
+
+void
+GCRuntime::resetBufferedGrayRoots() const
+{
+    MOZ_ASSERT(grayBufferState != GrayBufferState::Okay,
+               "Do not clear the gray buffers unless we are Failed or becoming Unused");
+    for (GCZonesIter zone(rt); !zone.done(); zone.next())
+        zone->gcGrayRoots.clearAndFree();
+}
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -508,17 +508,16 @@ MarkStack::sizeOfExcludingThis(mozilla::
 }
 
 /*
  * DoNotTraceWeakMaps: the GC is recomputing the liveness of WeakMap entries,
  * so we delay visting entries.
  */
 GCMarker::GCMarker(JSRuntime *rt)
   : JSTracer(rt, nullptr, DoNotTraceWeakMaps),
-    bufferingGrayRootsFailed(false),
     stack(size_t(-1)),
     color(BLACK),
     unmarkedArenaStackTop(nullptr),
     markLaterArenas(0),
     started(false),
     strictCompartmentChecking(false)
 {
 }
@@ -639,91 +638,22 @@ void
 GCMarker::checkZone(void *p)
 {
     MOZ_ASSERT(started);
     DebugOnly<Cell *> cell = static_cast<Cell *>(p);
     MOZ_ASSERT_IF(cell->isTenured(), cell->asTenured().zone()->isCollecting());
 }
 #endif
 
-void
-GCRuntime::resetBufferedGrayRoots() const
-{
-    MOZ_ASSERT(grayBufferState != GrayBufferState::Okay,
-               "Do not clear the gray buffers unless we are Failed or becoming Unused");
-    for (GCZonesIter zone(rt); !zone.done(); zone.next())
-        zone->gcGrayRoots.clearAndFree();
-}
-
-void
-GCRuntime::markBufferedGrayRoots(JS::Zone *zone)
-{
-    MOZ_ASSERT(grayBufferState == GrayBufferState::Okay);
-    MOZ_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting());
-
-    for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) {
-#ifdef DEBUG
-        marker.setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex);
-#endif
-        MarkKind(&marker, &elem->thing, elem->kind);
-    }
-}
-
-void
-GCMarker::appendGrayRoot(void *thing, JSGCTraceKind kind)
-{
-    MOZ_ASSERT(started);
-
-    if (bufferingGrayRootsFailed)
-        return;
-
-    GrayRoot root(thing, kind);
-#ifdef DEBUG
-    root.debugPrinter = debugPrinter();
-    root.debugPrintArg = debugPrintArg();
-    root.debugPrintIndex = debugPrintIndex();
-#endif
-
-    Zone *zone = TenuredCell::fromPointer(thing)->zone();
-    if (zone->isCollecting()) {
-        // See the comment on SetMaybeAliveFlag to see why we only do this for
-        // objects and scripts. We rely on gray root buffering for this to work,
-        // but we only need to worry about uncollected dead compartments during
-        // incremental GCs (when we do gray root buffering).
-        switch (kind) {
-          case JSTRACE_OBJECT:
-            static_cast<JSObject *>(thing)->compartment()->maybeAlive = true;
-            break;
-          case JSTRACE_SCRIPT:
-            static_cast<JSScript *>(thing)->compartment()->maybeAlive = true;
-            break;
-          default:
-            break;
-        }
-        if (!zone->gcGrayRoots.append(root))
-            bufferingGrayRootsFailed = true;
-    }
-}
-
-void
-GCMarker::GrayCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind)
-{
-    MOZ_ASSERT(thingp);
-    MOZ_ASSERT(*thingp);
-    GCMarker *gcmarker = static_cast<GCMarker *>(trc);
-    gcmarker->appendGrayRoot(*thingp, kind);
-}
-
 size_t
 GCMarker::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
     size_t size = stack.sizeOfExcludingThis(mallocSizeOf);
     for (ZonesIter zone(runtime(), WithAtoms); !zone.done(); zone.next())
         size += zone->gcGrayRoots.sizeOfExcludingThis(mallocSizeOf);
     return size;
 }
 
 void
 js::SetMarkStackLimit(JSRuntime *rt, size_t limit)
 {
     rt->gc.setMarkStackLimit(limit);
 }
-
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -196,21 +196,16 @@ class GCMarker : public JSTracer
     }
 
     bool isDrained() {
         return isMarkStackEmpty() && !unmarkedArenaStackTop;
     }
 
     bool drainMarkStack(SliceBudget &budget);
 
-    /* Set to false if we OOM while buffering gray roots. */
-    bool bufferingGrayRootsFailed;
-
-    static void GrayCallback(JSTracer *trc, void **thing, JSGCTraceKind kind);
-
     void setGCMode(JSGCMode mode) { stack.setGCMode(mode); }
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
 #ifdef DEBUG
     bool shouldCheckCompartments() { return strictCompartmentChecking; }
 #endif
 
@@ -325,33 +320,54 @@ class GCMarker : public JSTracer
 
     /*
      * If this is true, all marked objects must belong to a compartment being
      * GCed. This is used to look for compartment bugs.
      */
     mozilla::DebugOnly<bool> strictCompartmentChecking;
 };
 
+// Append traced things to a buffer on the zone for use later in the GC.
+// See the comment in GCRuntime.h above grayBufferState for details.
+class BufferGrayRootsTracer : public JSTracer
+{
+    // Set to false if we OOM while buffering gray roots.
+    bool bufferingGrayRootsFailed;
+
+    void appendGrayRoot(void *thing, JSGCTraceKind kind);
+
+  public:
+    explicit BufferGrayRootsTracer(JSRuntime *rt)
+      : JSTracer(rt, grayTraceCallback), bufferingGrayRootsFailed(false)
+    {}
+
+    static void grayTraceCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind) {
+        static_cast<BufferGrayRootsTracer *>(trc)->appendGrayRoot(*thingp, kind);
+    }
+
+    bool failed() const { return bufferingGrayRootsFailed; }
+};
+
 void
 SetMarkStackLimit(JSRuntime *rt, size_t limit);
 
 // Return true if this trace is happening on behalf of gray buffering during
 // the marking phase of incremental GC.
 inline bool
-IsMarkingGray(JSTracer *trc)
+IsBufferingGrayRoots(JSTracer *trc)
 {
-    return trc->callback == js::GCMarker::GrayCallback;
+    return trc->callback == BufferGrayRootsTracer::grayTraceCallback;
 }
 
 // Return true if this trace is happening on behalf of the marking phase of GC.
 inline bool
 IsMarkingTracer(JSTracer *trc)
 {
     // If we call this on the gray-buffering tracer, then we have encountered a
     // marking path that will be wrong when tracing with a callback marker to
     // enqueue for deferred gray marking.
-    MOZ_ASSERT(!IsMarkingGray(trc));
+    MOZ_ASSERT(!IsBufferingGrayRoots(trc));
     return trc->callback == nullptr;
 }
 
 } /* namespace js */
 
 #endif /* js_Tracer_h */
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -38,17 +38,17 @@ class ZoneHeapThreshold
   public:
     ZoneHeapThreshold()
       : gcHeapGrowthFactor_(3.0),
         gcTriggerBytes_(0)
     {}
 
     double gcHeapGrowthFactor() const { return gcHeapGrowthFactor_; }
     size_t gcTriggerBytes() const { return gcTriggerBytes_; }
-    bool isCloseToAllocTrigger(const js::gc::HeapUsage& usage, bool highFrequencyGC) const;
+    double allocTrigger(bool highFrequencyGC) const;
 
     void updateAfterGC(size_t lastBytes, JSGCInvocationKind gckind,
                        const GCSchedulingTunables &tunables, const GCSchedulingState &state);
     void updateForRemovedArena(const GCSchedulingTunables &tunables);
 
   private:
     static double computeZoneHeapGrowthFactorForHeapSize(size_t lastBytes,
                                                          const GCSchedulingTunables &tunables,
--- a/js/src/jit-test/tests/basic/bug908915.js
+++ b/js/src/jit-test/tests/basic/bug908915.js
@@ -1,14 +1,15 @@
 // |jit-test| error: 42
 function f(y) {}
 for each(let e in newGlobal()) {
     if (e.name === "quit" || e.name == "readline" || e.name == "terminate" ||
 	e.name == "nestedShell")
 	continue;
+        print(e.name);
     try {
         e();
     } catch (r) {}
 }
 (function() {
     arguments.__proto__.__proto__ = newGlobal()
     function f(y) {
         y()
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/self-test/inIon.js
@@ -0,0 +1,20 @@
+// Test that inIon eventually becomes truthy.
+// This code should never timeout.
+
+function callInIon() {
+    return inIon();
+};
+
+function test() {
+    // Test with OSR.
+    while(!inIon());
+
+    // Test with inlining.
+    while(!callInIon());
+
+    // Test with zealous gc preventing compilation.
+    while(!inIon()) gc();
+};
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/self-test/inJit.js
@@ -0,0 +1,20 @@
+// Test that inJit eventually becomes truthy.
+// This code should never timeout.
+
+function callInJit() {
+    return inJit();
+};
+
+function test() {
+    // Test with OSR.
+    while(!inJit());
+
+    // Test with inlining.
+    while(!callInJit());
+
+    // Test with zealous gc preventing compilation.
+    while(!inJit()) gc();
+};
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/self-test/notInIon.js
@@ -0,0 +1,3 @@
+// |jit-test| --no-ion
+
+assertEq(inIon(), "Ion is disabled.");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/self-test/notInJit.js
@@ -0,0 +1,4 @@
+// |jit-test| --no-baseline
+
+assertEq(inJit(), "Baseline is disabled.");
+assertEq(inIon(), "Ion is disabled.");
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -859,16 +859,17 @@ class IonBuilder
                                   const Class *clasp3 = nullptr,
                                   const Class *clasp4 = nullptr);
     InliningStatus inlineIsConstructing(CallInfo &callInfo);
     InliningStatus inlineSubstringKernel(CallInfo &callInfo);
 
     // Testing functions.
     InliningStatus inlineBailout(CallInfo &callInfo);
     InliningStatus inlineAssertFloat32(CallInfo &callInfo);
+    InliningStatus inlineTrue(CallInfo &callInfo);
 
     // Bind function.
     InliningStatus inlineBoundFunction(CallInfo &callInfo, JSFunction *target);
 
     // Main inlining functions
     InliningStatus inlineNativeCall(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineNativeGetter(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineNonFunctionCall(CallInfo &callInfo, JSObject *target);
--- a/js/src/jit/JitcodeMap.cpp
+++ b/js/src/jit/JitcodeMap.cpp
@@ -713,16 +713,24 @@ JitcodeGlobalTable::verifySkiplist()
         }
         curEntry = curEntry->tower_->next(0);
     }
 
     MOZ_ASSERT(count == skiplistSize_);
 }
 #endif // DEBUG
 
+void
+JitcodeGlobalTable::setAllEntriesAsExpired(JSRuntime *rt)
+{
+    AutoSuppressProfilerSampling suppressSampling(rt);
+    for (Range r(*this); !r.empty(); r.popFront())
+        r.front()->setAsExpired();
+}
+
 bool
 JitcodeGlobalTable::markIteratively(JSTracer *trc)
 {
     // JitcodeGlobalTable must keep entries that are in the sampler buffer
     // alive. This conditionality is akin to holding the entries weakly.
     //
     // If this table were marked at the beginning of the mark phase, then
     // sampling would require a read barrier for sampling in between
@@ -757,17 +765,17 @@ JitcodeGlobalTable::markIteratively(JSTr
         // If an entry is not sampled, reset its generation to the invalid
         // generation, and conditionally mark the rest of the entry if its
         // JitCode is not already marked. This conditional marking ensures
         // that so long as the JitCode *may* be sampled, we keep any
         // information that may be handed out to the sampler, like tracked
         // types used by optimizations and scripts used for pc to line number
         // mapping, alive as well.
         if (!entry->isSampled(gen, lapCount)) {
-            entry->setGeneration(UINT32_MAX);
+            entry->setAsExpired();
             if (!entry->baseEntry().isJitcodeMarkedFromAnyThread())
                 continue;
         }
 
         // The table is runtime-wide. Not all zones may be participating in
         // the GC.
         if (!entry->zone()->isCollecting() || entry->zone()->isGCFinished())
             continue;
--- a/js/src/jit/JitcodeMap.h
+++ b/js/src/jit/JitcodeMap.h
@@ -596,16 +596,19 @@ class JitcodeGlobalEntry
     }
 
     uint32_t generation() const {
         return baseEntry().generation();
     }
     void setGeneration(uint32_t gen) {
         baseEntry().setGeneration(gen);
     }
+    void setAsExpired() {
+        baseEntry().setGeneration(UINT32_MAX);
+    }
     bool isSampled(uint32_t currentGen, uint32_t lapCount) {
         return baseEntry().isSampled(currentGen, lapCount);
     }
 
     bool startsBelowPointer(void *ptr) const {
         return base_.startsBelowPointer(ptr);
     }
     bool endsAbovePointer(void *ptr) const {
@@ -948,16 +951,17 @@ class JitcodeGlobalTable
     }
     bool addEntry(const JitcodeGlobalEntry::DummyEntry &entry, JSRuntime *rt) {
         return addEntry(JitcodeGlobalEntry(entry), rt);
     }
 
     void removeEntry(JitcodeGlobalEntry &entry, JitcodeGlobalEntry **prevTower, JSRuntime *rt);
     void releaseEntry(JitcodeGlobalEntry &entry, JitcodeGlobalEntry **prevTower, JSRuntime *rt);
 
+    void setAllEntriesAsExpired(JSRuntime *rt);
     bool markIteratively(JSTracer *trc);
     void sweep(JSRuntime *rt);
 
   private:
     bool addEntry(const JitcodeGlobalEntry &entry, JSRuntime *rt);
 
     JitcodeGlobalEntry *lookupInternal(void *ptr);
 
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -245,16 +245,18 @@ IonBuilder::inlineNativeCall(CallInfo &c
     if (native == js::SetTypedObjectOffset)
         return inlineSetTypedObjectOffset(callInfo);
 
     // Testing Functions
     if (native == testingFunc_bailout)
         return inlineBailout(callInfo);
     if (native == testingFunc_assertFloat32)
         return inlineAssertFloat32(callInfo);
+    if (native == testingFunc_inIon || native == testingFunc_inJit)
+        return inlineTrue(callInfo);
 
     // Bound function
     if (native == js::CallOrConstructBoundFunction)
         return inlineBoundFunction(callInfo, target);
 
     // Simd functions
 #define INLINE_FLOAT32X4_SIMD_ARITH_(OP)                                                         \
     if (native == js::simd_float32x4_##OP)                                                       \
@@ -2564,16 +2566,25 @@ IonBuilder::inlineBailout(CallInfo &call
 
     MConstant *undefined = MConstant::New(alloc(), UndefinedValue());
     current->add(undefined);
     current->push(undefined);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
+IonBuilder::inlineTrue(CallInfo &callInfo)
+{
+    callInfo.setImplicitlyUsedUnchecked();
+
+    pushConstant(BooleanValue(true));
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
 IonBuilder::inlineAssertFloat32(CallInfo &callInfo)
 {
     callInfo.setImplicitlyUsedUnchecked();
 
     MDefinition *secondArg = callInfo.getArg(1);
 
     MOZ_ASSERT(secondArg->type() == MIRType_Boolean);
     MOZ_ASSERT(secondArg->isConstantValue());
@@ -2683,16 +2694,19 @@ IonBuilder::inlineAtomicsCompareExchange