Merge m-c to inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 01 Dec 2016 09:33:46 -0500
changeset 325016 07b9aab24f30b7172e4b6553773c382200b32488
parent 325015 1857e763332c21cb50da642cf583b95379df275f (current diff)
parent 324909 1fa4b27c956ff3d0a471aaf0efa7e03d4162b560 (diff)
child 325017 7d1386238da423cf6c163e1197aedf6d793bfb41
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersmerge
milestone53.0a1
Merge m-c to inbound. a=merge
dom/html/HTMLMediaElement.cpp
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -19,16 +19,19 @@ const L10N =
 // smoothly progress if this resolution is not high enough.
 // So, if the difference of animation progress between 2 divisions is more than
 // MIN_PROGRESS_THRESHOLD, then createPathSegments re-divides
 // by DURATION_RESOLUTION.
 // DURATION_RESOLUTION shoud be integer and more than 2.
 const DURATION_RESOLUTION = 4;
 // MIN_PROGRESS_THRESHOLD shoud be between more than 0 to 1.
 const MIN_PROGRESS_THRESHOLD = 0.1;
+// BOUND_EXCLUDING_TIME should be less than 1ms and is used to exclude start
+// and end bounds when dividing  duration in createPathSegments.
+const BOUND_EXCLUDING_TIME = 0.001;
 // Show max 10 iterations for infinite animations
 // to give users a clue that the animation does repeat.
 const MAX_INFINITE_ANIMATIONS_ITERATIONS = 10;
 // SVG namespace
 const SVG_NS = "http://www.w3.org/2000/svg";
 
 /**
  * UI component responsible for displaying a single animation timeline, which
@@ -671,19 +674,20 @@ function createPathSegments(startTime, e
       segmentHelper.getSegment(startTime + index * interval);
 
     // If the distance between the Y coordinate (the animation's progress) of
     // the previous segment and the Y coordinate of the current segment is too
     // large, then recurse with a smaller duration to get more details
     // in the graph.
     if (Math.abs(currentSegment.y - previousSegment.y) > minProgressThreshold) {
       // Divide the current interval (excluding start and end bounds
-      // by adding/subtracting 1ms).
+      // by adding/subtracting BOUND_EXCLUDING_TIME).
       pathSegments = pathSegments.concat(
-        createPathSegments(previousSegment.x + 1, currentSegment.x - 1,
+        createPathSegments(previousSegment.x + BOUND_EXCLUDING_TIME,
+                           currentSegment.x - BOUND_EXCLUDING_TIME,
                            minSegmentDuration, minProgressThreshold,
                            segmentHelper));
     }
 
     pathSegments.push(currentSegment);
     previousSegment = currentSegment;
   }
 
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -5,16 +5,17 @@ support-files =
   doc_body_animation.html
   doc_end_delay.html
   doc_frame_script.js
   doc_keyframes.html
   doc_modify_playbackRate.html
   doc_negative_animation.html
   doc_pseudo_elements.html
   doc_script_animation.html
+  doc_short_duration_animation.html
   doc_simple_animation.html
   doc_multiple_animation_types.html
   doc_timing_combination_animation.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/framework/test/shared-head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
@@ -53,16 +54,17 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_animation_timeline_pause_button_02.js]
 [browser_animation_timeline_pause_button_03.js]
 [browser_animation_timeline_rate_selector.js]
 [browser_animation_timeline_rewind_button.js]
 [browser_animation_timeline_scrubber_exists.js]
 [browser_animation_timeline_scrubber_movable.js]
 [browser_animation_timeline_scrubber_moves.js]
 [browser_animation_timeline_setCurrentTime.js]
+[browser_animation_timeline_short_duration.js]
 [browser_animation_timeline_shows_delay.js]
 [browser_animation_timeline_shows_endDelay.js]
 [browser_animation_timeline_shows_iterations.js]
 [browser_animation_timeline_shows_name_label.js]
 [browser_animation_timeline_shows_time_info.js]
 [browser_animation_timeline_takes_rate_into_account.js]
 [browser_animation_timeline_ui.js]
 [browser_animation_toggle_button_resets_on_navigate.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_short_duration.js
@@ -0,0 +1,97 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Test short duration (e.g. 1ms) animation.
+
+add_task(function* () {
+  yield addTab(URL_ROOT + "doc_short_duration_animation.html");
+  const { panel, inspector } = yield openAnimationInspector();
+
+  const timelineComponent = panel.animationsTimelineComponent;
+
+  info("Check the listed time blocks");
+  for (let i = 0; i < timelineComponent.timeBlocks.length; i++) {
+    info(`Check the time block ${i}`);
+    const {containerEl, animation: {state}} = timelineComponent.timeBlocks[i];
+    checkSummaryGraph(containerEl, state);
+  }
+
+  info("Check the time block one by one");
+  info("Check #onetime");
+  yield selectNodeAndWaitForAnimations("#onetime", inspector);
+  let timeBlock = timelineComponent.timeBlocks[0];
+  let containerEl = timeBlock.containerEl;
+  let state = timeBlock.animation.state;
+  checkSummaryGraph(containerEl, state, true);
+
+  info("Check #infinite");
+  yield selectNodeAndWaitForAnimations("#infinite", inspector);
+  timeBlock = timelineComponent.timeBlocks[0];
+  containerEl = timeBlock.containerEl;
+  state = timeBlock.animation.state;
+  checkSummaryGraph(containerEl, state, true);
+});
+
+function checkSummaryGraph(el, state, isDetail) {
+  info("Check the coordinates of summary graph");
+  const pathEls = el.querySelectorAll(".iteration-path");
+  let expectedIterationCount = 0;
+  if (isDetail) {
+    expectedIterationCount = state.iterationCount ? state.iterationCount : 1;
+  } else {
+    expectedIterationCount = state.iterationCount ? state.iterationCount : 2;
+  }
+  is(pathEls.length, expectedIterationCount,
+     `The count of path shoud be ${ expectedIterationCount }`);
+  pathEls.forEach((pathEl, index) => {
+    const startX = index * state.duration;
+    const endX = startX + state.duration;
+
+    const pathSegList = pathEl.pathSegList;
+    const firstPathSeg = pathSegList.getItem(0);
+    is(firstPathSeg.x, startX,
+       `The x of first segment should be ${ startX }`);
+    is(firstPathSeg.y, 0, "The y of first segment should be 0");
+
+    // The easing of test animation is 'linear'.
+    // Therefore, the y of second path segment will be 0.
+    const secondPathSeg = pathSegList.getItem(1);
+    is(secondPathSeg.x, startX,
+       `The x of second segment should be ${ startX }`);
+    is(secondPathSeg.y, 0, "The y of second segment should be 0");
+
+    const thirdLastPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 4);
+    approximate(thirdLastPathSeg.x, endX - 0.001, 0.005,
+                `The x of third last segment should be approximately ${ endX - 0.001 }`);
+    approximate(thirdLastPathSeg.y, 0.999, 0.005,
+                " The y of third last segment should be approximately "
+                + thirdLastPathSeg.x);
+
+    // The test animation is not 'forwards' fill-mode.
+    // Therefore, the y of second last path segment will be 0.
+    const secondLastPathSeg =
+      pathSegList.getItem(pathSegList.numberOfItems - 3);
+    is(secondLastPathSeg.x, endX,
+       `The x of second last segment should be ${ endX }`);
+    is(secondLastPathSeg.y, 0, "The y of second last segment should be 0");
+
+    const lastPathSeg = pathSegList.getItem(pathSegList.numberOfItems - 2);
+    is(lastPathSeg.x, endX, `The x of last segment should be ${ endX }`);
+    is(lastPathSeg.y, 0, "The y of last segment should be 0");
+
+    const closePathSeg = pathSegList.getItem(pathSegList.numberOfItems - 1);
+    is(closePathSeg.pathSegType, closePathSeg.PATHSEG_CLOSEPATH,
+       `The actual last segment should be close path`);
+  });
+}
+
+function approximate(value, expected, permissibleRange, message) {
+  const min = expected - permissibleRange;
+  const max = expected + permissibleRange;
+  ok(min <= value && value <= max, message);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_short_duration_animation.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <style>
+    div {
+      display: inline-block;
+      width: 100px;
+      height: 100px;
+      background-color: lime;
+    }
+    </style>
+  </head>
+  <body>
+    <div id="onetime"></div>
+    <div id="twotimes"></div>
+    <div id="infinite"></div>
+    <script>
+    "use strict";
+    let target = document.querySelector("#onetime");
+    target.animate({ opacity: [0, 1] },
+                   { duration: 1, iterations: 1 }).pause();
+    target = document.querySelector("#twotimes");
+    target.animate({ opacity: [0, 1] },
+                   { duration: 1, iterations: 2 }).pause();
+    target = document.querySelector("#infinite");
+    target.animate({ opacity: [0, 1] },
+                   { duration: 1, iterations: Infinity }).pause();
+    </script>
+  </body>
+</html>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -668,36 +668,29 @@ public:
       return;
     }
 
     mIsOwnerAudible = newAudibleState;
     mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
   }
 
   bool
-  IsAllowedToPlay()
+  IsPlaybackBlocked()
   {
-    // The media element has already been paused or blocked, so it can't start
-    // playback again by script or user's intend until resuming by audio channel.
-    if (mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
-        mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
-      return false;
-    }
-
     // If the tab hasn't been activated yet, the media element in that tab can't
     // be playback now until the tab goes to foreground first time or user clicks
     // the unblocking tab icon.
     if (!IsTabActivated()) {
       // Even we haven't start playing yet, we still need to notify the audio
       // channe system because we need to receive the resume notification later.
       UpdateAudioChannelPlayingState(true /* force to start */);
