Merge m-c to autoland
authorPhil Ringnalda <philringnalda@gmail.com>
Wed, 30 Nov 2016 19:24:21 -0800
changeset 324895 94f1a2b6072dc6e52f00e1cc4acd5f51be79c531
parent 324888 cd4cdcc9ad6c45dad8b8d8c0d40e459db2bca8a1 (current diff)
parent 324894 b393d76118f30a7e53fc43daa8b782aaca8445dd (diff)
child 324896 315a93b13ee0a6763e5650323943f9cc95d7c2a6
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
milestone53.0a1
Merge m-c to autoland
--- 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/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) {