-      return false;
-    }
-
-    return true;
+      return true;
+    }
+
+    return false;
   }
 
   float
   GetEffectiveVolume() const
   {
     return mOwner->Volume() * mAudioChannelVolume;
   }
 
@@ -948,24 +941,24 @@ private:
   bool mPlayingThroughTheAudioChannel;
   // True if the sound is being captured by the window.
   bool mAudioCapturedByWindow;
   // We have different kinds of suspended cases,
   // - SUSPENDED_PAUSE
   // It's used when we temporary lost platform audio focus. MediaElement can
   // only be resumed when we gain the audio focus again.
   // - SUSPENDED_PAUSE_DISPOSABLE
-  // It's used when user press the pause botton on the remote media-control.
-  // MediaElement can be resumed by reomte media-control or via play().
+  // It's used when user press the pause button on the remote media-control.
+  // MediaElement can be resumed by remote media-control or via play().
   // - SUSPENDED_BLOCK
-  // It's used to reduce the power comsuption, we won't play the auto-play
+  // It's used to reduce the power consumption, we won't play the auto-play
   // audio/video in the page we have never visited before. MediaElement would
   // be resumed when the page is active. See bug647429 for more details.
   // - SUSPENDED_STOP_DISPOSABLE
-  // When we permanently lost platform audio focus, we shuold stop playing
+  // When we permanently lost platform audio focus, we should stop playing
   // and stop the audio channel agent. MediaElement can only be restarted by
   // play().
   SuspendTypes mSuspended;
   // True if media element is audible for users.
   bool mIsOwnerAudible;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
@@ -3563,32 +3556,38 @@ void
 HTMLMediaElement::NotifyXPCOMShutdown()
 {
   ShutdownDecoder();
 }
 
 void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
-  if (!IsAllowedToPlay()) {
+  if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) {
+    // NOTE: for promise-based-play, will return a pending promise here.
     MaybeDoLoad();
     return;
   }
 
   nsresult rv = PlayInternal();
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 
   UpdateCustomPolicyAfterPlayed();
 }
 
 nsresult
 HTMLMediaElement::PlayInternal()
 {
+  if (!IsAllowedToPlay()) {
+    // NOTE: for promise-based-play, will return a rejected promise here.
+    return NS_OK;
+  }
+
   // Play was not blocked so assume user interacted with the element.
   mHasUserInteraction = true;
 
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
 
   MaybeDoLoad();
   if (mSuspendedForPreloadNone) {
@@ -3652,17 +3651,18 @@ HTMLMediaElement::MaybeDoLoad()
 {
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     DoLoad();
   }
 }
 
 NS_IMETHODIMP HTMLMediaElement::Play()
 {
-  if (!IsAllowedToPlay()) {
+  if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) {
+    // NOTE: for promise-based-play, will return a pending promise here.
     MaybeDoLoad();
     return NS_OK;
   }
 
   nsresult rv = PlayInternal();
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -5484,18 +5484,23 @@ bool HTMLMediaElement::CanActivateAutopl
   if (!mPaused) {
     return false;
   }
 
   if (mPausedForInactiveDocumentOrChannel) {
     return false;
   }
 
-  if (mAudioChannelWrapper && !mAudioChannelWrapper->IsAllowedToPlay()) {
-    return false;
+  if (mAudioChannelWrapper) {
+    // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single state.
+    if (mAudioChannelWrapper->GetSuspendType() == nsISuspendedTypes::SUSPENDED_PAUSE ||
+        mAudioChannelWrapper->GetSuspendType() == nsISuspendedTypes::SUSPENDED_BLOCK ||
+        mAudioChannelWrapper->IsPlaybackBlocked()) {
+      return false;
+    }
   }
 
   bool hasData =
     (mDecoder && mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) ||
     (mSrcStream && mSrcStream->Active()) ||
     mMediaSource;
 
   return hasData;
@@ -6310,17 +6315,23 @@ HTMLMediaElement::IsAllowedToPlay()
                                          false,
                                          false);
 #endif
     return false;
   }
 
   // Check our custom playback policy.
   if (mAudioChannelWrapper) {
-    return mAudioChannelWrapper->IsAllowedToPlay();
+    // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single state.
+    if (mAudioChannelWrapper->GetSuspendType() == nsISuspendedTypes::SUSPENDED_PAUSE ||
+        mAudioChannelWrapper->GetSuspendType() == nsISuspendedTypes::SUSPENDED_BLOCK) {
+      return false;
+    }
+
+    return true;
   }
 
   // If the mAudioChannelWrapper doesn't exist that means the CC happened.
   return false;
 }
 
 static const char* VisibilityString(Visibility aVisibility) {
   switch(aVisibility) {
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -3,35 +3,31 @@
 
 'use strict';
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 const {PushDB, PushService, PushServiceHttp2} = serviceExports;
 
 var prefs;
-var tlsProfile;
 
 var serverPort = -1;
 
 function run_test() {
   serverPort = getTestServerPort();
 
   do_get_profile();
   setPrefs({
     'testing.allowInsecureServerURL': true,
   });
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
-  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
-
   // Set to allow the cert presented by our H2 server
   var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
   prefs.setIntPref("network.http.speculative-parallel-limit", 0);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setBoolPref("dom.push.enabled", true);
   prefs.setBoolPref("dom.push.connection.enabled", true);
 
   addCertOverride("localhost", serverPort,
                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                   Ci.nsICertOverrideService.ERROR_MISMATCH |
                   Ci.nsICertOverrideService.ERROR_TIME);
 
@@ -178,12 +174,8 @@ add_task(function* test_pushNotification
 
   PushService.init({
     serverURI: serverURL,
     db
   });
 
   yield notifyPromise;
 });
-
-add_task(function* test_complete() {
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
-});
--- a/dom/push/test/xpcshell/test_register_error_http2.js
+++ b/dom/push/test/xpcshell/test_register_error_http2.js
@@ -3,29 +3,26 @@
 
 'use strict';
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 const {PushDB, PushService, PushServiceHttp2} = serviceExports;
 
 var prefs;
-var tlsProfile;
 var serverURL;
 
 var serverPort = -1;
 
 function run_test() {
   serverPort = getTestServerPort();
 
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
-  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
-
   serverURL = "https://localhost:" + serverPort;
 
   run_next_test();
 }
 
 // Connection will fail because of the certificates.
 add_task(function* test_pushSubscriptionNoConnection() {
 
@@ -52,17 +49,16 @@ add_task(function* test_pushSubscription
   ok(record.length === 0, "Should not store records when connection couldn't be established.");
   PushService.uninit();
 });
 
 add_task(function* test_TLS() {
     // Set to allow the cert presented by our H2 server
   var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
   prefs.setIntPref("network.http.speculative-parallel-limit", 0);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
 
   addCertOverride("localhost", serverPort,
                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                   Ci.nsICertOverrideService.ERROR_MISMATCH |
                   Ci.nsICertOverrideService.ERROR_TIME);
 
   prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
 });
@@ -190,12 +186,8 @@ add_task(function* test_pushSubscription
         { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     'Expected error for not 201 responce code.'
   );
 
   let record = yield db.getAllKeyIDs();
   ok(record.length === 0, 'Should not store records when respons code is not 201.');
 });
-
-add_task(function* test_complete() {
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
-});
--- a/dom/push/test/xpcshell/test_register_success_http2.js
+++ b/dom/push/test/xpcshell/test_register_success_http2.js
@@ -3,37 +3,34 @@
 
 'use strict';
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 const {PushDB, PushService, PushServiceHttp2} = serviceExports;
 
 var prefs;
-var tlsProfile;
 var serverURL;
 var serverPort = -1;
 var pushEnabled;
 var pushConnectionEnabled;
 var db;
 
 function run_test() {
   serverPort = getTestServerPort();
 
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
-  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
   pushEnabled = prefs.getBoolPref("dom.push.enabled");
   pushConnectionEnabled = prefs.getBoolPref("dom.push.connection.enabled");
 
   // Set to allow the cert presented by our H2 server
   var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
   prefs.setIntPref("network.http.speculative-parallel-limit", 0);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setBoolPref("dom.push.enabled", true);
   prefs.setBoolPref("dom.push.connection.enabled", true);
 
   addCertOverride("localhost", serverPort,
                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                   Ci.nsICertOverrideService.ERROR_MISMATCH |
                   Ci.nsICertOverrideService.ERROR_TIME);
 
@@ -117,12 +114,11 @@ add_task(function* test_pushSubscription
     'Wrong push endpoint in database record');
   equal(record.pushReceiptEndpoint, pushReceiptEndpoint,
     'Wrong push endpoint receipt in database record');
   equal(record.scope, 'https://example.org/no_receiptEndpoint',
     'Wrong scope in database record');
 });
 
 add_task(function* test_complete() {
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
   prefs.setBoolPref("dom.push.enabled", pushEnabled);
   prefs.setBoolPref("dom.push.connection.enabled", pushConnectionEnabled);
 });
--- a/dom/push/test/xpcshell/test_unregister_success_http2.js
+++ b/dom/push/test/xpcshell/test_unregister_success_http2.js
@@ -3,36 +3,33 @@
 
 'use strict';
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 const {PushDB, PushService, PushServiceHttp2} = serviceExports;
 
 var prefs;
-var tlsProfile;
 var pushEnabled;
 var pushConnectionEnabled;
 
 var serverPort = -1;
 
 function run_test() {
   serverPort = getTestServerPort();
 
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
-  tlsProfile = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
   pushEnabled = prefs.getBoolPref("dom.push.enabled");
   pushConnectionEnabled = prefs.getBoolPref("dom.push.connection.enabled");
 
   // Set to allow the cert presented by our H2 server
   var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
   prefs.setIntPref("network.http.speculative-parallel-limit", 0);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setBoolPref("dom.push.enabled", true);
   prefs.setBoolPref("dom.push.connection.enabled", true);
 
   addCertOverride("localhost", serverPort,
                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                   Ci.nsICertOverrideService.ERROR_MISMATCH |
                   Ci.nsICertOverrideService.ERROR_TIME);
 
@@ -70,12 +67,11 @@ add_task(function* test_pushUnsubscripti
       { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   let record = yield db.getByKeyID(serverURL + '/subscriptionUnsubscriptionSuccess');
   ok(!record, 'Unregister did not remove record');
 
 });
 
 add_task(function* test_complete() {
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlsProfile);
   prefs.setBoolPref("dom.push.enabled", pushEnabled);
   prefs.setBoolPref("dom.push.connection.enabled", pushConnectionEnabled);
 });
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -7,17 +7,16 @@
 
 #include <algorithm>
 
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Monitor.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
-#include "nsPrintfCString.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
 
 #include "gfxPrefs.h"
 
 #include "Decoder.h"
@@ -157,37 +156,34 @@ private:
   bool mShuttingDown;
 };
 
 class DecodePoolWorker : public Runnable
 {
 public:
   explicit DecodePoolWorker(DecodePoolImpl* aImpl)
     : mImpl(aImpl)
-    , mSerialNumber(++sNextSerialNumber)
   { }
 
   NS_IMETHOD Run() override
   {
 #ifdef MOZ_ENABLE_PROFILER_SPS
     char stackBaseGuess; // Need to be the first variable of main loop function.
 #endif // MOZ_ENABLE_PROFILER_SPS
 
     MOZ_ASSERT(!NS_IsMainThread());
 
     mImpl->InitCurrentThread();
 
     nsCOMPtr<nsIThread> thisThread;
     nsThreadManager::get().GetCurrentThread(getter_AddRefs(thisThread));
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
-    {
-      const nsPrintfCString threadName("ImgDecoder#%lu", mSerialNumber);
-      profiler_register_thread(threadName.get(), &stackBaseGuess);
-    }
+    // InitCurrentThread() has assigned the thread name.
+    profiler_register_thread(PR_GetThreadName(PR_GetCurrentThread()), &stackBaseGuess);
 #endif // MOZ_ENABLE_PROFILER_SPS
 
     do {
       Work work = mImpl->PopWork();
       switch (work.mType) {
         case Work::Type::TASK:
           work.mTask->Run();
           break;
@@ -206,24 +202,19 @@ public:
       }
     } while (true);
 
     MOZ_ASSERT_UNREACHABLE("Exiting thread without Work::Type::SHUTDOWN");
     return NS_OK;
   }
 
 private:
-  static uint32_t sNextSerialNumber;
-
   RefPtr<DecodePoolImpl> mImpl;
-  uint32_t mSerialNumber;
 };
 
-uint32_t DecodePoolWorker::sNextSerialNumber = 0;
-
 /* static */ void
 DecodePool::Initialize()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sNumCores = max<int32_t>(PR_GetNumberOfProcessors(), 1);
   DecodePool::Singleton();
 }
 
--- a/layout/generic/CSSAlignUtils.cpp
+++ b/layout/generic/CSSAlignUtils.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Utility code for performing CSS Box Alignment */
 
 #include "CSSAlignUtils.h"
+#include "ReflowInput.h"
 
 namespace mozilla {
 
 static nscoord
 SpaceToFill(WritingMode aWM, const LogicalSize& aSize, nscoord aMargin,
             LogicalAxis aAxis, nscoord aCBSize)
 {
   nscoord size = aAxis == eLogicalAxisBlock ? aSize.BSize(aWM)
--- a/layout/generic/CSSAlignUtils.h
+++ b/layout/generic/CSSAlignUtils.h
@@ -7,16 +7,18 @@
 
 #ifndef mozilla_CSSAlignUtils_h
 #define mozilla_CSSAlignUtils_h
 
 #include "mozilla/WritingModes.h"
 
 namespace mozilla {
 
+struct ReflowInput;
+
 class CSSAlignUtils {
 public:
   /**
    * Flags to customize the behavior of AlignJustifySelf:
    */
   enum class AlignJustifyFlags {
     eNoFlags           = 0,
     // Indicates that we have <overflow-position> = safe.
--- a/layout/generic/MathMLTextRunFactory.h
+++ b/layout/generic/MathMLTextRunFactory.h
@@ -9,17 +9,17 @@
 #include "mozilla/UniquePtr.h"
 #include "nsTextRunTransformations.h"
 
 /**
  * Builds textruns that render their text with MathML specific renderings.
  */
 class MathMLTextRunFactory : public nsTransformingTextRunFactory {
 public:
-  MathMLTextRunFactory(UniquePtr<nsTransformingTextRunFactory> aInnerTransformingTextRunFactory,
+  MathMLTextRunFactory(mozilla::UniquePtr<nsTransformingTextRunFactory> aInnerTransformingTextRunFactory,
                        uint32_t aFlags, uint8_t aSSTYScriptLevel,
                        float aFontInflation)
     : mInnerTransformingTextRunFactory(Move(aInnerTransformingTextRunFactory)),
       mFlags(aFlags),
       mFontInflation(aFontInflation),
       mSSTYScriptLevel(aSSTYScriptLevel) {}
 
   virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
@@ -28,15 +28,15 @@ public:
   enum {
     // Style effects which may override single character <mi> behaviour
     MATH_FONT_STYLING_NORMAL   = 0x1, // fontstyle="normal" has been set.
     MATH_FONT_WEIGHT_BOLD      = 0x2, // fontweight="bold" has been set.
     MATH_FONT_FEATURE_DTLS     = 0x4, // font feature dtls should be set
   };
 
 protected:
-  UniquePtr<nsTransformingTextRunFactory> mInnerTransformingTextRunFactory;
+  mozilla::UniquePtr<nsTransformingTextRunFactory> mInnerTransformingTextRunFactory;
   uint32_t mFlags;
   float mFontInflation;
   uint8_t mSSTYScriptLevel;
 };
 
 #endif /*MATHMLTEXTRUNFACTORY_H_*/
--- a/layout/generic/ReflowOutput.cpp
+++ b/layout/generic/ReflowOutput.cpp
@@ -31,16 +31,18 @@ nsOverflowAreas::UnionAllWith(const nsRe
 void
 nsOverflowAreas::SetAllTo(const nsRect& aRect)
 {
   NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
     mRects[otype] = aRect;
   }
 }
 
+namespace mozilla {
+
 ReflowOutput::ReflowOutput(const ReflowInput& aState,
                                          uint32_t aFlags)
   : mISize(0)
   , mBSize(0)
   , mBlockStartAscent(ASK_FOR_BASELINE)
   , mFlags(aFlags)
   , mWritingMode(aState.GetWritingMode())
 {
@@ -60,8 +62,10 @@ ReflowOutput::UnionOverflowAreasWithDesi
   // FIXME: We should probably change scrollable overflow to use
   // UnionRectIncludeEmpty (but leave visual overflow using UnionRect).
   nsRect rect(0, 0, Width(), Height());
   NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
     nsRect& o = mOverflowAreas.Overflow(otype);
     o.UnionRect(o, rect);
   }
 }
+
+} // namespace mozilla
--- a/layout/generic/RubyUtils.h
+++ b/layout/generic/RubyUtils.h
@@ -2,19 +2,20 @@
 /* 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_RubyUtils_h_
 #define mozilla_RubyUtils_h_
 
-#include "nsTArray.h"
+#include "nsCSSAnonBoxes.h"
 #include "nsGkAtoms.h"
-#include "nsCSSAnonBoxes.h"
+#include "nsIFrame.h"
+#include "nsTArray.h"
 
 #define RTC_ARRAY_SIZE 1
 
 class nsRubyFrame;
 class nsRubyBaseFrame;
 class nsRubyTextFrame;
 class nsRubyContentFrame;
 class nsRubyBaseContainerFrame;
--- a/layout/generic/ScrollSnap.h
+++ b/layout/generic/ScrollSnap.h
@@ -1,16 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layout_ScrollSnap_h_
 #define mozilla_layout_ScrollSnap_h_
 
+#include "mozilla/Maybe.h"
+#include "nsIScrollableFrame.h"
+
 namespace mozilla {
 
 namespace layers {
 struct ScrollSnapInfo;
 }
 
 struct ScrollSnapUtils {
   /**
@@ -22,17 +25,17 @@ struct ScrollSnapUtils {
    * of the scroll frame for which snapping is being performed.
    * If a suitable snap point could be found, it is returned. Otherwise, an
    * empty Maybe is returned.
    * IMPORTANT NOTE: This function is designed to be called both on and off
    *                 the main thread. If modifying its implementation, be sure
    *                 not to touch main-thread-only data structures without
    *                 appropriate locking.
    */
-  static Maybe<nsPoint> GetSnapPointForDestination(
+  static mozilla::Maybe<nsPoint> GetSnapPointForDestination(
       const layers::ScrollSnapInfo& aSnapInfo,
       nsIScrollableFrame::ScrollUnit aUnit,
       const nsSize& aScrollPortSize,
       const nsRect& aScrollRange,
       const nsPoint& aStartPos,
       const nsPoint& aDestination);
 };
 
--- a/layout/generic/ViewportFrame.cpp
+++ b/layout/generic/ViewportFrame.cpp
@@ -12,16 +12,17 @@
 
 #include "nsGkAtoms.h"
 #include "nsIScrollableFrame.h"
 #include "nsSubDocumentFrame.h"
 #include "nsCanvasFrame.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "GeckoProfiler.h"
 #include "nsIMozBrowserFrame.h"
+#include "nsPlaceholderFrame.h"
 
 using namespace mozilla;
 typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
 
 ViewportFrame*
 NS_NewViewportFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) ViewportFrame(aContext);
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -5,16 +5,18 @@
 
 /* base class #1 for rendering objects that have child lists */
 
 #include "nsContainerFrame.h"
 
 #include "mozilla/dom/HTMLDetailsElement.h"
 #include "mozilla/dom/HTMLSummaryElement.h"
 #include "nsAbsoluteContainingBlock.h"
+#include "nsAttrValue.h"
+#include "nsAttrValueInlines.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsRect.h"
 #include "nsPoint.h"
 #include "nsStyleConsts.h"
 #include "nsView.h"
 #include "nsIPresShell.h"
@@ -24,16 +26,17 @@
 #include "nsIWidget.h"
 #include "nsCSSRendering.h"
 #include "nsError.h"
 #include "nsDisplayList.h"
 #include "nsIBaseWindow.h"
 #include "nsBoxLayoutState.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsBlockFrame.h"
+#include "nsBulletFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "mozilla/AutoRestore.h"
 #include "nsIFrameInlines.h"
 #include "nsPrintfCString.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::dom;
--- a/layout/generic/nsPageContentFrame.cpp
+++ b/layout/generic/nsPageContentFrame.cpp
@@ -4,18 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "nsPageContentFrame.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsIPresShell.h"
 #include "nsSimplePageSequenceFrame.h"
 
-using mozilla::LogicalSize;
-using mozilla::WritingMode;
+using namespace mozilla;
 
 nsPageContentFrame*
 NS_NewPageContentFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsPageContentFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsPageContentFrame)
--- a/layout/generic/nsRubyBaseContainerFrame.cpp
+++ b/layout/generic/nsRubyBaseContainerFrame.cpp
@@ -17,16 +17,17 @@
 #include "nsLineLayout.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsStyleStructInlines.h"
 #include "nsTextFrame.h"
 #include "RubyUtils.h"
 
 using namespace mozilla;
+using namespace mozilla::gfx;
 
 //----------------------------------------------------------------------
 
 // Frame class boilerplate
 // =======================
 
 NS_QUERYFRAME_HEAD(nsRubyBaseContainerFrame)
   NS_QUERYFRAME_ENTRY(nsRubyBaseContainerFrame)
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -8,16 +8,18 @@
 #include "nsBidiUtils.h"
 #include "nsCharTraits.h"
 #include "nsIContent.h"
 #include "nsStyleStruct.h"
 #include "nsTextFragment.h"
 #include "nsUnicharUtils.h"
 #include <algorithm>
 
+using namespace mozilla;
+
 static bool IsDiscardable(char16_t ch, uint32_t* aFlags)
 {
   // Unlike IS_DISCARDABLE, we don't discard \r. \r will be ignored by gfxTextRun
   // and discarding it would force us to copy text in many cases of preformatted
   // text containing \r\n.
   if (ch == CH_SHY) {
     *aFlags |= nsTextFrameUtils::TEXT_HAS_SHY;
     return true;
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -15,16 +15,17 @@
 #include "nsSpecialCasingData.h"
 #include "mozilla/gfx/2D.h"
 #include "nsTextFrameUtils.h"
 #include "nsIPersistentProperties2.h"
 #include "GreekCasing.h"
 #include "IrishCasing.h"
 
 using namespace mozilla;
+using namespace mozilla::gfx;
 
 // Unicode characters needing special casing treatment in tr/az languages
 #define LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE  0x0130
 #define LATIN_SMALL_LETTER_DOTLESS_I           0x0131
 
 // Greek sigma needs custom handling for the lowercase transform; for details
 // see comments under "case NS_STYLE_TEXT_TRANSFORM_LOWERCASE" within
 // nsCaseTransformTextRunFactory::RebuildTextRun(), and bug 740120.
--- a/layout/generic/nsTextRunTransformations.h
+++ b/layout/generic/nsTextRunTransformations.h
@@ -73,17 +73,17 @@ class nsCaseTransformTextRunFactory : pu
 public:
   // We could add an optimization here so that when there is no inner
   // factory, no title-case conversion, and no upper-casing of SZLIG, we override
   // MakeTextRun (after making it virtual in the superclass) and have it
   // just convert the string to uppercase or lowercase and create the textrun
   // via the fontgroup.
   
   // Takes ownership of aInnerTransformTextRunFactory
-  explicit nsCaseTransformTextRunFactory(UniquePtr<nsTransformingTextRunFactory> aInnerTransformingTextRunFactory,
+  explicit nsCaseTransformTextRunFactory(mozilla::UniquePtr<nsTransformingTextRunFactory> aInnerTransformingTextRunFactory,
                                          bool aAllUppercase = false)
     : mInnerTransformingTextRunFactory(Move(aInnerTransformingTextRunFactory)),
       mAllUppercase(aAllUppercase) {}
 
   virtual void RebuildTextRun(nsTransformedTextRun* aTextRun,
                               mozilla::gfx::DrawTarget* aRefDrawTarget,
                               gfxMissingFontRecorder* aMFR) override;
 
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -105,50 +105,43 @@ nsConditionalResetStyleData::GetConditio
 
       return data;
     }
     e = e->mNext;
   } while (e);
   return nullptr;
 }
 
-// Creates an imgRequestProxy based on the specified value in
-// aValue and calls aCallback with it.  If the nsPresContext
-// is static (e.g. for printing), then a static request (i.e.
-// showing the first frame, without animation) will be created.
-// (The expectation is then that aCallback will set the resulting
-// imgRequestProxy in a style struct somewhere.)
-static void
-SetImageRequest(std::function<void(imgRequestProxy*)> aCallback,
-                nsPresContext* aPresContext,
-                const nsCSSValue& aValue)
+// Creates an imgRequestProxy based on the specified value in aValue and
+// returns it.  If the nsPresContext is static (e.g. for printing), then
+// a static request (i.e. showing the first frame, without animation)
+// will be created.
+static already_AddRefed<imgRequestProxy>
+CreateImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue)
 {
   RefPtr<imgRequestProxy> req =
     aValue.GetPossiblyStaticImageValue(aPresContext->Document(),
                                        aPresContext);
-  aCallback(req);
-}
-
-static void
-SetStyleImageRequest(std::function<void(nsStyleImageRequest*)> aCallback,
-                     nsPresContext* aPresContext,
-                     const nsCSSValue& aValue,
-                     nsStyleImageRequest::Mode aModeFlags =
-                       nsStyleImageRequest::Mode::Track)
-{
-  SetImageRequest([&](imgRequestProxy* aProxy) {
-    css::ImageValue* imageValue = aValue.GetImageStructValue();
-    ImageTracker* imageTracker =
-      (aModeFlags & nsStyleImageRequest::Mode::Track)
-      ? aPresContext->Document()->ImageTracker()
-      : nullptr;
-    RefPtr<nsStyleImageRequest> request =
-      new nsStyleImageRequest(aModeFlags, aProxy, imageValue, imageTracker);
-    aCallback(request);
-  }, aPresContext, aValue);
+  return req.forget();
+}
+
+static already_AddRefed<nsStyleImageRequest>
+CreateStyleImageRequest(nsPresContext* aPresContext, const nsCSSValue& aValue,
+                        nsStyleImageRequest::Mode aModeFlags =
+                          nsStyleImageRequest::Mode::Track)
+{
+  css::ImageValue* imageValue = aValue.GetImageStructValue();
+  ImageTracker* imageTracker =
+    (aModeFlags & nsStyleImageRequest::Mode::Track)
+    ? aPresContext->Document()->ImageTracker()
+    : nullptr;
+  RefPtr<imgRequestProxy> proxy = CreateImageRequest(aPresContext, aValue);
+  RefPtr<nsStyleImageRequest> request =
+    new nsStyleImageRequest(aModeFlags, proxy, imageValue, imageTracker);
+  return request.forget();
 }
 
 template<typename ReferenceBox>
 static void
 SetStyleShapeSourceToCSSValue(StyleShapeSource<ReferenceBox>* aShapeSource,
                               const nsCSSValue* aValue,
                               nsStyleContext* aStyleContext,
                               nsPresContext* aPresContext,
@@ -1312,19 +1305,18 @@ static void SetStyleImageToImageRect(nsS
              aValue.EqualsFunction(eCSSKeyword__moz_image_rect),
              "the value is not valid -moz-image-rect()");
 
   nsCSSValue::Array* arr = aValue.GetArrayValue();
   MOZ_ASSERT(arr && arr->Count() == 6, "invalid number of arguments");
 
   // <uri>
   if (arr->Item(1).GetUnit() == eCSSUnit_Image) {
-    SetStyleImageRequest([&](nsStyleImageRequest* req) {
-      aResult.SetImageRequest(do_AddRef(req));
-    }, aStyleContext->PresContext(), arr->Item(1));
+    nsPresContext* pc = aStyleContext->PresContext();
+    aResult.SetImageRequest(CreateStyleImageRequest(pc, arr->Item(1)));
   } else {
     NS_WARNING("nsCSSValue::Image::Image() failed?");
   }
 
   // <top>, <right>, <bottom>, <left>
   nsStyleSides cropRect;
   NS_FOR_CSS_SIDES(side) {
     nsStyleCoord coord;
@@ -1346,34 +1338,32 @@ static void SetStyleImage(nsStyleContext
                           RuleNodeCacheConditions& aConditions)
 {
   if (aValue.GetUnit() == eCSSUnit_Null) {
     return;
   }
 
   aResult.SetNull();
 
+  nsPresContext* presContext = aStyleContext->PresContext();
   switch (aValue.GetUnit()) {
     case eCSSUnit_Image:
-      SetStyleImageRequest([&](nsStyleImageRequest* req) {
-        aResult.SetImageRequest(do_AddRef(req));
-      }, aStyleContext->PresContext(), aValue);
+      aResult.SetImageRequest(CreateStyleImageRequest(presContext, aValue));
       break;
     case eCSSUnit_Function:
       if (aValue.EqualsFunction(eCSSKeyword__moz_image_rect)) {
         SetStyleImageToImageRect(aStyleContext, aValue, aResult);
       } else {
         NS_NOTREACHED("-moz-image-rect() is the only expected function");
       }
       break;
     case eCSSUnit_Gradient:
     {
       nsStyleGradient* gradient = new nsStyleGradient();
-      SetGradient(aValue, aStyleContext->PresContext(), aStyleContext,
-                  *gradient, aConditions);
+      SetGradient(aValue, presContext, aStyleContext, *gradient, aConditions);
       aResult.SetGradientData(gradient);
       break;
     }
     case eCSSUnit_Element:
       aResult.SetElementId(aValue.GetStringBufferValue());
       break;
     case eCSSUnit_Initial:
     case eCSSUnit_Unset:
@@ -1395,17 +1385,17 @@ static void SetStyleImage(nsStyleContext
       // eCSSUnit_Image here.
 
       // Check #2.
       bool isLocalRef = aValue.GetURLStructValue()->IsLocalRef();
 
       // Check #3.
       bool isEqualExceptRef = false;
       if (!isLocalRef) {
-        nsIDocument* currentDoc = aStyleContext->PresContext()->Document();
+        nsIDocument* currentDoc = presContext->Document();
         nsIURI* docURI = currentDoc->GetDocumentURI();
         nsIURI* imageURI = aValue.GetURLValue();
         imageURI->EqualsExceptRef(docURI, &isEqualExceptRef);
       }
 
       MOZ_ASSERT(aStyleContext->IsStyleIfVisited() || isEqualExceptRef ||
                  isLocalRef,
                  "unexpected unit; maybe nsCSSValue::Image::Image() failed?");
@@ -5167,26 +5157,25 @@ nsRuleNode::ComputeUserInterfaceData(voi
     else {
       // The parser will never create a list that is *all* URL values --
       // that's invalid.
       MOZ_ASSERT(cursorUnit == eCSSUnit_List || cursorUnit == eCSSUnit_ListDep,
                  "unrecognized cursor unit");
       const nsCSSValueList* list = cursorValue->GetListValue();
       for ( ; list->mValue.GetUnit() == eCSSUnit_Array; list = list->mNext) {
         nsCSSValue::Array* arr = list->mValue.GetArrayValue();
-        SetStyleImageRequest([&](nsStyleImageRequest* req) {
-          nsCursorImage* item = ui->mCursorImages.AppendElement();
-          item->mImage = req;
-          if (arr->Item(1).GetUnit() != eCSSUnit_Null) {
-            item->mHaveHotspot = true;
-            item->mHotspotX = arr->Item(1).GetFloatValue();
-            item->mHotspotY = arr->Item(2).GetFloatValue();
-          }
-        }, aContext->PresContext(), arr->Item(0),
-           nsStyleImageRequest::Mode::Discard);
+        nsCursorImage* item = ui->mCursorImages.AppendElement();
+        item->mImage =
+          CreateStyleImageRequest(aContext->PresContext(), arr->Item(0),
+                                  nsStyleImageRequest::Mode::Discard);
+        if (arr->Item(1).GetUnit() != eCSSUnit_Null) {
+          item->mHaveHotspot = true;
+          item->mHotspotX = arr->Item(1).GetFloatValue();
+          item->mHotspotY = arr->Item(2).GetFloatValue();
+        }
       }
 
       NS_ASSERTION(list, "Must have non-array value at the end");
       NS_ASSERTION(list->mValue.GetUnit() == eCSSUnit_Enumerated,
                    "Unexpected fallback value at end of cursor list");
       ui->mCursor = list->mValue.GetIntValue();
     }
   }
@@ -8015,19 +8004,18 @@ nsRuleNode::ComputeListData(void* aStart
       break;
     default:
       NS_NOTREACHED("Unexpected value unit");
   }
 
   // list-style-image: url, none, inherit
   const nsCSSValue* imageValue = aRuleData->ValueForListStyleImage();
   if (eCSSUnit_Image == imageValue->GetUnit()) {
-    SetStyleImageRequest([&](nsStyleImageRequest* req) {
-      list->mListStyleImage = req;
-    }, mPresContext, *imageValue, nsStyleImageRequest::Mode(0));
+    list->mListStyleImage = CreateStyleImageRequest(
+      mPresContext, *imageValue, nsStyleImageRequest::Mode(0));
   }
   else if (eCSSUnit_None == imageValue->GetUnit() ||
            eCSSUnit_Initial == imageValue->GetUnit()) {
     list->mListStyleImage = nullptr;
   }
   else if (eCSSUnit_Inherit == imageValue->GetUnit() ||
            eCSSUnit_Unset == imageValue->GetUnit()) {
     conditions.SetUncacheable();
@@ -8920,19 +8908,17 @@ nsRuleNode::ComputeContentData(void* aSt
         }
         break;
       default:
         NS_ERROR("bad content type");
         type = eStyleContentType_Uninitialized;
       }
       data.mType = type;
       if (type == eStyleContentType_Image) {
-        SetImageRequest([&](imgRequestProxy* req) {
-          data.SetImage(req);
-        }, mPresContext, value);
+        data.SetImage(CreateImageRequest(mPresContext, value));
       } else if (type <= eStyleContentType_Attr) {
         value.GetStringValue(buffer);
         data.mContent.mString = NS_strdup(buffer.get());
       } else if (type <= eStyleContentType_Counters) {
         data.mContent.mCounters = value.GetArrayValue();
         data.mContent.mCounters->AddRef();
       } else {
         data.mContent.mString = nullptr;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -3185,22 +3185,22 @@ struct nsStyleContentData
 
   bool operator!=(const nsStyleContentData& aOther) const {
     return !(*this == aOther);
   }
 
   void TrackImage(mozilla::dom::ImageTracker* aImageTracker);
   void UntrackImage(mozilla::dom::ImageTracker* aImageTracker);
 
-  void SetImage(imgRequestProxy* aRequest)
+  void SetImage(already_AddRefed<imgRequestProxy> aRequest)
   {
     MOZ_ASSERT(!mImageTracked,
                "Setting a new image without untracking the old one!");
     MOZ_ASSERT(mType == eStyleContentType_Image, "Wrong type!");
-    NS_IF_ADDREF(mContent.mImage = aRequest);
+    mContent.mImage = aRequest.take();
   }
 };
 
 struct nsStyleCounterData
 {
   nsString  mCounter;
   int32_t   mValue;
 
--- a/netwerk/test/unit/test_altsvc.js
+++ b/netwerk/test/unit/test_altsvc.js
@@ -1,16 +1,15 @@
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 var h2Port;
 var prefs;
 var spdypref;
 var http2pref;
-var tlspref;
 var altsvcpref1;
 var altsvcpref2;
 
 // https://foo.example.com:(h2Port)
 // https://bar.example.com:(h2Port) <- invalid for bar, but ok for foo
 var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort)
 var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort)
 
@@ -29,23 +28,21 @@ function run_test() {
   do_check_neq(h2Port, "");
 
   // Set to allow the cert presented by our H2 server
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   spdypref = prefs.getBoolPref("network.http.spdy.enabled");
   http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
-  tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
   altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
   altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
 
   prefs.setBoolPref("network.http.spdy.enabled", true);
   prefs.setBoolPref("network.http.spdy.enabled.http2", true);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setBoolPref("network.http.altsvc.enabled", true);
   prefs.setBoolPref("network.http.altsvc.oe", true);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
 
   // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
   // so add that cert to the trust list as a signing cert. The same cert is used
   // for both h2FooRoute and h2BarRoute though it is only valid for
   // the foo.example.com domain name.
@@ -109,17 +106,16 @@ function h1ServerWK(metadata, response) 
 
   var body = '{"http://foo.example.com:' + h1Foo.identity.primaryPort + '": { "tls-ports": [' + h2Port + '] }}';
   response.bodyOutputStream.write(body, body.length);
 }
 
 function resetPrefs() {
   prefs.setBoolPref("network.http.spdy.enabled", spdypref);
   prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
   prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
   prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
   prefs.clearUserPref("network.dns.localDomains");
 }
 
 function readFile(file) {
   let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
                   .createInstance(Ci.nsIFileInputStream);
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -1020,29 +1020,27 @@ function addCertOverride(host, port, bit
     // This will fail since the server is not trusted yet
   }
 }
 
 var prefs;
 var spdypref;
 var spdypush;
 var http2pref;
-var tlspref;
 var altsvcpref1;
 var altsvcpref2;
 var loadGroup;
 var serverPort;
 var speculativeLimit;
 
 function resetPrefs() {
   prefs.setIntPref("network.http.speculative-parallel-limit", speculativeLimit);
   prefs.setBoolPref("network.http.spdy.enabled", spdypref);
   prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
   prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
   prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
   prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
   prefs.clearUserPref("network.dns.localDomains");
 }
 
 function run_test() {
   var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
   serverPort = env.get("MOZHTTP2_PORT");
@@ -1067,25 +1065,23 @@ function run_test() {
                   Ci.nsICertOverrideService.ERROR_UNTRUSTED |
                   Ci.nsICertOverrideService.ERROR_MISMATCH |
                   Ci.nsICertOverrideService.ERROR_TIME);
 
   // Enable all versions of spdy to see that we auto negotiate http/2
   spdypref = prefs.getBoolPref("network.http.spdy.enabled");
   spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
   http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
-  tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
   altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
   altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
 
   prefs.setBoolPref("network.http.spdy.enabled", true);
   prefs.setBoolPref("network.http.spdy.enabled.v3-1", true);
   prefs.setBoolPref("network.http.spdy.allow-push", true);
   prefs.setBoolPref("network.http.spdy.enabled.http2", true);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setBoolPref("network.http.altsvc.enabled", true);
   prefs.setBoolPref("network.http.altsvc.oe", true);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
 
   loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
 
   httpserv = new HttpServer();
   httpserv.registerPathHandler("/altsvc1", altsvcHttp1Server);
--- a/netwerk/test/unit/test_immutable.js
+++ b/netwerk/test/unit/test_immutable.js
@@ -1,51 +1,47 @@
 Cu.import("resource://testing-common/httpd.js");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 var prefs;
 var spdypref;
 var http2pref;
-var tlspref;
 var origin;
 
 function run_test() {
   var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
   var h2Port = env.get("MOZHTTP2_PORT");
   do_check_neq(h2Port, null);
   do_check_neq(h2Port, "");
 
   // Set to allow the cert presented by our H2 server
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   spdypref = prefs.getBoolPref("network.http.spdy.enabled");
   http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
-  tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
 
   prefs.setBoolPref("network.http.spdy.enabled", true);
   prefs.setBoolPref("network.http.spdy.enabled.http2", true);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setCharPref("network.dns.localDomains", "foo.example.com, bar.example.com");
 
   // The moz-http2 cert is for foo.example.com and is signed by CA.cert.der
   // so add that cert to the trust list as a signing cert.  // the foo.example.com domain name.
   let certdb = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
   addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
 
   origin = "https://foo.example.com:" + h2Port;
   dump ("origin - " + origin + "\n");
   doTest1();
 }
 
 function resetPrefs() {
   prefs.setBoolPref("network.http.spdy.enabled", spdypref);
   prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
-  prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
   prefs.clearUserPref("network.dns.localDomains");
 }
 
 function readFile(file) {
   let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
                   .createInstance(Ci.nsIFileInputStream);
   fstream.init(file, -1, 0, 0);
   let data = NetUtil.readInputStreamToString(fstream, fstream.available());
--- a/taskcluster/docs/transforms.rst
+++ b/taskcluster/docs/transforms.rst
@@ -135,18 +135,20 @@ A job description says what to run in th
 ``run`` section and all of the fields from a task description.  The run section
 has a ``using`` property that defines how this task should be run; for example,
 ``mozharness`` to run a mozharness script, or ``mach`` to run a mach command.
 The remainder of the run section is specific to the run-using implementation.
 
 The effect of a job description is to say "run this thing on this worker".  The
 job description must contain enough information about the worker to identify
 the workerType and the implementation (docker-worker, generic-worker, etc.).
-Any other task-description information is passed along verbatim, although it is
-augmented by the run-using implementation.
+Alternatively, job descriptions can specify the ``platforms`` field in
+conjunction with the  ``by-platform`` key to specify multiple workerTypes and
+implementations. Any other task-description information is passed along
+verbatim, although it is augmented by the run-using implementation.
 
 The run-using implementations are all located in
 ``taskcluster/taskgraph/transforms/job``, along with the schemas for their
 implementations.  Those well-commented source files are the canonical
 documentation for what constitutes a job description, and should be considered
 part of the documentation.
 
 Task Descriptions
--- a/taskcluster/taskgraph/test/test_transforms_base.py
+++ b/taskcluster/taskgraph/test/test_transforms_base.py
@@ -103,41 +103,41 @@ class TestKeyedBy(unittest.TestCase):
                     'a': 10,
                     'default': 30,
                 },
             },
             'other-value': 'xxx',
         }
         self.assertEqual(get_keyed_by(test, 'option', 'x'), 30)
 
-    def test_by_value_invalid_dict(self):
+    def test_by_value_dict(self):
         test = {
             'test-name': 'tname',
             'option': {
                 'by-something-else': {},
                 'by-other-value': {},
             },
         }
-        self.assertRaises(Exception, get_keyed_by, test, 'option', 'x')
+        self.assertEqual(get_keyed_by(test, 'option', 'x'), test['option'])
 
     def test_by_value_invalid_no_default(self):
         test = {
             'test-name': 'tname',
             'option': {
                 'by-other-value': {
                     'a': 10,
                 },
             },
             'other-value': 'b',
         }
         self.assertRaises(Exception, get_keyed_by, test, 'option', 'x')
 
-    def test_by_value_invalid_no_by(self):
+    def test_by_value_no_by(self):
         test = {
             'test-name': 'tname',
             'option': {
                 'other-value': {},
             },
         }
-        self.assertRaises(Exception, get_keyed_by, test, 'option', 'x')
+        self.assertEqual(get_keyed_by(test, 'option', 'x'), test['option'])
 
 if __name__ == '__main__':
     main()
--- a/taskcluster/taskgraph/transforms/base.py
+++ b/taskcluster/taskgraph/transforms/base.py
@@ -98,31 +98,28 @@ def get_keyed_by(item, field, item_name,
     value = item[field]
     if not isinstance(value, dict):
         return value
     if subfield:
         value = item[field][subfield]
         if not isinstance(value, dict):
             return value
 
-    assert len(value) == 1, "Invalid attribute {} in {}".format(field, item_name)
     keyed_by = value.keys()[0]
+    if len(value) > 1 or not keyed_by.startswith('by-'):
+        return value
+
     values = value[keyed_by]
-    if keyed_by.startswith('by-'):
-        keyed_by = keyed_by[3:]  # extract just the keyed-by field name
-        if item[keyed_by] in values:
-            return values[item[keyed_by]]
-        for k in values.keys():
-            if re.match(k, item[keyed_by]):
-                return values[k]
-        if 'default' in values:
-            return values['default']
-        for k in item[keyed_by], 'default':
-            if k in values:
-                return values[k]
-        else:
-            raise Exception(
-                "Neither {} {} nor 'default' found while determining item {} in {}".format(
-                    keyed_by, item[keyed_by], field, item_name))
+    keyed_by = keyed_by[3:]  # strip 'by-' off the keyed-by field name
+    if item[keyed_by] in values:
+        return values[item[keyed_by]]
+    for k in values.keys():
+        if re.match(k, item[keyed_by]):
+            return values[k]
+    if 'default' in values:
+        return values['default']
+    for k in item[keyed_by], 'default':
+        if k in values:
+            return values[k]
     else:
         raise Exception(
-            "Invalid attribute {} keyed-by value {} in {}".format(
-                field, keyed_by, item_name))
+            "Neither {} {} nor 'default' found while determining item {} in {}".format(
+                keyed_by, item[keyed_by], field, item_name))
--- a/taskcluster/taskgraph/transforms/job/__init__.py
+++ b/taskcluster/taskgraph/transforms/job/__init__.py
@@ -10,23 +10,24 @@ run-using handlers in `taskcluster/taskg
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
 import logging
 import os
 
-from taskgraph.transforms.base import validate_schema, TransformSequence
+from taskgraph.transforms.base import get_keyed_by, validate_schema, TransformSequence
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import (
+    Any,
+    Extra,
     Optional,
     Required,
     Schema,
-    Extra,
 )
 
 logger = logging.getLogger(__name__)
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
@@ -47,54 +48,95 @@ job_description_schema = Schema({
     Optional('expires-after'): task_description_schema['expires-after'],
     Optional('routes'): task_description_schema['routes'],
     Optional('scopes'): task_description_schema['scopes'],
     Optional('extra'): task_description_schema['extra'],
     Optional('treeherder'): task_description_schema['treeherder'],
     Optional('index'): task_description_schema['index'],
     Optional('run-on-projects'): task_description_schema['run-on-projects'],
     Optional('coalesce-name'): task_description_schema['coalesce-name'],
-    Optional('worker-type'): task_description_schema['worker-type'],
     Optional('needs-sccache'): task_description_schema['needs-sccache'],
-    Required('worker'): task_description_schema['worker'],
     Optional('when'): task_description_schema['when'],
 
     # A description of how to run this job.
     'run': {
         # The key to a job implementation in a peer module to this one
         'using': basestring,
 
         # Any remaining content is verified against that job implementation's
         # own schema.
         Extra: object,
     },
+    Optional('platforms'): [basestring],
+    Required('worker-type'): Any(
+        task_description_schema['worker-type'],
+        {'by-platform': {basestring: task_description_schema['worker-type']}},
+    ),
+    Required('worker'): Any(
+        task_description_schema['worker'],
+        {'by-platform': {basestring: task_description_schema['worker']}},
+    ),
 })
 
 transforms = TransformSequence()
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         yield validate_schema(job_description_schema, job,
                               "In job {!r}:".format(job['name']))
 
 
 @transforms.add
+def expand_platforms(config, jobs):
+    for job in jobs:
+        if 'platforms' not in job:
+            yield job
+            continue
+
+        for platform in job['platforms']:
+            pjob = copy.deepcopy(job)
+            pjob['platform'] = platform
+            del pjob['platforms']
+
+            platform, buildtype = platform.rsplit('/', 1)
+            pjob['name'] = '{}-{}-{}'.format(pjob['name'], platform, buildtype)
+            yield pjob
+
+
+@transforms.add
+def resolve_keyed_by(config, jobs):
+    fields = [
+        'worker-type',
+        'worker',
+    ]
+
+    for job in jobs:
+        for field in fields:
+            job[field] = get_keyed_by(item=job, field=field, item_name=job['name'])
+        yield job
+
+
+@transforms.add
 def make_task_description(config, jobs):
     """Given a build description, create a task description"""
     # import plugin modules first, before iterating over jobs
     import_all()
     for job in jobs:
         if 'label' not in job:
             if 'name' not in job:
                 raise Exception("job has neither a name nor a label")
             job['label'] = '{}-{}'.format(config.kind, job['name'])
         if job['name']:
             del job['name']
+        if 'platform' in job:
+            if 'treeherder' in job:
+                job['treeherder']['platform'] = job['platform']
+            del job['platform']
 
         taskdesc = copy.deepcopy(job)
 
         # fill in some empty defaults to make run implementations easier
         taskdesc.setdefault('attributes', {})
         taskdesc.setdefault('dependencies', {})
         taskdesc.setdefault('routes', [])
         taskdesc.setdefault('scopes', [])
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1062,40 +1062,30 @@ private:
 public:
     void AttachToJava(jni::Object::Param aClient, jni::Object::Param aNPZC)
     {
         MOZ_ASSERT(NS_IsMainThread());
         if (!mWindow) {
             return; // Already shut down.
         }
 
-        const auto& layerClient = GeckoLayerClient::Ref::From(aClient);
-
-        // If resetting is true, Android destroyed our GeckoApp activity and we
-        // had to recreate it, but all the Gecko-side things were not
-        // destroyed.  We therefore need to link up the new java objects to
-        // Gecko, and that's what we do here.
-        const bool resetting = !!mLayerClient;
-        mLayerClient = layerClient;
+        mLayerClient = GeckoLayerClient::Ref::From(aClient);
 
         MOZ_ASSERT(aNPZC);
         auto npzc = NativePanZoomController::LocalRef(
                 jni::GetGeckoThreadEnv(),
                 NativePanZoomController::Ref::From(aNPZC));
         mWindow->mNPZCSupport.Attach(npzc, mWindow, npzc);
 
-        layerClient->OnGeckoReady();
-
-        if (resetting) {
-            // Since we are re-linking the new java objects to Gecko, we need
-            // to get the viewport from the compositor (since the Java copy was
-            // thrown away) and we do that by setting the first-paint flag.
-            if (RefPtr<CompositorBridgeParent> bridge = mWindow->GetCompositorBridgeParent()) {
-                bridge->ForceIsFirstPaint();
-            }
+        mLayerClient->OnGeckoReady();
+
+        // Set the first-paint flag so that we (re-)link any new Java objects
+        // to Gecko, co-ordinate viewports, etc.
+        if (RefPtr<CompositorBridgeParent> bridge = mWindow->GetCompositorBridgeParent()) {
+            bridge->ForceIsFirstPaint();
         }
     }
 
     void OnSizeChanged(int32_t aWindowWidth, int32_t aWindowHeight,
                        int32_t aScreenWidth, int32_t aScreenHeight)
     {
         MOZ_ASSERT(NS_IsMainThread());
         if (!mWindow) {
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -327,24 +327,35 @@ protected:
         return NS_OK;
       }
 
     private:
       RefPtr<ThenValueBase> mThenValue;
       RefPtr<MozPromise> mPromise;
     };
 
-    explicit ThenValueBase(AbstractThread* aResponseTarget, const char* aCallSite)
-      : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {}
+    ThenValueBase(AbstractThread* aResponseTarget,
+                  const char* aCallSite,
+                  bool aInitCompletionPromise = false)
+      : mResponseTarget(aResponseTarget)
+      , mCallSite(aCallSite)
+      , mInitCompletionPromise(aInitCompletionPromise)
+    {
+      if (mInitCompletionPromise) {
+        mCompletionPromise = new MozPromise::Private(
+          "<completion promise>", true /* aIsCompletionPromise */);
+      }
+    }
 
     MozPromise* CompletionPromise() override
     {
-      MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
+      MOZ_DIAGNOSTIC_ASSERT(mInitCompletionPromise ||
+                            mResponseTarget->IsCurrentThreadIn());
       MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
-      if (!mCompletionPromise) {
+      if (!mInitCompletionPromise && !mCompletionPromise) {
         mCompletionPromise = new MozPromise::Private(
           "<completion promise>", true /* aIsCompletionPromise */);
       }
       return mCompletionPromise;
     }
 
     void AssertIsDead() override
     {
@@ -428,16 +439,20 @@ protected:
 
     // Declaring RefPtr<MozPromise::Private> here causes build failures
     // on MSVC because MozPromise::Private is only forward-declared at this
     // point. This hack can go away when we inline-declare MozPromise::Private,
     // which is blocked on the B2G ICS compiler being too old.
     RefPtr<MozPromise> mCompletionPromise;
 
     const char* mCallSite;
+
+    // True if mCompletionPromise should be initialized in the constructor
+    // to make CompletionPromise() thread-safe.
+    const bool mInitCompletionPromise;
   };
 
   /*
    * We create two overloads for invoking Resolve/Reject Methods so as to
    * make the resolve/reject value argument "optional".
    */
 
   template<typename ThisType, typename MethodType, typename ValueType>
@@ -479,18 +494,18 @@ protected:
   }
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
   class MethodThenValue : public ThenValueBase
   {
   public:
     MethodThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal,
                     ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
-                    const char* aCallSite)
-      : ThenValueBase(aResponseTarget, aCallSite)
+                    const char* aCallSite, bool aInitCompletionPromise = false)
+      : ThenValueBase(aResponseTarget, aCallSite, aInitCompletionPromise)
       , mThisVal(aThisVal)
       , mResolveMethod(aResolveMethod)
       , mRejectMethod(aRejectMethod) {}
 
   virtual void Disconnect() override
   {
     ThenValueBase::Disconnect();
 
@@ -528,18 +543,19 @@ protected:
   // NB: We could use std::function here instead of a template if it were supported. :-(
   template<typename ResolveFunction, typename RejectFunction>
   class FunctionThenValue : public ThenValueBase
   {
   public:
     FunctionThenValue(AbstractThread* aResponseTarget,
                       ResolveFunction&& aResolveFunction,
                       RejectFunction&& aRejectFunction,
-                      const char* aCallSite)
-      : ThenValueBase(aResponseTarget, aCallSite)
+                      const char* aCallSite,
+                      bool aInitCompletionPromise = false)
+      : ThenValueBase(aResponseTarget, aCallSite, aInitCompletionPromise)
     {
       mResolveFunction.emplace(Move(aResolveFunction));
       mRejectFunction.emplace(Move(aRejectFunction));
     }
 
   virtual void Disconnect() override
   {
     ThenValueBase::Disconnect();
@@ -598,34 +614,69 @@ public:
       mThenValues.AppendElement(aThenValue);
     }
   }
 
 public:
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
   RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
-                         ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
+                       ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
   {
     RefPtr<ThenValueBase> thenValue = new MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>(
                                               aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite);
     ThenInternal(aResponseThread, thenValue, aCallSite);
     return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
   }
 
   template<typename ResolveFunction, typename RejectFunction>
   RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite,
-                         ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+                       ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
   {
     RefPtr<ThenValueBase> thenValue = new FunctionThenValue<ResolveFunction, RejectFunction>(aResponseThread,
                                               Move(aResolveFunction), Move(aRejectFunction), aCallSite);
     ThenInternal(aResponseThread, thenValue, aCallSite);
     return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
   }
 
+  // Equivalent to Then(target, ...)->CompletionPromise()
+  // without the restriction that CompletionPromise() must be called on the
+  // |target| thread. So ThenPromise() can be called on any thread as Then().
+  // The syntax is close to JS promise and makes promise chaining easier
+  // where you can do: p->ThenPromise()->ThenPromise()->ThenPromise();
+  //
+  // Note you would have to call Then() instead when the result needs to be held
+  // by a MozPromiseRequestHolder for future disconnection.
+  //
+  // TODO: replace Then()->CompletionPromise() with ThenPromise() and
+  // stop exposing CompletionPromise() to the client code.
+  template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+  MOZ_MUST_USE RefPtr<MozPromise>
+  ThenPromise(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
+              ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
+  {
+    using ThenType = MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>;
+    RefPtr<ThenValueBase> thenValue = new ThenType(aResponseThread, aThisVal, aResolveMethod,
+      aRejectMethod, aCallSite, true /* aInitCompletionPromise */);
+    ThenInternal(aResponseThread, thenValue, aCallSite);
+    return thenValue->CompletionPromise();
+  }
+
+  template<typename ResolveFunction, typename RejectFunction>
+  MOZ_MUST_USE RefPtr<MozPromise>
+  ThenPromise(AbstractThread* aResponseThread, const char* aCallSite,
+              ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+  {
+    using ThenType = FunctionThenValue<ResolveFunction, RejectFunction>;
+    RefPtr<ThenValueBase> thenValue = new ThenType(aResponseThread, Move(aResolveFunction),
+      Move(aRejectFunction), aCallSite, true /* aInitCompletionPromise */);
+    ThenInternal(aResponseThread, thenValue, aCallSite);
+    return thenValue->CompletionPromise();
+  }
+
   void ChainTo(already_AddRefed<Private> aChainedPromise, const char* aCallSite)
   {
     MutexAutoLock lock(mMutex);
     MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
     mHaveRequest = true;
     RefPtr<Private> chainedPromise = aChainedPromise;
     PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
                 aCallSite, this, chainedPromise.get(), (int) IsPending());