Merge m-i to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 10 Sep 2016 00:14:21 -0700
changeset 313438 a07fb64ae0a7dc76aa0f8e7067ed169baf11ef7c
parent 313420 5ec8f8f7a7006274f4fe764b0525627179d49d2f (current diff)
parent 313437 1fcda39e5626b8e35d7218303fd66f7c7d4d68ab (diff)
child 313439 42840add3cde53efe19bac0b06bc886ccae49a10
child 313444 4c66e0814004e1226362e63581d10c941961cc4c
child 313456 0e8f7203191d83d7adc0bd4b49a6e8ccaf1463ee
child 313470 cd2f33114ac415889028969fa92ea2e803a1d2be
push id30682
push userphilringnalda@gmail.com
push dateSat, 10 Sep 2016 07:14:41 +0000
treeherdermozilla-central@a07fb64ae0a7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
first release with
nightly linux32
a07fb64ae0a7 / 51.0a1 / 20160910030426 / files
nightly linux64
a07fb64ae0a7 / 51.0a1 / 20160910030426 / files
nightly mac
a07fb64ae0a7 / 51.0a1 / 20160910030426 / files
nightly win32
a07fb64ae0a7 / 51.0a1 / 20160910030426 / files
nightly win64
a07fb64ae0a7 / 51.0a1 / 20160910030426 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-i to m-c, a=merge
toolkit/crashreporter/test/unit/test_crash_rust_panic.js
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -213,16 +213,21 @@ function onSearchSubmit(aEvent)
   gContentSearchController.search(aEvent);
 }
 
 
 var gContentSearchController;
 
 function setupSearch()
 {
+  // Set submit button label for when CSS background are disabled (e.g.
+  // high contrast mode).
+  document.getElementById("searchSubmit").value =
+    document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
+
   // The "autofocus" attribute doesn't focus the form element
   // immediately when the element is first drawn, so the
   // attribute is also used for styling when the page first loads.
   searchText = document.getElementById("searchText");
   searchText.addEventListener("blur", function searchText_onBlur() {
     searchText.removeEventListener("blur", searchText_onBlur);
     searchText.removeAttribute("autofocus");
   });
--- a/browser/base/content/abouthome/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -39,17 +39,17 @@
     <div id="topSection">
       <div id="brandLogo"></div>
 
       <div id="searchIconAndTextContainer">
         <div id="searchIcon"/>
         <input type="text" name="q" value="" id="searchText" maxlength="256"
                aria-label="&contentSearchInput.label;" autofocus="autofocus"
                dir="auto"/>
-        <input id="searchSubmit" type="button" value="&#x25b6;" onclick="onSearchSubmit(event)"
+        <input id="searchSubmit" type="button" onclick="onSearchSubmit(event)"
                title="&contentSearchSubmit.tooltip;"/>
       </div>
 
       <div id="snippetContainer">
         <div id="defaultSnippets" hidden="true">
           <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
           <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
         </div>
--- a/browser/base/content/newtab/newTab.xhtml
+++ b/browser/base/content/newtab/newTab.xhtml
@@ -66,17 +66,17 @@
       </div>
     </div>
 
     <div id="newtab-search-container">
       <div id="newtab-search-form">
         <div id="newtab-search-icon"/>
         <input type="text" name="q" value="" id="newtab-search-text"
              aria-label="&contentSearchInput.label;" maxlength="256" dir="auto"/>
-        <input id="newtab-search-submit" type="button" value="&#x25b6;"
+        <input id="newtab-search-submit" type="button"
              title="&contentSearchSubmit.tooltip;"/>
       </div>
     </div>
 
     <div id="newtab-horizontal-margin">
       <div class="newtab-side-margin"/>
       <div id="newtab-grid">
       </div>
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -109,16 +109,21 @@ var gPage = {
    * is/gets enabled.
    */
   _init: function Page_init() {
     if (this._initialized)
       return;
 
     this._initialized = true;
 
+    // Set submit button label for when CSS background are disabled (e.g.
+    // high contrast mode).
+    document.getElementById("newtab-search-submit").value =
+      document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
+
     // Initialize search.
     gSearch.init();
 
     if (document.hidden) {
       addEventListener("visibilitychange", this);
     } else {
       setTimeout(_ => this.onPageFirstVisible());
     }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -356,16 +356,18 @@
 @RESPATH@/components/BrowserElementParent.manifest
 @RESPATH@/components/BrowserElementParent.js
 @RESPATH@/components/BrowserElementProxy.manifest
 @RESPATH@/components/BrowserElementProxy.js
 @RESPATH@/components/FeedProcessor.manifest
 @RESPATH@/components/FeedProcessor.js
 @RESPATH@/components/PackagedAppUtils.js
 @RESPATH@/components/PackagedAppUtils.manifest
+@RESPATH@/components/WellKnownOpportunisticUtils.js
+@RESPATH@/components/WellKnownOpportunisticUtils.manifest
 #ifndef XP_MACOSX
 ; OSX uses native platform impl.  Windows, Linux, and Android uses fallback JS impl.
 @BINPATH@/components/nsDNSServiceDiscovery.manifest
 @BINPATH@/components/nsDNSServiceDiscovery.js
 #endif
 @RESPATH@/browser/components/BrowserFeeds.manifest
 @RESPATH@/browser/components/FeedConverter.js
 @RESPATH@/browser/components/FeedWriter.js
--- a/dom/crypto/CryptoKey.cpp
+++ b/dom/crypto/CryptoKey.cpp
@@ -1,21 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "pk11pub.h"
+#include "CryptoKey.h"
+
+#include "ScopedNSSTypes.h"
 #include "cryptohi.h"
-#include "nsNSSComponent.h"
-#include "ScopedNSSTypes.h"
-#include "mozilla/dom/CryptoKey.h"
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/dom/SubtleCryptoBinding.h"
 #include "mozilla/dom/ToJSValue.h"
+#include "nsNSSComponent.h"
+#include "pk11pub.h"
 
 // Templates taken from security/nss/lib/cryptohi/seckey.c
 // These would ideally be exported by NSS and until that
 // happens we have to keep our own copies.
 const SEC_ASN1Template SECKEY_DHPublicKeyTemplate[] = {
     { SEC_ASN1_INTEGER, offsetof(SECKEYPublicKey,u.dh.publicValue), },
     { 0, }
 };
@@ -356,17 +358,17 @@ CryptoKey::AddPublicKeyData(SECKEYPublic
     // PrivateKeyFromPrivateKeyTemplate sets the ID.
     { CKA_ID,               nullptr,              0 },
     { CKA_EC_PARAMS,        params->data,         params->len },
     { CKA_EC_POINT,         point->data,          point->len },
     { CKA_VALUE,            value->data,          value->len },
   };
 
   mPrivateKey = PrivateKeyFromPrivateKeyTemplate(keyTemplate,
-                                                 PR_ARRAY_SIZE(keyTemplate));
+                                                 ArrayLength(keyTemplate));
   NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR);
 
   return NS_OK;
 }
 
 void
 CryptoKey::ClearUsages()
 {
@@ -792,17 +794,17 @@ CryptoKey::PrivateKeyFromJwk(const JsonW
       // PrivateKeyFromPrivateKeyTemplate sets the ID.
       { CKA_ID,               nullptr,              0 },
       { CKA_EC_PARAMS,        params->data,         params->len },
       { CKA_EC_POINT,         ecPoint->data,        ecPoint->len },
       { CKA_VALUE,            (void*) d.Elements(), (CK_ULONG) d.Length() },
     };
 
     return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
-                                            PR_ARRAY_SIZE(keyTemplate));
+                                            ArrayLength(keyTemplate));
   }
 
   if (aJwk.mKty.EqualsLiteral(JWK_TYPE_RSA)) {
     // Verify that all of the required parameters are present
     CryptoBuffer n, e, d, p, q, dp, dq, qi;
     if (!aJwk.mN.WasPassed() || NS_FAILED(n.FromJwkBase64(aJwk.mN.Value())) ||
         !aJwk.mE.WasPassed() || NS_FAILED(e.FromJwkBase64(aJwk.mE.Value())) ||
         !aJwk.mD.WasPassed() || NS_FAILED(d.FromJwkBase64(aJwk.mD.Value())) ||
@@ -835,17 +837,17 @@ CryptoKey::PrivateKeyFromJwk(const JsonW
       { CKA_PRIME_1,          (void*) p.Elements(),  (CK_ULONG) p.Length() },
       { CKA_PRIME_2,          (void*) q.Elements(),  (CK_ULONG) q.Length() },
       { CKA_EXPONENT_1,       (void*) dp.Elements(), (CK_ULONG) dp.Length() },
       { CKA_EXPONENT_2,       (void*) dq.Elements(), (CK_ULONG) dq.Length() },
       { CKA_COEFFICIENT,      (void*) qi.Elements(), (CK_ULONG) qi.Length() },
     };
 
     return PrivateKeyFromPrivateKeyTemplate(keyTemplate,
-                                            PR_ARRAY_SIZE(keyTemplate));
+                                            ArrayLength(keyTemplate));
   }
 
   return nullptr;
 }
 
 bool ReadAndEncodeAttribute(SECKEYPrivateKey* aKey,
                             CK_ATTRIBUTE_TYPE aAttribute,
                             Optional<nsString>& aDst)
--- a/dom/crypto/WebCryptoCommon.h
+++ b/dom/crypto/WebCryptoCommon.h
@@ -2,21 +2,22 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_WebCryptoCommon_h
 #define mozilla_dom_WebCryptoCommon_h
 
-#include "pk11pub.h"
-#include "nsString.h"
+#include "js/StructuredClone.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/dom/CryptoBuffer.h"
 #include "nsContentUtils.h"
-#include "mozilla/dom/CryptoBuffer.h"
-#include "js/StructuredClone.h"
+#include "nsString.h"
+#include "pk11pub.h"
 
 // WebCrypto algorithm names
 #define WEBCRYPTO_ALG_AES_CBC       "AES-CBC"
 #define WEBCRYPTO_ALG_AES_CTR       "AES-CTR"
 #define WEBCRYPTO_ALG_AES_GCM       "AES-GCM"
 #define WEBCRYPTO_ALG_AES_KW        "AES-KW"
 #define WEBCRYPTO_ALG_SHA1          "SHA-1"
 #define WEBCRYPTO_ALG_SHA256        "SHA-256"
@@ -101,25 +102,27 @@
 #define JWK_USE_SIG                 "sig"
 
 // Define an unknown mechanism type
 #define UNKNOWN_CK_MECHANISM        CKM_VENDOR_DEFINED+1
 
 // python security/pkix/tools/DottedOIDToCode.py id-ecDH 1.3.132.112
 static const uint8_t id_ecDH[] = { 0x2b, 0x81, 0x04, 0x70 };
 const SECItem SEC_OID_DATA_EC_DH = { siBuffer, (unsigned char*)id_ecDH,
-                                     PR_ARRAY_SIZE(id_ecDH) };
+                                     static_cast<unsigned int>(
+                                       mozilla::ArrayLength(id_ecDH)) };
 
 // python security/pkix/tools/DottedOIDToCode.py dhKeyAgreement 1.2.840.113549.1.3.1
 static const uint8_t dhKeyAgreement[] = {
   0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x03, 0x01
 };
 const SECItem SEC_OID_DATA_DH_KEY_AGREEMENT = { siBuffer,
                                                 (unsigned char*)dhKeyAgreement,
-                                                PR_ARRAY_SIZE(dhKeyAgreement) };
+                                                static_cast<unsigned int>(
+                                                  mozilla::ArrayLength(dhKeyAgreement)) };
 
 namespace mozilla {
 namespace dom {
 
 // Helper functions for structured cloning
 inline bool
 ReadString(JSStructuredCloneReader* aReader, nsString& aString)
 {
--- a/dom/flyweb/FlyWebPublishedServer.cpp
+++ b/dom/flyweb/FlyWebPublishedServer.cpp
@@ -536,19 +536,25 @@ FlyWebPublishedServerParent::HandleEvent
   }
 
   if (type.EqualsLiteral("websocket")) {
     RefPtr<InternalRequest> request =
       static_cast<FlyWebWebSocketEvent*>(aEvent)->Request()->GetInternalRequest();
     uint64_t id = mNextRequestId++;
     mPendingRequests.Put(id, request);
 
+    nsTArray<PNeckoParent*> neckoParents;
+    Manager()->ManagedPNeckoParent(neckoParents);
+    if (neckoParents.Length() != 1) {
+      MOZ_CRASH("Expected exactly 1 PNeckoParent instance per PNeckoChild");
+    }
+
     RefPtr<TransportProviderParent> provider =
       static_cast<TransportProviderParent*>(
-        mozilla::net::gNeckoParent->SendPTransportProviderConstructor());
+        neckoParents[0]->SendPTransportProviderConstructor());
 
     IPCInternalRequest ipcReq;
     request->ToIPC(&ipcReq);
     Unused << SendWebSocketRequest(ipcReq, id, provider);
 
     mPendingTransportProviders.Put(id, provider.forget());
     return NS_OK;
   }
--- a/dom/media/ogg/OpusParser.cpp
+++ b/dom/media/ogg/OpusParser.cpp
@@ -81,18 +81,19 @@ bool OpusParser::DecodeHeader(unsigned c
         OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: too many channels (%d) for"
                            " mapping family 0.", mChannels));
         return false;
       }
       mStreams = 1;
       mCoupledStreams = mChannels - 1;
       mMappingTable[0] = 0;
       mMappingTable[1] = 1;
-    } else if (mChannelMapping == 1) {
-      // Currently only up to 8 channels are defined for mapping family 1
+    } else if (mChannelMapping == 1 || mChannelMapping == 255) {
+      // Currently only up to 8 channels are defined for mapping family 1 and we
+      // only supports only up to 8 channels for mapping family 255.
       if (mChannels>8) {
         OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: too many channels (%d) for"
                            " mapping family 1.", mChannels));
         return false;
       }
       if (aLength>static_cast<unsigned>(20+mChannels)) {
         mStreams = aData[19];
         mCoupledStreams = aData[20];
--- a/js/src/asmjs/AsmJS.cpp
+++ b/js/src/asmjs/AsmJS.cpp
@@ -7845,23 +7845,22 @@ CheckBuffer(JSContext* cx, const AsmJSMe
 #ifdef WASM_HUGE_MEMORY
         bool needGuard = true;
 #else
         bool needGuard = metadata.usesSimd;
 #endif
         Rooted<ArrayBufferObject*> arrayBuffer(cx, &buffer->as<ArrayBufferObject>());
         if (!ArrayBufferObject::prepareForAsmJS(cx, arrayBuffer, needGuard))
             return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
-
-        MOZ_ASSERT(arrayBuffer->isAsmJSMalloced() || arrayBuffer->isWasmMapped());
     } else {
         if (!buffer->as<SharedArrayBufferObject>().isPreparedForAsmJS())
             return LinkFail(cx, "SharedArrayBuffer must be created with wasm test mode enabled");
     }
 
+    MOZ_ASSERT(buffer->isPreparedForAsmJS());
     return true;
 }
 
 static bool
 GetImports(JSContext* cx, const AsmJSMetadata& metadata, HandleValue globalVal,
            HandleValue importVal, MutableHandle<FunctionVector> funcImports, ValVector* valImports)
 {
     Rooted<FunctionVector> ffis(cx, FunctionVector(cx));
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -278,23 +278,36 @@ Instance::callImport_f64(Instance* insta
         return false;
 
     return ToNumber(cx, rval, (double*)argv);
 }
 
 /* static */ uint32_t
 Instance::growMemory_i32(Instance* instance, uint32_t delta)
 {
-    return instance->growMemory(delta);
+    MOZ_ASSERT(!instance->isAsmJS());
+
+    JSContext* cx = instance->cx();
+    RootedWasmMemoryObject memory(cx, instance->memory_);
+
+    uint32_t ret = WasmMemoryObject::grow(memory, delta, cx);
+
+    // If there has been a moving grow, this Instance should have been notified.
+    MOZ_RELEASE_ASSERT(instance->tlsData_.memoryBase ==
+                       instance->memory_->buffer().dataPointerEither());
+
+    return ret;
 }
 
 /* static */ uint32_t
 Instance::currentMemory_i32(Instance* instance)
 {
-    return instance->currentMemory();
+    uint32_t byteLength = instance->memoryLength();
+    MOZ_ASSERT(byteLength % wasm::PageSize == 0);
+    return byteLength / wasm::PageSize;
 }
 
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    UniqueCode code,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
                    Handle<FunctionVector> funcImports,
@@ -474,17 +487,17 @@ Instance::memoryBase() const
     MOZ_ASSERT(metadata().usesMemory());
     MOZ_ASSERT(tlsData_.memoryBase == memory_->buffer().dataPointerEither());
     return memory_->buffer().dataPointerEither();
 }
 
 size_t
 Instance::memoryLength() const
 {
-    return memory_->buffer().wasmActualByteLength();
+    return memory_->buffer().byteLength();
 }
 
 template<typename T>
 static JSObject*
 CreateCustomNaNObject(JSContext* cx, T* addr)
 {
     MOZ_ASSERT(IsNaN(*addr));
 
@@ -771,34 +784,16 @@ Instance::callExport(JSContext* cx, uint
     }
 
     if (retObj)
         args.rval().set(ObjectValue(*retObj));
 
     return true;
 }
 
-uint32_t
-Instance::currentMemory()
-{
-    MOZ_RELEASE_ASSERT(memory_);
-    uint32_t byteLength = memory_->buffer().wasmActualByteLength();
-    MOZ_ASSERT(byteLength % wasm::PageSize == 0);
-    return byteLength / wasm::PageSize;
-}
-
-uint32_t
-Instance::growMemory(uint32_t delta)
-{
-    MOZ_ASSERT(!isAsmJS());
-    uint32_t ret = memory_->grow(delta);
-    MOZ_RELEASE_ASSERT(tlsData_.memoryBase == memory_->buffer().dataPointerEither());
-    return ret;
-}
-
 void
 Instance::onMovingGrow(uint8_t* prevMemoryBase)
 {
     MOZ_ASSERT(!isAsmJS());
     ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>();
     tlsData_.memoryBase = buffer.dataPointer();
     code_->segment().onMovingGrow(prevMemoryBase, metadata(), buffer);
 }
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -102,22 +102,16 @@ class Instance
 
     WasmInstanceObject* object() const;
 
     // Execute the given export given the JS call arguments, storing the return
     // value in args.rval.
 
     MOZ_MUST_USE bool callExport(JSContext* cx, uint32_t funcDefIndex, CallArgs args);
 
-    // These methods implement their respective wasm operator but may also be
-    // called via the Memory JS API.
-
-    uint32_t currentMemory();
-    uint32_t growMemory(uint32_t delta);
-
     // Initially, calls to imports in wasm code call out through the generic
     // callImport method. If the imported callee gets JIT compiled and the types
     // match up, callImport will patch the code to instead call through a thunk
     // directly into the JIT code. If the JIT code is released, the Instance must
     // be notified so it can go back to the generic callImport.
 
     void deoptimizeImportExit(uint32_t funcImportIndex);
 
--- a/js/src/asmjs/WasmJS.cpp
+++ b/js/src/asmjs/WasmJS.cpp
@@ -831,38 +831,73 @@ WasmMemoryObject::construct(JSContext* c
 }
 
 static bool
 IsMemory(HandleValue v)
 {
     return v.isObject() && v.toObject().is<WasmMemoryObject>();
 }
 
-static bool
-MemoryBufferGetterImpl(JSContext* cx, const CallArgs& args)
+/* static */ bool
+WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args)
 {
     args.rval().setObject(args.thisv().toObject().as<WasmMemoryObject>().buffer());
     return true;
 }
 
-static bool
-MemoryBufferGetter(JSContext* cx, unsigned argc, Value* vp)
+/* static */ bool
+WasmMemoryObject::bufferGetter(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod<IsMemory, MemoryBufferGetterImpl>(cx, args);
+    return CallNonGenericMethod<IsMemory, bufferGetterImpl>(cx, args);
 }
 
 const JSPropertySpec WasmMemoryObject::properties[] =
 {
-    JS_PSG("buffer", MemoryBufferGetter, 0),
+    JS_PSG("buffer", WasmMemoryObject::bufferGetter, 0),
     JS_PS_END
 };
 
+/* static */ bool
+WasmMemoryObject::growImpl(JSContext* cx, const CallArgs& args)
+{
+    RootedWasmMemoryObject memory(cx, &args.thisv().toObject().as<WasmMemoryObject>());
+
+    double deltaDbl;
+    if (!ToInteger(cx, args.get(0), &deltaDbl))
+        return false;
+
+    if (deltaDbl < 0 || deltaDbl > UINT32_MAX) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW);
+        return false;
+    }
+
+    uint32_t ret = grow(memory, uint32_t(deltaDbl), cx);
+
+    if (ret == uint32_t(-1)) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW);
+        return false;
+    }
+
+    args.rval().setInt32(ret);
+    return true;
+}
+
+/* static */ bool
+WasmMemoryObject::grow(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return CallNonGenericMethod<IsMemory, growImpl>(cx, args);
+}
+
 const JSFunctionSpec WasmMemoryObject::methods[] =
-{ JS_FS_END };
+{
+    JS_FN("grow", WasmMemoryObject::grow, 1, 0),
+    JS_FS_END
+};
 
 ArrayBufferObjectMaybeShared&
 WasmMemoryObject::buffer() const
 {
     return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObjectMaybeShared>();
 }
 
 bool
@@ -916,53 +951,59 @@ WasmMemoryObject::addMovingGrowObserver(
     if (!observers->putNew(instance)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
-uint32_t
-WasmMemoryObject::grow(uint32_t delta)
+/* static */ uint32_t
+WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta, JSContext* cx)
 {
-    ArrayBufferObject &buf = buffer().as<ArrayBufferObject>();
+    RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>());
 
-    MOZ_ASSERT(buf.wasmActualByteLength() % PageSize == 0);
-    uint32_t oldNumPages = buf.wasmActualByteLength() / PageSize;
+    MOZ_ASSERT(oldBuf->byteLength() % PageSize == 0);
+    uint32_t oldNumPages = oldBuf->byteLength() / PageSize;
 
     CheckedInt<uint32_t> newSize = oldNumPages;
     newSize += delta;
     newSize *= PageSize;
     if (!newSize.isValid())
         return -1;
 
-    if (Maybe<uint32_t> maxSize = buf.wasmMaxSize()) {
+    RootedArrayBufferObject newBuf(cx);
+    uint8_t* prevMemoryBase = nullptr;
+
+    if (Maybe<uint32_t> maxSize = oldBuf->wasmMaxSize()) {
         if (newSize.value() > maxSize.value())
             return -1;
 
-        if (!buf.wasmGrowToSizeInPlace(newSize.value()))
+        if (!ArrayBufferObject::wasmGrowToSizeInPlace(newSize.value(), oldBuf, &newBuf, cx))
             return -1;
     } else {
 #ifdef WASM_HUGE_MEMORY
-        if (!buf.wasmGrowToSizeInPlace(newSize.value()))
+        if (!ArrayBufferObject::wasmGrowToSizeInPlace(newSize.value(), oldBuf, &newBuf, cx))
             return -1;
 #else
-        MOZ_ASSERT(movingGrowable());
-
-        uint8_t* prevMemoryBase = buf.dataPointer();
-
-        if (!buf.wasmMovingGrowToSize(newSize.value()))
+        MOZ_ASSERT(memory->movingGrowable());
+        prevMemoryBase = oldBuf->dataPointer();
+        if (!ArrayBufferObject::wasmMovingGrowToSize(newSize.value(), oldBuf, &newBuf, cx))
             return -1;
+#endif
+    }
 
-        if (hasObservers()) {
-            for (InstanceSet::Range r = observers().all(); !r.empty(); r.popFront())
-                r.front()->instance().onMovingGrow(prevMemoryBase);
-        }
-#endif
+    memory->setReservedSlot(BUFFER_SLOT, ObjectValue(*newBuf));
+
+    // Only notify moving-grow-observers after the BUFFER_SLOT has been updated
+    // since observers will call buffer().
+    if (memory->hasObservers()) {
+        MOZ_ASSERT(prevMemoryBase);
+        for (InstanceSet::Range r = memory->observers().all(); !r.empty(); r.popFront())
+            r.front()->instance().onMovingGrow(prevMemoryBase);
     }
 
     return oldNumPages;
 }
 
 // ============================================================================
 // WebAssembly.Table class and methods
 
--- a/js/src/asmjs/WasmJS.h
+++ b/js/src/asmjs/WasmJS.h
@@ -158,16 +158,20 @@ class WasmInstanceObject : public Native
 // or SharedArrayBuffer object which owns the actual memory.
 
 class WasmMemoryObject : public NativeObject
 {
     static const unsigned BUFFER_SLOT = 0;
     static const unsigned OBSERVERS_SLOT = 1;
     static const ClassOps classOps_;
     static void finalize(FreeOp* fop, JSObject* obj);
+    static bool bufferGetterImpl(JSContext* cx, const CallArgs& args);
+    static bool bufferGetter(JSContext* cx, unsigned argc, Value* vp);
+    static bool growImpl(JSContext* cx, const CallArgs& args);
+    static bool grow(JSContext* cx, unsigned argc, Value* vp);
 
     using InstanceSet = GCHashSet<ReadBarrieredWasmInstanceObject,
                                   MovableCellHasher<ReadBarrieredWasmInstanceObject>,
                                   SystemAllocPolicy>;
     using WeakInstanceSet = JS::WeakCache<InstanceSet>;
     bool hasObservers() const;
     WeakInstanceSet& observers() const;
     WeakInstanceSet* getOrCreateObservers(JSContext* cx);
@@ -181,17 +185,17 @@ class WasmMemoryObject : public NativeOb
 
     static WasmMemoryObject* create(ExclusiveContext* cx,
                                     Handle<ArrayBufferObjectMaybeShared*> buffer,
                                     HandleObject proto);
     ArrayBufferObjectMaybeShared& buffer() const;
 
     bool movingGrowable() const;
     bool addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance);
-    uint32_t grow(uint32_t delta);
+    static uint32_t grow(HandleWasmMemoryObject memory, uint32_t delta, JSContext* cx);
 };
 
 // The class of WebAssembly.Table. A WasmTableObject holds a refcount on a
 // wasm::Table, allowing a Table to be shared between multiple Instances
 // (eventually between multiple threads).
 
 class WasmTableObject : public NativeObject
 {
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -561,31 +561,31 @@ Module::instantiateMemory(JSContext* cx,
         MOZ_ASSERT(dataSegments_.empty());
         return true;
     }
 
     uint32_t declaredMin = metadata_->minMemoryLength;
     Maybe<uint32_t> declaredMax = metadata_->maxMemoryLength;
 
     if (memory) {
-        RootedArrayBufferObjectMaybeShared buffer(cx, &memory->buffer());
-        MOZ_RELEASE_ASSERT(buffer->is<SharedArrayBufferObject>() ||
-                           buffer->as<ArrayBufferObject>().isWasm());
+        ArrayBufferObjectMaybeShared& buffer = memory->buffer();
+        MOZ_ASSERT_IF(metadata_->isAsmJS(), buffer.isPreparedForAsmJS());
+        MOZ_ASSERT_IF(!metadata_->isAsmJS(), buffer.as<ArrayBufferObject>().isWasm());
 
-        uint32_t actualLength = buffer->wasmActualByteLength();
+        uint32_t actualLength = buffer.byteLength();
         if (actualLength < declaredMin || actualLength > declaredMax.valueOr(UINT32_MAX)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
             return false;
         }
 
         if (metadata_->isAsmJS()) {
             MOZ_ASSERT(IsValidAsmJSHeapLength(actualLength));
-            MOZ_ASSERT(actualLength == buffer->wasmMaxSize().value());
+            MOZ_ASSERT(actualLength == buffer.wasmMaxSize().value());
         } else {
-            Maybe<uint32_t> actualMax = buffer->as<ArrayBufferObject>().wasmMaxSize();
+            Maybe<uint32_t> actualMax = buffer.as<ArrayBufferObject>().wasmMaxSize();
             if (declaredMax.isSome() != actualMax.isSome() || declaredMax < actualMax) {
                 JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
                 return false;
             }
         }
     } else {
         MOZ_ASSERT(!metadata_->isAsmJS());
         MOZ_ASSERT(metadata_->memoryUsage == MemoryUsage::Unshared);
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -16,16 +16,22 @@
 #include "js/HashTable.h"
 #include "js/Value.h"
 #include "vm/EnvironmentObject.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/Symbol.h"
 
 namespace js {
 
+bool
+RuntimeFromMainThreadIsHeapMajorCollecting(JS::shadow::Zone* shadowZone)
+{
+    return shadowZone->runtimeFromMainThread()->isHeapMajorCollecting();
+}
+
 #ifdef DEBUG
 
 static bool
 IsMarkedBlack(NativeObject* obj)
 {
     // Note: we assume conservatively that Nursery things will be live.
     if (!obj->isTenured())
         return true;
@@ -53,22 +59,16 @@ HeapSlot::preconditionForWriteBarrierPos
                          ? obj->getSlotAddressUnchecked(slot)->get() == target
                          : static_cast<HeapSlot*>(obj->getDenseElements() + slot)->get() == target;
     bool isBlackToGray = target.isMarkable() &&
                          IsMarkedBlack(obj) && JS::GCThingIsMarkedGray(JS::GCCellPtr(target));
     return isCorrectSlot && !isBlackToGray;
 }
 
 bool
-RuntimeFromMainThreadIsHeapMajorCollecting(JS::shadow::Zone* shadowZone)
-{
-    return shadowZone->runtimeFromMainThread()->isHeapMajorCollecting();
-}
-
-bool
 CurrentThreadIsIonCompiling()
 {
     return TlsPerThreadData.get()->ionCompiling;
 }
 
 bool
 CurrentThreadIsIonCompilingSafeForMinorGC()
 {
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -38,20 +38,21 @@ struct Runtime;
 } // namespace shadow
 } // namespace JS
 
 namespace js {
 
 class AutoLockGC;
 class FreeOp;
 
-#ifdef DEBUG
 extern bool
 RuntimeFromMainThreadIsHeapMajorCollecting(JS::shadow::Zone* shadowZone);
 
+#ifdef DEBUG
+
 // Barriers can't be triggered during backend Ion compilation, which may run on
 // a helper thread.
 extern bool
 CurrentThreadIsIonCompiling();
 #endif
 
 // The return value indicates if anything was unmarked.
 extern bool
@@ -1276,31 +1277,39 @@ TenuredCell::isInsideZone(JS::Zone* zone
     return zone == arena()->zone;
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 TenuredCell::readBarrier(TenuredCell* thing)
 {
     MOZ_ASSERT(!CurrentThreadIsIonCompiling());
     MOZ_ASSERT(!isNullLike(thing));
-    if (thing->shadowRuntimeFromAnyThread()->isHeapCollecting())
-        return;
+
+    // It would be good if barriers were never triggered during collection, but
+    // at the moment this can happen e.g. when rekeying tables containing
+    // read-barriered GC things after a moving GC.
+    //
+    // TODO: Fix this and assert we're not collecting if we're on the main
+    // thread.
 
     JS::shadow::Zone* shadowZone = thing->shadowZoneFromAnyThread();
-    MOZ_ASSERT_IF(!CurrentThreadCanAccessRuntime(thing->runtimeFromAnyThread()),
-                  !shadowZone->needsIncrementalBarrier());
-
     if (shadowZone->needsIncrementalBarrier()) {
+        // Barriers are only enabled on the main thread and are disabled while collecting.
         MOZ_ASSERT(!RuntimeFromMainThreadIsHeapMajorCollecting(shadowZone));
         Cell* tmp = thing;
         TraceManuallyBarrieredGenericPointerEdge(shadowZone->barrierTracer(), &tmp, "read barrier");
         MOZ_ASSERT(tmp == thing);
     }
-    if (thing->isMarked(GRAY))
-        UnmarkGrayCellRecursively(thing, thing->getTraceKind());
+
+    if (thing->isMarked(GRAY)) {
+        // There shouldn't be anything marked grey unless we're on the main thread.
+        MOZ_ASSERT(CurrentThreadCanAccessRuntime(thing->runtimeFromAnyThread()));
+        if (!RuntimeFromMainThreadIsHeapMajorCollecting(shadowZone))
+            UnmarkGrayCellRecursively(thing, thing->getTraceKind());
+    }
 }
 
 void
 AssertSafeToSkipBarrier(TenuredCell* thing);
 
 /* static */ MOZ_ALWAYS_INLINE void
 TenuredCell::writeBarrierPre(TenuredCell* thing)
 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/asm.js/testAsmJSWasmMixing.js
@@ -0,0 +1,25 @@
+load(libdir + "asm.js");
+load(libdir + "wasm.js");
+
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const Memory = WebAssembly.Memory;
+
+var asmJS = asmCompile('stdlib', 'ffis', 'buf', USE_ASM + 'var i32 = new stdlib.Int32Array(buf); return {}');
+
+var asmJSBuf = new ArrayBuffer(BUF_MIN);
+asmLink(asmJS, this, null, asmJSBuf);
+
+var wasmMem = evalText('(module (memory 1 1) (export "mem" memory))').exports.mem;
+assertAsmLinkFail(asmJS, this, null, wasmMem.buffer);
+
+if (!getBuildConfiguration().x64 && isSimdAvailable() && this["SIMD"]) {
+    var simdJS = asmCompile('stdlib', 'ffis', 'buf', USE_ASM + 'var i32 = new stdlib.Int32Array(buf); var i32x4 = stdlib.SIMD.Int32x4; return {}');
+    assertAsmLinkFail(simdJS, this, null, asmJSBuf);
+    assertAsmLinkFail(simdJS, this, null, wasmMem.buffer);
+
+    var simdJSBuf = new ArrayBuffer(BUF_MIN);
+    asmLink(simdJS, this, null, simdJSBuf);
+    asmLink(simdJS, this, null, simdJSBuf);  // multiple SIMD.js instantiations succeed
+    assertAsmLinkFail(asmJS, this, null, simdJSBuf);  // but not asm.js
+}
--- a/js/src/jit-test/tests/wasm/jsapi.js
+++ b/js/src/jit-test/tests/wasm/jsapi.js
@@ -1,25 +1,27 @@
 load(libdir + 'wasm.js');
 load(libdir + 'asserts.js');
 
+const WasmPage = 64 * 1024;
+
 const emptyModule = textToBinary('(module)');
 
-// 'WebAssembly' property on global object
+// 'WebAssembly' data property on global object
 const wasmDesc = Object.getOwnPropertyDescriptor(this, 'WebAssembly');
 assertEq(typeof wasmDesc.value, "object");
 assertEq(wasmDesc.writable, true);
 assertEq(wasmDesc.enumerable, false);
 assertEq(wasmDesc.configurable, true);
 
 // 'WebAssembly' object
 assertEq(WebAssembly, wasmDesc.value);
 assertEq(String(WebAssembly), "[object WebAssembly]");
 
-// 'WebAssembly.Module' property
+// 'WebAssembly.Module' data property
 const moduleDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Module');
 assertEq(typeof moduleDesc.value, "function");
 assertEq(moduleDesc.writable, true);
 assertEq(moduleDesc.enumerable, false);
 assertEq(moduleDesc.configurable, true);
 
 // 'WebAssembly.Module' constructor function
 const Module = WebAssembly.Module;
@@ -31,17 +33,17 @@ assertErrorMessage(() => new Module(), T
 assertErrorMessage(() => new Module(undefined), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module(1), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module({}), TypeError, "first argument must be an ArrayBuffer or typed array object");
 assertErrorMessage(() => new Module(new Uint8Array()), /* TODO: WebAssembly.CompileError */ TypeError, /compile error/);
 assertErrorMessage(() => new Module(new ArrayBuffer()), /* TODO: WebAssembly.CompileError */ TypeError, /compile error/);
 assertEq(new Module(emptyModule) instanceof Module, true);
 assertEq(new Module(emptyModule.buffer) instanceof Module, true);
 
-// 'WebAssembly.Module.prototype' property
+// 'WebAssembly.Module.prototype' data property
 const moduleProtoDesc = Object.getOwnPropertyDescriptor(Module, 'prototype');
 assertEq(typeof moduleProtoDesc.value, "object");
 assertEq(moduleProtoDesc.writable, false);
 assertEq(moduleProtoDesc.enumerable, false);
 assertEq(moduleProtoDesc.configurable, false);
 
 // 'WebAssembly.Module.prototype' object
 const moduleProto = Module.prototype;
@@ -50,17 +52,17 @@ assertEq(String(moduleProto), "[object O
 assertEq(Object.getPrototypeOf(moduleProto), Object.prototype);
 
 // 'WebAssembly.Module' instance objects
 const m1 = new Module(emptyModule);
 assertEq(typeof m1, "object");
 assertEq(String(m1), "[object WebAssembly.Module]");
 assertEq(Object.getPrototypeOf(m1), moduleProto);
 
-// 'WebAssembly.Instance' property
+// 'WebAssembly.Instance' data property
 const instanceDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Instance');
 assertEq(typeof instanceDesc.value, "function");
 assertEq(instanceDesc.writable, true);
 assertEq(instanceDesc.enumerable, false);
 assertEq(instanceDesc.configurable, true);
 
 // 'WebAssembly.Instance' constructor function
 const Instance = WebAssembly.Instance;
@@ -69,17 +71,17 @@ assertEq(Instance.length, 1);
 assertEq(Instance.name, "Instance");
 assertErrorMessage(() => Instance(), TypeError, /constructor without new is forbidden/);
 assertErrorMessage(() => new Instance(1), TypeError, "first argument must be a WebAssembly.Module");
 assertErrorMessage(() => new Instance({}), TypeError, "first argument must be a WebAssembly.Module");
 assertErrorMessage(() => new Instance(m1, null), TypeError, "second argument, if present, must be an object");
 assertEq(new Instance(m1) instanceof Instance, true);
 assertEq(new Instance(m1, {}) instanceof Instance, true);
 
-// 'WebAssembly.Instance.prototype' property
+// 'WebAssembly.Instance.prototype' data property
 const instanceProtoDesc = Object.getOwnPropertyDescriptor(Instance, 'prototype');
 assertEq(typeof instanceProtoDesc.value, "object");
 assertEq(instanceProtoDesc.writable, false);
 assertEq(instanceProtoDesc.enumerable, false);
 assertEq(instanceProtoDesc.configurable, false);
 
 // 'WebAssembly.Instance.prototype' object
 const instanceProto = Instance.prototype;
@@ -88,26 +90,24 @@ assertEq(String(instanceProto), "[object
 assertEq(Object.getPrototypeOf(instanceProto), Object.prototype);
 
 // 'WebAssembly.Instance' instance objects
 const i1 = new Instance(m1);
 assertEq(typeof i1, "object");
 assertEq(String(i1), "[object WebAssembly.Instance]");
 assertEq(Object.getPrototypeOf(i1), instanceProto);
 
-// 'WebAssembly.Instance' 'exports' property
+// 'WebAssembly.Instance' 'exports' data property
 const exportsDesc = Object.getOwnPropertyDescriptor(i1, 'exports');
 assertEq(typeof exportsDesc.value, "object");
 assertEq(exportsDesc.writable, true);
 assertEq(exportsDesc.enumerable, true);
 assertEq(exportsDesc.configurable, true);
 
-// TODO: test export object objects are ES6 module namespace objects.
-
-// 'WebAssembly.Memory' property
+// 'WebAssembly.Memory' data property
 const memoryDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Memory');
 assertEq(typeof memoryDesc.value, "function");
 assertEq(memoryDesc.writable, true);
 assertEq(memoryDesc.enumerable, false);
 assertEq(memoryDesc.configurable, true);
 
 // 'WebAssembly.Memory' constructor function
 const Memory = WebAssembly.Memory;
@@ -118,19 +118,19 @@ assertErrorMessage(() => Memory(), TypeE
 assertErrorMessage(() => new Memory(1), TypeError, "first argument must be a memory descriptor");
 assertErrorMessage(() => new Memory({initial:{valueOf() { throw new Error("here")}}}), Error, "here");
 assertErrorMessage(() => new Memory({initial:-1}), TypeError, /bad Memory initial size/);
 assertErrorMessage(() => new Memory({initial:Math.pow(2,32)}), TypeError, /bad Memory initial size/);
 assertErrorMessage(() => new Memory({initial:1, maximum: Math.pow(2,32)/Math.pow(2,14) }), TypeError, /bad Memory maximum size/);
 assertErrorMessage(() => new Memory({initial:2, maximum: 1 }), TypeError, /bad Memory maximum size/);
 assertErrorMessage(() => new Memory({maximum: -1 }), TypeError, /bad Memory maximum size/);
 assertEq(new Memory({initial:1}) instanceof Memory, true);
-assertEq(new Memory({initial:1.5}).buffer.byteLength, 64*1024);
+assertEq(new Memory({initial:1.5}).buffer.byteLength, WasmPage);
 
-// 'WebAssembly.Memory.prototype' property
+// 'WebAssembly.Memory.prototype' data property
 const memoryProtoDesc = Object.getOwnPropertyDescriptor(Memory, 'prototype');
 assertEq(typeof memoryProtoDesc.value, "object");
 assertEq(memoryProtoDesc.writable, false);
 assertEq(memoryProtoDesc.enumerable, false);
 assertEq(memoryProtoDesc.configurable, false);
 
 // 'WebAssembly.Memory.prototype' object
 const memoryProto = Memory.prototype;
@@ -151,19 +151,48 @@ assertEq(bufferDesc.set, undefined);
 assertEq(bufferDesc.enumerable, false);
 assertEq(bufferDesc.configurable, true);
 
 // 'WebAssembly.Memory.prototype.buffer' getter
 const bufferGetter = bufferDesc.get;
 assertErrorMessage(() => bufferGetter.call(), TypeError, /called on incompatible undefined/);
 assertErrorMessage(() => bufferGetter.call({}), TypeError, /called on incompatible Object/);
 assertEq(bufferGetter.call(mem1) instanceof ArrayBuffer, true);
-assertEq(bufferGetter.call(mem1).byteLength, 64 * 1024);
+assertEq(bufferGetter.call(mem1).byteLength, WasmPage);
+
+// 'WebAssembly.Memory.prototype.grow' data property
+const growDesc = Object.getOwnPropertyDescriptor(memoryProto, 'grow');
+assertEq(typeof growDesc.value, "function");
+assertEq(growDesc.enumerable, false);
+assertEq(growDesc.configurable, true);
 
-// 'WebAssembly.Table' property
+// 'WebAssembly.Memory.prototype.grow' method
+const grow = growDesc.value;
+assertEq(grow.length, 1);
+assertErrorMessage(() => grow.call(), TypeError, /called on incompatible undefined/);
+assertErrorMessage(() => grow.call({}), TypeError, /called on incompatible Object/);
+assertErrorMessage(() => grow.call(mem1, -1), Error, /failed to grow memory/);
+assertErrorMessage(() => grow.call(mem1, Math.pow(2,32)), Error, /failed to grow memory/);
+var mem = new Memory({initial:1, maximum:2});
+var buf = mem.buffer;
+assertEq(buf.byteLength, WasmPage);
+assertEq(mem.grow(0), 1);
+assertEq(buf !== mem.buffer, true);
+assertEq(buf.byteLength, 0);
+buf = mem.buffer;
+assertEq(buf.byteLength, WasmPage);
+assertEq(mem.grow(1), 1);
+assertEq(buf !== mem.buffer, true);
+assertEq(buf.byteLength, 0);
+buf = mem.buffer;
+assertEq(buf.byteLength, 2 * WasmPage);
+assertErrorMessage(() => mem.grow(1), Error, /failed to grow memory/);
+assertEq(buf, mem.buffer);
+
+// 'WebAssembly.Table' data property
 const tableDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'Table');
 assertEq(typeof tableDesc.value, "function");
 assertEq(tableDesc.writable, true);
 assertEq(tableDesc.enumerable, false);
 assertEq(tableDesc.configurable, true);
 
 // 'WebAssembly.Table' constructor function
 const Table = WebAssembly.Table;
@@ -176,17 +205,17 @@ assertErrorMessage(() => new Table({init
 assertErrorMessage(() => new Table({initial:1, element:"any"}), TypeError, /must be "anyfunc"/);
 assertErrorMessage(() => new Table({initial:1, element:{valueOf() { return "anyfunc" }}}), TypeError, /must be "anyfunc"/);
 assertErrorMessage(() => new Table({initial:{valueOf() { throw new Error("here")}}, element:"anyfunc"}), Error, "here");
 assertErrorMessage(() => new Table({initial:-1, element:"anyfunc"}), TypeError, /bad Table initial size/);
 assertErrorMessage(() => new Table({initial:Math.pow(2,32), element:"anyfunc"}), TypeError, /bad Table initial size/);
 assertEq(new Table({initial:1, element:"anyfunc"}) instanceof Table, true);
 assertEq(new Table({initial:1.5, element:"anyfunc"}) instanceof Table, true);
 
-// 'WebAssembly.Table.prototype' property
+// 'WebAssembly.Table.prototype' data property
 const tableProtoDesc = Object.getOwnPropertyDescriptor(Table, 'prototype');
 assertEq(typeof tableProtoDesc.value, "object");
 assertEq(tableProtoDesc.writable, false);
 assertEq(tableProtoDesc.enumerable, false);
 assertEq(tableProtoDesc.configurable, false);
 
 // 'WebAssembly.Table.prototype' object
 const tableProto = Table.prototype;
@@ -195,32 +224,32 @@ assertEq(String(tableProto), "[object Ob
 assertEq(Object.getPrototypeOf(tableProto), Object.prototype);
 
 // 'WebAssembly.Table' instance objects
 const tbl1 = new Table({initial:2, element:"anyfunc"});
 assertEq(typeof tbl1, "object");
 assertEq(String(tbl1), "[object WebAssembly.Table]");
 assertEq(Object.getPrototypeOf(tbl1), tableProto);
 
-// 'WebAssembly.Table.prototype.length' accessor property
+// 'WebAssembly.Table.prototype.length' accessor data property
 const lengthDesc = Object.getOwnPropertyDescriptor(tableProto, 'length');
 assertEq(typeof lengthDesc.get, "function");
 assertEq(lengthDesc.set, undefined);
 assertEq(lengthDesc.enumerable, false);
 assertEq(lengthDesc.configurable, true);
 
 // 'WebAssembly.Table.prototype.length' getter
 const lengthGetter = lengthDesc.get;
 assertEq(lengthGetter.length, 0);
 assertErrorMessage(() => lengthGetter.call(), TypeError, /called on incompatible undefined/);
 assertErrorMessage(() => lengthGetter.call({}), TypeError, /called on incompatible Object/);
 assertEq(typeof lengthGetter.call(tbl1), "number");
 assertEq(lengthGetter.call(tbl1), 2);
 
-// 'WebAssembly.Table.prototype.get' property
+// 'WebAssembly.Table.prototype.get' data property
 const getDesc = Object.getOwnPropertyDescriptor(tableProto, 'get');
 assertEq(typeof getDesc.value, "function");
 assertEq(getDesc.enumerable, false);
 assertEq(getDesc.configurable, true);
 
 // 'WebAssembly.Table.prototype.get' method
 const get = getDesc.value;
 assertEq(get.length, 1);
@@ -230,17 +259,17 @@ assertEq(get.call(tbl1, 0), null);
 assertEq(get.call(tbl1, 1), null);
 assertEq(get.call(tbl1, 1.5), null);
 assertErrorMessage(() => get.call(tbl1, 2), RangeError, /out-of-range index/);
 assertErrorMessage(() => get.call(tbl1, 2.5), RangeError, /out-of-range index/);
 assertErrorMessage(() => get.call(tbl1, -1), RangeError, /out-of-range index/);
 assertErrorMessage(() => get.call(tbl1, Math.pow(2,33)), RangeError, /out-of-range index/);
 assertErrorMessage(() => get.call(tbl1, {valueOf() { throw new Error("hi") }}), Error, "hi");
 
-// 'WebAssembly.Table.prototype.set' property
+// 'WebAssembly.Table.prototype.set' data property
 const setDesc = Object.getOwnPropertyDescriptor(tableProto, 'set');
 assertEq(typeof setDesc.value, "function");
 assertEq(setDesc.enumerable, false);
 assertEq(setDesc.configurable, true);
 
 // 'WebAssembly.Table.prototype.set' method
 const set = setDesc.value;
 assertEq(set.length, 2);
@@ -253,17 +282,17 @@ assertErrorMessage(() => set.call(tbl1, 
 assertErrorMessage(() => set.call(tbl1, 0, undefined), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, 0, {}), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, 0, function() {}), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, 0, Math.sin), TypeError, /can only assign WebAssembly exported functions to Table/);
 assertErrorMessage(() => set.call(tbl1, {valueOf() { throw Error("hai") }}, null), Error, "hai");
 assertEq(set.call(tbl1, 0, null), undefined);
 assertEq(set.call(tbl1, 1, null), undefined);
 
-// 'WebAssembly.compile' property
+// 'WebAssembly.compile' data property
 const compileDesc = Object.getOwnPropertyDescriptor(WebAssembly, 'compile');
 assertEq(typeof compileDesc.value, "function");
 assertEq(compileDesc.writable, true);
 assertEq(compileDesc.enumerable, false);
 assertEq(compileDesc.configurable, true);
 
 // 'WebAssembly.compile' function
 const compile = WebAssembly.compile;
--- a/js/src/jit-test/tests/wasm/resizing.js
+++ b/js/src/jit-test/tests/wasm/resizing.js
@@ -70,8 +70,46 @@ var exports2 = evalText(`(module
             (i32.load (i32.const 0))
             (i32.add
                 (i32.load (i32.const 65532))
                 (i32.load (i32.const 6553596)))))
     (export "test" $test)
 )`, {a:{tbl, mem}}).exports;
 tbl.set(0, exports1.grow);
 assertEq(exports2.test(), 111);
+
+// Test for coherent length/contents
+
+var mem = new Memory({initial:1});
+new Int32Array(mem.buffer)[0] = 42;
+var mod = new Module(textToBinary(`(module
+    (import "" "mem" (memory 1))
+    (func $gm (param i32) (grow_memory (get_local 0)))
+    (export "grow_memory" $gm)
+    (func $cm (result i32) (current_memory))
+    (export "current_memory" $cm)
+    (func $ld (param i32) (result i32) (i32.load (get_local 0)))
+    (export "load" $ld)
+    (func $st (param i32) (param i32) (i32.store (get_local 0) (get_local 1)))
+    (export "store" $st)
+)`));
+var exp1 = new Instance(mod, {"":{mem}}).exports;
+var exp2 = new Instance(mod, {"":{mem}}).exports;
+assertEq(exp1.current_memory(), 1);
+assertEq(exp1.load(0), 42);
+assertEq(exp2.current_memory(), 1);
+assertEq(exp2.load(0), 42);
+mem.grow(1);
+assertEq(mem.buffer.byteLength, 2*64*1024);
+new Int32Array(mem.buffer)[64*1024/4] = 13;
+assertEq(exp1.current_memory(), 2);
+assertEq(exp1.load(0), 42);
+assertEq(exp1.load(64*1024), 13);
+assertEq(exp2.current_memory(), 2);
+assertEq(exp2.load(0), 42);
+assertEq(exp2.load(64*1024), 13);
+exp1.grow_memory(2);
+assertEq(exp1.current_memory(), 4);
+exp1.store(3*64*1024, 99);
+assertEq(exp2.current_memory(), 4);
+assertEq(exp2.load(3*64*1024), 99);
+assertEq(mem.buffer.byteLength, 4*64*1024);
+assertEq(new Int32Array(mem.buffer)[3*64*1024/4], 99);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -344,16 +344,17 @@ MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL,       1
 MSG_DEF(JSMSG_USE_ASM_LINK_FAIL,       1, JSEXN_TYPEERR, "asm.js link error: {0}")
 MSG_DEF(JSMSG_USE_ASM_TYPE_OK,         1, JSEXN_WARN,    "Successfully compiled asm.js code ({0})")
 
 // wasm
 MSG_DEF(JSMSG_WASM_FAIL,               1, JSEXN_TYPEERR,     "wasm error: {0}")
 MSG_DEF(JSMSG_WASM_DECODE_FAIL,        2, JSEXN_TYPEERR,     "wasm validation error at offset {0}: {1}")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
 MSG_DEF(JSMSG_WASM_BAD_IND_CALL,       0, JSEXN_ERR,         "bad wasm indirect call")
+MSG_DEF(JSMSG_WASM_BAD_GROW,           0, JSEXN_ERR,         "failed to grow memory")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_TYPEERR,     "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_SIZE,           2, JSEXN_TYPEERR,     "bad {0} {1} size")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument, if present, must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field is not {0}")
@@ -362,17 +363,17 @@ MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0
 MSG_DEF(JSMSG_WASM_BAD_I64,            0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_BAD_FIT,            2, JSEXN_RANGEERR,    "{0} segment does not fit in {1}")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_ERR,         "unreachable executed")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_ERR,         "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_ERR,         "invalid conversion to integer")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_ERR,         "integer divide by zero")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_ERR,         "unaligned memory access")
 MSG_DEF(JSMSG_WASM_OVERRECURSED,       0, JSEXN_INTERNALERR, "call stack exhausted")
-MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_INTERNALERR, "cannot transfer buffer aliasing WebAssembly Memory")
+MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_INTERNALERR, "cannot transfer WebAssembly/asm.js ArrayBuffer")
 
 // Proxy
 MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE,   2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
 MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
 MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
 MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
 MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
 MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -731,17 +731,17 @@ NewObjectWithClassProto(ExclusiveContext
                         NewObjectKind newKind = GenericObject)
 {
     gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
     return NewObjectWithClassProto(cx, clasp, proto, allocKind, newKind);
 }
 
 template<class T>
 inline T*
-NewObjectWithClassProto(ExclusiveContext* cx, HandleObject proto,
+NewObjectWithClassProto(ExclusiveContext* cx, HandleObject proto = nullptr,
                         NewObjectKind newKind = GenericObject)
 {
     JSObject* obj = NewObjectWithClassProto(cx, &T::class_, proto, newKind);
     return obj ? &obj->as<T>() : nullptr;
 }
 
 template <class T>
 inline T*
--- a/js/src/vm/ArrayBufferObject-inl.h
+++ b/js/src/vm/ArrayBufferObject-inl.h
@@ -50,32 +50,32 @@ WasmArrayBufferMappedSize(const ArrayBuf
         return buf->as<ArrayBufferObject>().wasmMappedSize();
 #ifdef WASM_HUGE_MEMORY
     return wasm::HugeMappedSize;
 #else
     return buf->as<SharedArrayBufferObject>().byteLength();
 #endif
 }
 
-inline uint32_t
-WasmArrayBufferActualByteLength(const ArrayBufferObjectMaybeShared* buf)
-{
-    if (buf->is<ArrayBufferObject>())
-        return buf->as<ArrayBufferObject>().wasmActualByteLength();
-    return buf->as<SharedArrayBufferObject>().byteLength();
-}
-
 inline mozilla::Maybe<uint32_t>
 WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
 {
     if (buf->is<ArrayBufferObject>())
         return buf->as<ArrayBufferObject>().wasmMaxSize();
     return mozilla::Some(buf->as<SharedArrayBufferObject>().byteLength());
 }
 
+inline bool
+AnyArrayBufferIsPreparedForAsmJS(const ArrayBufferObjectMaybeShared* buf)
+{
+    if (buf->is<ArrayBufferObject>())
+        return buf->as<ArrayBufferObject>().isPreparedForAsmJS();
+    return buf->as<SharedArrayBufferObject>().isPreparedForAsmJS();
+}
+
 inline ArrayBufferObjectMaybeShared&
 AsAnyArrayBuffer(HandleValue val)
 {
     if (val.toObject().is<ArrayBufferObject>())
         return val.toObject().as<ArrayBufferObject>();
     return val.toObject().as<SharedArrayBufferObject>();
 }
 
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -268,18 +268,17 @@ NoteViewBufferWasDetached(ArrayBufferVie
     MarkObjectStateChange(cx, view);
 }
 
 /* static */ void
 ArrayBufferObject::detach(JSContext* cx, Handle<ArrayBufferObject*> buffer,
                           BufferContents newContents)
 {
     assertSameCompartment(cx, buffer);
-
-    MOZ_ASSERT(!buffer->isWasm());
+    MOZ_ASSERT(!buffer->isPreparedForAsmJS());
 
     // When detaching buffers where we don't know all views, the new data must
     // match the old data. All missing views are typed objects, which do not
     // expect their data to ever change.
     MOZ_ASSERT_IF(buffer->forInlineTypedObject(),
                   newContents.data() == buffer->dataPointer());
 
     // When detaching a buffer with typed object views, any jitcode accessing
@@ -359,16 +358,17 @@ ArrayBufferObject::changeViewContents(JS
     MarkObjectStateChange(cx, view);
 }
 
 // BufferContents is specific to ArrayBuffer, hence it will not represent shared memory.
 
 void
 ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents)
 {
+    MOZ_RELEASE_ASSERT(!isWasm());
     MOZ_ASSERT(!forInlineTypedObject());
 
     // Change buffer contents.
     uint8_t* oldDataPointer = dataPointer();
     setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
 
     // Update all views.
     auto& innerViews = cx->compartment()->innerViews;
@@ -446,23 +446,22 @@ ArrayBufferObject::changeContents(JSCont
  *  part of the SLOP allows us to bounds check against base pointers and fold
  *  some constant offsets inside loads. This enables better Bounds
  *  Check Elimination.
  *
  */
 
 class js::WasmArrayRawBuffer
 {
-    uint32_t length_;
     Maybe<uint32_t> maxSize_;
     size_t mappedSize_;
 
   protected:
     WasmArrayRawBuffer(uint8_t* buffer, uint32_t length, Maybe<uint32_t> maxSize, size_t mappedSize)
-      : length_(length), maxSize_(maxSize), mappedSize_(mappedSize)
+      : maxSize_(maxSize), mappedSize_(mappedSize)
     {
         MOZ_ASSERT(buffer == dataPointer());
     }
 
   public:
     static WasmArrayRawBuffer* Allocate(uint32_t numBytes, Maybe<uint32_t> maxSize);
     static void Release(void* mem);
 
@@ -470,24 +469,16 @@ class js::WasmArrayRawBuffer
         uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
         return ptr + sizeof(WasmArrayRawBuffer);
     }
 
     uint8_t* basePointer() {
         return dataPointer() - gc::SystemPageSize();
     }
 
-    // TODO: actualByteLength in WasmArrayRawBuffer is a temporary hack to allow
-    // keeping track of the size of dynamically growing WASM memory. We can't
-    // keep it in the containg ArrayBufferObject's byte length field since those
-    // are immutable. This will be removed in a followup resizing patch.
-    uint32_t actualByteLength() const {
-        return length_;
-    }
-
     size_t mappedSize() const {
         return mappedSize_;
     }
 
     Maybe<uint32_t> maxSize() const {
         return maxSize_;
     }
 
@@ -499,41 +490,39 @@ class js::WasmArrayRawBuffer
     uint32_t boundsCheckLimit() const {
         MOZ_ASSERT(mappedSize_ <= UINT32_MAX);
         MOZ_ASSERT(mappedSize_ >= wasm::GuardSize);
         MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize_ - wasm::GuardSize));
         return mappedSize_ - wasm::GuardSize;
     }
 #endif
 
-    MOZ_MUST_USE bool growToSizeInPlace(uint32_t newSize) {
-        MOZ_ASSERT(newSize >= actualByteLength());
+    MOZ_MUST_USE bool growToSizeInPlace(uint32_t oldSize, uint32_t newSize) {
+        MOZ_ASSERT(newSize >= oldSize);
         MOZ_ASSERT_IF(maxSize(), newSize <= maxSize().value());
         MOZ_ASSERT(newSize <= mappedSize());
 
-        uint32_t delta = newSize - actualByteLength();
+        uint32_t delta = newSize - oldSize;
         MOZ_ASSERT(delta % wasm::PageSize == 0);
 
-        uint8_t* dataEnd = dataPointer() + actualByteLength();
+        uint8_t* dataEnd = dataPointer() + oldSize;
         MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
 # ifdef XP_WIN
         if (delta && !VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE))
             return false;
 # else  // XP_WIN
         if (delta && mprotect(dataEnd, delta, PROT_READ | PROT_WRITE))
             return false;
 # endif  // !XP_WIN
 
 #  if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
         VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
 #  endif
 
         MemProfiler::SampleNative(dataEnd, delta);
-
-        length_ = newSize;
         return true;
     }
 
 #ifndef WASM_HUGE_MEMORY
     bool extendMappedSize(uint32_t maxSize) {
         size_t newMappedSize = wasm::ComputeMappedSize(maxSize);
         MOZ_ASSERT(mappedSize_ <= newMappedSize);
         if (mappedSize_ == newMappedSize)
@@ -645,17 +634,17 @@ WasmArrayRawBuffer::Release(void* mem)
 #  if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
     VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, mappedSizeWithHeader);
 #  endif
 }
 
 WasmArrayRawBuffer*
 ArrayBufferObject::BufferContents::wasmBuffer() const
 {
-    MOZ_RELEASE_ASSERT(kind_ == WASM_MAPPED);
+    MOZ_RELEASE_ASSERT(kind_ == WASM);
     return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
 }
 
 #define ROUND_UP(v, a) ((v) % (a) == 0 ? (v) : v + a - ((v) % (a)))
 
 /* static */ ArrayBufferObject*
 ArrayBufferObject::createForWasm(JSContext* cx, uint32_t initialSize, Maybe<uint32_t> maxSize)
 {
@@ -703,84 +692,83 @@ ArrayBufferObject::createForWasm(JSConte
         }
 
         // Try to grow our chunk as much as possible.
         for (size_t d = cur / 2; d >= wasm::PageSize; d /= 2)
             wasmBuf->tryGrowMaxSizeInPlace(ROUND_UP(d, wasm::PageSize));
 #endif
     }
 
-    auto contents = BufferContents::create<WASM_MAPPED>(wasmBuf->dataPointer());
+    auto contents = BufferContents::create<WASM>(wasmBuf->dataPointer());
     buffer->initialize(initialSize, contents, OwnsData);
     cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
     return buffer;
 }
 
+// Note this function can return false with or without an exception pending. The
+// asm.js caller checks cx->isExceptionPending before propagating failure.
+// Returning false without throwing means that asm.js linking will fail which
+// will recompile as non-asm.js.
 /* static */ bool
 ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer, bool needGuard)
 {
 #ifdef WASM_HUGE_MEMORY
     MOZ_ASSERT(needGuard);
 #endif
     MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
-    if (buffer->forInlineTypedObject()) {
-        JS_ReportError(cx, "ArrayBuffer can't be used by asm.js");
+    if (buffer->forInlineTypedObject())
         return false;
-    }
-
-    // Since asm.js doesn't grow, assume max is same as length.
-    uint32_t length = buffer->byteLength();
-    uint32_t maxSize = length;
 
     if (needGuard) {
-        if (buffer->isWasmMapped())
+        if (buffer->isWasm() && buffer->isPreparedForAsmJS())
             return true;
 
-        if (buffer->isAsmJSMalloced()) {
-            // needGuard is only set for SIMD.js (which isn't shipping, so this
-            // error isn't content-visible).
-            JS_ReportError(cx, "ArrayBuffer can't be prepared for asm.js with SIMD.js");
+        // Non-prepared-for-asm.js wasm buffers can be detached at any time.
+        // This error can only be triggered for SIMD.js (which isn't shipping)
+        // on !WASM_HUGE_MEMORY so this error is only visible in testing.
+        if (buffer->isWasm() || buffer->isPreparedForAsmJS())
             return false;
-        }
 
-        WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(length, Some(maxSize));
+        uint32_t length = buffer->byteLength();
+        WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(length, Some(length));
         if (!wasmBuf) {
-            // Note - we don't need the same backoff search as in WASM, since we don't over-map to
-            // allow growth in asm.js
             ReportOutOfMemory(cx);
             return false;
         }
 
-        // Copy over the current contents of the typed array.
         void* data = wasmBuf->dataPointer();
         memcpy(data, buffer->dataPointer(), length);
 
         // Swap the new elements into the ArrayBufferObject. Mark the
         // ArrayBufferObject so we don't do this again.
-        BufferContents newContents = BufferContents::create<WASM_MAPPED>(data);
-        buffer->changeContents(cx, newContents);
+        buffer->changeContents(cx, BufferContents::create<WASM>(data));
+        buffer->setIsPreparedForAsmJS();
         MOZ_ASSERT(data == buffer->dataPointer());
         cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
         return true;
     }
 
-    if (buffer->isAsmJSMalloced())
+    if (!buffer->isWasm() && buffer->isPreparedForAsmJS())
         return true;
 
+    // Non-prepared-for-asm.js wasm buffers can be detached at any time.
+    if (buffer->isWasm())
+        return false;
+
     if (!buffer->ownsData()) {
         BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
         if (!contents)
             return false;
         memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
         buffer->changeContents(cx, contents);
     }
 
-    buffer->setIsAsmJSMalloced();
+    buffer->setIsPreparedForAsmJS();
     return true;
 }
 
 ArrayBufferObject::BufferContents
 ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
 {
     void* data = AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
     MemProfiler::SampleNative(data, length);
@@ -807,26 +795,27 @@ ArrayBufferObject::dataPointerShared() c
 
 void
 ArrayBufferObject::releaseData(FreeOp* fop)
 {
     MOZ_ASSERT(ownsData());
 
     switch (bufferKind()) {
       case PLAIN:
-      case ASMJS_MALLOCED:
         fop->free_(dataPointer());
         break;
       case MAPPED:
         MemProfiler::RemoveNative(dataPointer());
         DeallocateMappedContent(dataPointer(), byteLength());
         break;
-      case WASM_MAPPED:
+      case WASM:
         WasmArrayRawBuffer::Release(dataPointer());
         break;
+      case KIND_MASK:
+        MOZ_CRASH("bad bufferKind()");
     }
 }
 
 void
 ArrayBufferObject::setDataPointer(BufferContents contents, OwnsState ownsData)
 {
     setSlot(DATA_SLOT, PrivateValue(contents.data()));
     setOwnsData(ownsData);
@@ -844,74 +833,102 @@ ArrayBufferObject::setByteLength(uint32_
 {
     MOZ_ASSERT(length <= INT32_MAX);
     setSlot(BYTE_LENGTH_SLOT, Int32Value(length));
 }
 
 size_t
 ArrayBufferObject::wasmMappedSize() const
 {
-    if (isWasmMapped()) {
+    if (isWasm())
         return contents().wasmBuffer()->mappedSize();
-    } else {
-        // Can use byteLength() instead of actualByteLength since if !wasmMapped()
-        // then this is an asm.js buffer, and thus cannot grow.
-        return byteLength();
-    }
+    return byteLength();
 }
 
 Maybe<uint32_t>
 ArrayBufferObject::wasmMaxSize() const
 {
-    if (isWasmMapped())
+    if (isWasm())
         return contents().wasmBuffer()->maxSize();
     else
         return Some<uint32_t>(byteLength());
 }
 
-uint32_t
-ArrayBufferObject::wasmActualByteLength() const
+/* static */ bool
+ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize,
+                                         HandleArrayBufferObject oldBuf,
+                                         MutableHandleArrayBufferObject newBuf,
+                                         JSContext* cx)
 {
-    if (isWasmMapped())
-        return contents().wasmBuffer()->actualByteLength();
-    else
-        return byteLength();
-}
+    // On failure, do not throw and ensure that the original buffer is
+    // unmodified and valid. After WasmArrayRawBuffer::growToSizeInPlace(), the
+    // wasm-visible length of the buffer has been increased so it must be the
+    // last fallible operation.
+
+    // byteLength can be at most INT32_MAX.
+    if (newSize > INT32_MAX)
+        return false;
 
-bool
-ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize)
-{
-    return contents().wasmBuffer()->growToSizeInPlace(newSize);
+    newBuf.set(ArrayBufferObject::createEmpty(cx));
+    if (!newBuf) {
+        cx->clearPendingException();
+        return false;
+    }
+
+    if (!oldBuf->contents().wasmBuffer()->growToSizeInPlace(oldBuf->byteLength(), newSize))
+        return false;
+
+    bool hasStealableContents = true;
+    BufferContents contents = ArrayBufferObject::stealContents(cx, oldBuf, hasStealableContents);
+    MOZ_ASSERT(contents);
+    newBuf->initialize(newSize, contents, OwnsData);
+    return true;
 }
 
 #ifndef WASM_HUGE_MEMORY
-bool
-ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize)
+/* static */ bool
+ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize,
+                                        HandleArrayBufferObject oldBuf,
+                                        MutableHandleArrayBufferObject newBuf,
+                                        JSContext* cx)
 {
-    WasmArrayRawBuffer* curBuf = contents().wasmBuffer();
+    // On failure, do not throw and ensure that the original buffer is
+    // unmodified and valid.
 
-    if (newSize <= curBuf->boundsCheckLimit() || curBuf->extendMappedSize(newSize))
-        return curBuf->growToSizeInPlace(newSize);
-
-    WasmArrayRawBuffer* newBuf = WasmArrayRawBuffer::Allocate(newSize, Nothing());
-    if (!newBuf)
+    // byteLength can be at most INT32_MAX.
+    if (newSize > INT32_MAX)
         return false;
 
-    void* newData = newBuf->dataPointer();
-    memcpy(newData, curBuf->dataPointer(), curBuf->actualByteLength());
+    if (newSize <= oldBuf->wasmBoundsCheckLimit() ||
+        oldBuf->contents().wasmBuffer()->extendMappedSize(newSize))
+    {
+        return wasmGrowToSizeInPlace(newSize, oldBuf, newBuf, cx);
+    }
 
-    BufferContents newContents = BufferContents::create<WASM_MAPPED>(newData);
-    changeContents(GetJSContextFromMainThread(), newContents);
+    newBuf.set(ArrayBufferObject::createEmpty(cx));
+    if (!newBuf) {
+        cx->clearPendingException();
+        return false;
+    }
+
+    WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::Allocate(newSize, Nothing());
+    if (!newRawBuf)
+        return false;
+    BufferContents contents = BufferContents::create<WASM>(newRawBuf->dataPointer());
+    newBuf->initialize(newSize, contents, OwnsData);
+
+    memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
+    ArrayBufferObject::detach(cx, oldBuf, BufferContents::createPlain(nullptr));
     return true;
 }
 
 uint32_t
 ArrayBufferObject::wasmBoundsCheckLimit() const
 {
-    if (isWasmMapped())
+    if (isWasm())
         return contents().wasmBuffer()->boundsCheckLimit();
     else
         return byteLength();
 }
 
 uint32_t
 ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
 {
@@ -951,17 +968,17 @@ ArrayBufferObject::create(JSContext* cx,
     size_t nslots = reservedSlots;
     bool allocated = false;
     if (contents) {
         if (ownsState == OwnsData) {
             // The ABO is taking ownership, so account the bytes against the zone.
             size_t nAllocated = nbytes;
             if (contents.kind() == MAPPED)
                 nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
-            else if (contents.kind() == WASM_MAPPED)
+            else if (contents.kind() == WASM)
                 nAllocated = contents.wasmBuffer()->allocatedBytes();
             cx->zone()->updateMallocCounter(nAllocated);
         }
     } else {
         MOZ_ASSERT(ownsState == OwnsData);
         size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
         if (nbytes <= usableSlots * sizeof(Value)) {
             int newSlots = (nbytes - 1) / sizeof(Value) + 1;
@@ -1057,17 +1074,20 @@ ArrayBufferObject::createDataViewForThis
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
 }
 
 /* static */ ArrayBufferObject::BufferContents
 ArrayBufferObject::stealContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
                                  bool hasStealableContents)
 {
-    MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
+    // While wasm buffers cannot generally be transferred by content, the
+    // stealContents() is used internally by the impl of memory growth.
+    MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents() ||
+                                        (buffer->isWasm() && !buffer->isPreparedForAsmJS()));
     assertSameCompartment(cx, buffer);
 
     BufferContents oldContents(buffer->dataPointer(), buffer->bufferKind());
 
     if (hasStealableContents) {
         // Return the old contents and reset the detached buffer's data
         // pointer. This pointer should never be accessed.
         auto newContents = BufferContents::createPlain(nullptr);
@@ -1100,22 +1120,21 @@ ArrayBufferObject::addSizeOfExcludingThi
 
     switch (buffer.bufferKind()) {
       case PLAIN:
         info->objectsMallocHeapElementsNormal += mallocSizeOf(buffer.dataPointer());
         break;
       case MAPPED:
         info->objectsNonHeapElementsNormal += buffer.byteLength();
         break;
-      case ASMJS_MALLOCED:
-        info->objectsMallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer());
-        break;
-      case WASM_MAPPED:
+      case WASM:
         info->objectsNonHeapElementsAsmJS += buffer.byteLength();
         break;
+      case KIND_MASK:
+        MOZ_CRASH("bad bufferKind()");
     }
 }
 
 /* static */ void
 ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
 {
     ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
 
@@ -1519,18 +1538,18 @@ JS_DetachArrayBuffer(JSContext* cx, Hand
 
     if (!obj->is<ArrayBufferObject>()) {
         JS_ReportError(cx, "ArrayBuffer object required");
         return false;
     }
 
     Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
 
-    if (buffer->isWasm()) {
-        JS_ReportError(cx, "Cannot detach WebAssembly ArrayBuffer");
+    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
+        JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
         return false;
     }
 
     ArrayBufferObject::BufferContents newContents =
         buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
                                        : buffer->contents();
 
     ArrayBufferObject::detach(cx, buffer, newContents);
@@ -1627,17 +1646,17 @@ JS_StealArrayBufferContents(JSContext* c
     }
 
     Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
     if (buffer->isDetached()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
         return nullptr;
     }
 
-    if (buffer->isWasm()) {
+    if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
         return nullptr;
     }
 
     // The caller assumes that a plain malloc'd buffer is returned.
     // hasStealableContents is true for mapped buffers, so we must additionally
     // require that the buffer is plain. In the future, we could consider
     // returning something that handles releasing the memory.
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -69,20 +69,20 @@ class WasmArrayRawBuffer;
 // that (3) may only be pointed to by the typed array the data is inline with.
 //
 // During a minor GC, (3) and (4) may move. During a compacting GC, (2), (3),
 // and (4) may move.
 
 class ArrayBufferObjectMaybeShared;
 
 uint32_t AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared* buf);
-uint32_t WasmArrayBufferActualByteLength(const ArrayBufferObjectMaybeShared* buf);
 mozilla::Maybe<uint32_t> WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf);
 size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);
 bool WasmArrayBufferGrowForWasm(ArrayBufferObjectMaybeShared* buf, uint32_t delta);
+bool AnyArrayBufferIsPreparedForAsmJS(const ArrayBufferObjectMaybeShared* buf);
 ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
 
 class ArrayBufferObjectMaybeShared : public NativeObject
 {
   public:
     uint32_t byteLength() {
         return AnyArrayBufferByteLength(this);
     }
@@ -90,28 +90,29 @@ class ArrayBufferObjectMaybeShared : pub
     inline bool isDetached() const;
 
     inline SharedMem<uint8_t*> dataPointerEither();
 
     // WebAssembly support:
     // Note: the eventual goal is to remove this from ArrayBuffer and have
     // (Shared)ArrayBuffers alias memory owned by some wasm::Memory object.
 
-    uint32_t wasmActualByteLength() const {
-        return WasmArrayBufferActualByteLength(this);
-    }
     mozilla::Maybe<uint32_t> wasmMaxSize() const {
         return WasmArrayBufferMaxSize(this);
     }
     size_t wasmMappedSize() const {
         return WasmArrayBufferMappedSize(this);
     }
 #ifndef WASM_HUGE_MEMORY
     uint32_t wasmBoundsCheckLimit() const;
 #endif
+
+    bool isPreparedForAsmJS() const {
+        return AnyArrayBufferIsPreparedForAsmJS(this);
+    }
 };
 
 typedef Rooted<ArrayBufferObjectMaybeShared*> RootedArrayBufferObjectMaybeShared;
 typedef Handle<ArrayBufferObjectMaybeShared*> HandleArrayBufferObjectMaybeShared;
 typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObjectMaybeShared;
 
 /*
  * ArrayBufferObject
@@ -150,19 +151,18 @@ class ArrayBufferObject : public ArrayBu
 
     enum OwnsState {
         DoesntOwnData = 0,
         OwnsData = 1,
     };
 
     enum BufferKind {
         PLAIN               = 0, // malloced or inline data
-        ASMJS_MALLOCED      = 1,
-        WASM_MAPPED         = 2,
-        MAPPED              = 3,
+        WASM                = 1,
+        MAPPED              = 2,
 
         KIND_MASK           = 0x3
     };
 
   protected:
 
     enum ArrayBufferFlags {
         // The flags also store the BufferKind
@@ -183,17 +183,21 @@ class ArrayBufferObject : public ArrayBu
 
         // This array buffer was created lazily for a typed object with inline
         // data. This implies both that the typed object owns the buffer's data
         // and that the list of views sharing this buffer's data might be
         // incomplete. Any missing views will be typed objects.
         FOR_INLINE_TYPED_OBJECT = 0x10,
 
         // Views of this buffer might include typed objects.
-        TYPED_OBJECT_VIEWS  = 0x20
+        TYPED_OBJECT_VIEWS  = 0x20,
+
+        // This PLAIN or WASM buffer has been prepared for asm.js and cannot
+        // henceforth be transferred/detached.
+        FOR_ASMJS           = 0x40
     };
 
     static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
                   "self-hosted code with burned-in constants must use the "
                   "correct DETACHED bit value");
   public:
 
     class BufferContents {
@@ -273,17 +277,17 @@ class ArrayBufferObject : public ArrayBu
     static void objectMoved(JSObject* obj, const JSObject* old);
 
     static BufferContents stealContents(JSContext* cx,
                                         Handle<ArrayBufferObject*> buffer,
                                         bool hasStealableContents);
 
     bool hasStealableContents() const {
         // Inline elements strictly adhere to the corresponding buffer.
-        return ownsData();
+        return ownsData() && !isPreparedForAsmJS() && !isWasm();
     }
 
     static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
                                        JS::ClassInfo* info);
 
     // ArrayBufferObjects (strongly) store the first view added to them, while
     // later views are (weakly) stored in the compartment's InnerViewTable
     // below. Buffers usually only have one view, so this slot optimizes for
@@ -327,32 +331,37 @@ class ArrayBufferObject : public ArrayBu
      * ArrayBuffer.prototype and detached ArrayBuffers.
      */
     bool hasData() const {
         return getClass() == &class_;
     }
 
     BufferKind bufferKind() const { return BufferKind(flags() & BUFFER_KIND_MASK); }
     bool isPlain() const { return bufferKind() == PLAIN; }
-    bool isWasmMapped() const { return bufferKind() == WASM_MAPPED; }
-    bool isAsmJSMalloced() const { return bufferKind() == ASMJS_MALLOCED; }
-    bool isWasm() const { return isWasmMapped() || isAsmJSMalloced(); }
+    bool isWasm() const { return bufferKind() == WASM; }
     bool isMapped() const { return bufferKind() == MAPPED; }
     bool isDetached() const { return flags() & DETACHED; }
+    bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
 
     // WebAssembly support:
     static ArrayBufferObject* createForWasm(JSContext* cx, uint32_t initialSize,
                                             mozilla::Maybe<uint32_t> maxSize);
-    static bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer, bool needGuard);
-    uint32_t wasmActualByteLength() const;
+    static MOZ_MUST_USE bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer,
+                                             bool needGuard);
     size_t wasmMappedSize() const;
     mozilla::Maybe<uint32_t> wasmMaxSize() const;
-    MOZ_MUST_USE bool wasmGrowToSizeInPlace(uint32_t newSize);
+    static MOZ_MUST_USE bool wasmGrowToSizeInPlace(uint32_t newSize,
+                                                   Handle<ArrayBufferObject*> oldBuf,
+                                                   MutableHandle<ArrayBufferObject*> newBuf,
+                                                   JSContext* cx);
 #ifndef WASM_HUGE_MEMORY
-    MOZ_MUST_USE bool wasmMovingGrowToSize(uint32_t newSize);
+    static MOZ_MUST_USE bool wasmMovingGrowToSize(uint32_t newSize,
+                                                  Handle<ArrayBufferObject*> oldBuf,
+                                                  MutableHandle<ArrayBufferObject*> newBuf,
+                                                  JSContext* cx);
     uint32_t wasmBoundsCheckLimit() const;
 #endif
 
     static void finalize(FreeOp* fop, JSObject* obj);
 
     static BufferContents createMappedContents(int fd, size_t offset, size_t length);
 
     static size_t offsetOfFlagsSlot() {
@@ -380,18 +389,18 @@ class ArrayBufferObject : public ArrayBu
 
     bool ownsData() const { return flags() & OWNS_DATA; }
     void setOwnsData(OwnsState owns) {
         setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA));
     }
 
     bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; }
 
-    void setIsAsmJSMalloced() { setFlags((flags() & ~KIND_MASK) | ASMJS_MALLOCED); }
     void setIsDetached() { setFlags(flags() | DETACHED); }
+    void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); }
 
     void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
         setByteLength(byteLength);
         setFlags(0);
         setFirstView(nullptr);
         setDataPointer(contents, ownsState);
     }
 
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -1468,17 +1468,17 @@ JSStructuredCloneWriter::transferOwnersh
 
         if (cls == ESClass::ArrayBuffer) {
             // The current setup of the array buffer inheritance hierarchy doesn't
             // lend itself well to generic manipulation via proxies.
             Rooted<ArrayBufferObject*> arrayBuffer(context(), &CheckedUnwrap(obj)->as<ArrayBufferObject>());
             JSAutoCompartment ac(context(), arrayBuffer);
             size_t nbytes = arrayBuffer->byteLength();
 
-            if (arrayBuffer->isWasm()) {
+            if (arrayBuffer->isWasm() || arrayBuffer->isPreparedForAsmJS()) {
                 JS_ReportErrorNumber(context(), GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
                 return false;
             }
 
             bool hasStealableContents = arrayBuffer->hasStealableContents() &&
                                         (scope != JS::StructuredCloneScope::DifferentProcess);
 
             ArrayBufferObject::BufferContents bufContents =
--- a/media/mtransport/transportlayerdtls.cpp
+++ b/media/mtransport/transportlayerdtls.cpp
@@ -1,38 +1,37 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Original author: ekr@rtfm.com
 
+#include "transportlayerdtls.h"
+
+#include <algorithm>
 #include <queue>
-#include <algorithm>
 #include <sstream>
 
+#include "dtlsidentity.h"
+#include "keyhi.h"
+#include "logging.h"
 #include "mozilla/UniquePtr.h"
-
-#include "logging.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIEventTarget.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
 #include "ssl.h"
 #include "sslerr.h"
 #include "sslproto.h"
-#include "keyhi.h"
+#include "transportflow.h"
 
-#include "nsCOMPtr.h"
-#include "nsComponentManagerUtils.h"
-#include "nsIEventTarget.h"
-#include "nsNetCID.h"
-#include "nsComponentManagerUtils.h"
-#include "nsServiceManagerUtils.h"
-
-#include "dtlsidentity.h"
-#include "transportflow.h"
-#include "transportlayerdtls.h"
 
 namespace mozilla {
 
 MOZ_MTLOG_MODULE("mtransport")
 
 static PRDescIdentity transport_layer_identity = PR_INVALID_IO_LAYER;
 
 // TODO: Implement a mode for this where
@@ -734,41 +733,41 @@ bool TransportLayerDtls::SetupCipherSuit
     rv = SSL_SetSRTPCiphers(ssl_fd, &srtp_ciphers_[0], srtp_ciphers_.size());
 
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, "Couldn't set SRTP cipher suite");
       return false;
     }
   }
 
-  for (size_t i = 0; i < PR_ARRAY_SIZE(EnabledCiphers); ++i) {
-    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Enabling: " << EnabledCiphers[i]);
-    rv = SSL_CipherPrefSet(ssl_fd, EnabledCiphers[i], PR_TRUE);
+  for (const auto& cipher : EnabledCiphers) {
+    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Enabling: " << cipher);
+    rv = SSL_CipherPrefSet(ssl_fd, cipher, PR_TRUE);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_ERROR, LAYER_INFO <<
-                "Unable to enable suite: " << EnabledCiphers[i]);
+                "Unable to enable suite: " << cipher);
       return false;
     }
   }
 
-  for (size_t i = 0; i < PR_ARRAY_SIZE(DisabledCiphers); ++i) {
-    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Disabling: " << DisabledCiphers[i]);
+  for (const auto& cipher : DisabledCiphers) {
+    MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Disabling: " << cipher);
 
     PRBool enabled = false;
-    rv = SSL_CipherPrefGet(ssl_fd, DisabledCiphers[i], &enabled);
+    rv = SSL_CipherPrefGet(ssl_fd, cipher, &enabled);
     if (rv != SECSuccess) {
       MOZ_MTLOG(ML_NOTICE, LAYER_INFO <<
-                "Unable to check if suite is enabled: " << DisabledCiphers[i]);
+                "Unable to check if suite is enabled: " << cipher);
       return false;
     }
     if (enabled) {
-      rv = SSL_CipherPrefSet(ssl_fd, DisabledCiphers[i], PR_FALSE);
+      rv = SSL_CipherPrefSet(ssl_fd, cipher, PR_FALSE);
       if (rv != SECSuccess) {
         MOZ_MTLOG(ML_NOTICE, LAYER_INFO <<
-                  "Unable to disable suite: " << DisabledCiphers[i]);
+                  "Unable to disable suite: " << cipher);
         return false;
       }
     }
   }
 
   return true;
 }
 
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -282,16 +282,18 @@
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/BrowserElementProxy.manifest
 @BINPATH@/components/BrowserElementProxy.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
 @BINPATH@/components/PackagedAppUtils.manifest
 @BINPATH@/components/PackagedAppUtils.js
+@BINPATH@/components/WellKnownOpportunisticUtils.js
+@BINPATH@/components/WellKnownOpportunisticUtils.manifest
 @BINPATH@/components/PermissionSettings.js
 @BINPATH@/components/PermissionSettings.manifest
 @BINPATH@/components/PermissionPromptService.js
 @BINPATH@/components/PermissionPromptService.manifest
 @BINPATH@/components/nsDNSServiceDiscovery.manifest
 @BINPATH@/components/nsDNSServiceDiscovery.js
 @BINPATH@/components/toolkitsearch.manifest
 @BINPATH@/components/nsSearchService.js
--- a/netwerk/cookie/nsCookie.cpp
+++ b/netwerk/cookie/nsCookie.cpp
@@ -4,18 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ToJSValue.h"
 #include "nsAutoPtr.h"
 #include "nsCookie.h"
 #include "nsUTF8ConverterService.h"
 #include <stdlib.h>
 
-static const int64_t kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds
-
 /******************************************************************************
  * nsCookie:
  * string helper impl
  ******************************************************************************/
 
 // copy aSource strings into contiguous storage provided in aDest1,
 // providing terminating nulls for each destination string.
 static inline void
@@ -125,17 +123,17 @@ nsCookie::SizeOfIncludingThis(mozilla::M
     return aMallocSizeOf(this);
 }
 
 bool
 nsCookie::IsStale() const
 {
   int64_t currentTimeInUsec = PR_Now();
 
-  return currentTimeInUsec - LastAccessed() > kCookieStaleThreshold;
+  return currentTimeInUsec - LastAccessed() > mCookieStaleThreshold * PR_USEC_PER_SEC;
 }
 
 /******************************************************************************
  * nsCookie:
  * xpcom impl
  ******************************************************************************/
 
 // xpcom getters
--- a/netwerk/cookie/nsCookie.h
+++ b/netwerk/cookie/nsCookie.h
@@ -7,16 +7,17 @@
 #define nsCookie_h__
 
 #include "nsICookie.h"
 #include "nsICookie2.h"
 #include "nsString.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/BasePrincipal.h"
+#include "mozilla/Preferences.h"
 
 using mozilla::OriginAttributes;
 
 /** 
  * The nsCookie class is the main cookie storage medium for use within cookie
  * code. It implements nsICookie2, which extends nsICookie, a frozen interface
  * for xpcom access of cookie objects.
  */
@@ -51,16 +52,18 @@ class nsCookie : public nsICookie2
      : mName(aName)
      , mValue(aValue)
      , mHost(aHost)
      , mPath(aPath)
      , mEnd(aEnd)
      , mExpiry(aExpiry)
      , mLastAccessed(aLastAccessed)
      , mCreationTime(aCreationTime)
+       // Defaults to 60s
+     , mCookieStaleThreshold(mozilla::Preferences::GetInt("network.cookie.staleThreshold", 60))
      , mIsSession(aIsSession)
      , mIsSecure(aIsSecure)
      , mIsHttpOnly(aIsHttpOnly)
      , mOriginAttributes(aOriginAttributes)
     {
     }
 
   public:
@@ -122,15 +125,16 @@ class nsCookie : public nsICookie2
     const char  *mName;
     const char  *mValue;
     const char  *mHost;
     const char  *mPath;
     const char  *mEnd;
     int64_t      mExpiry;
     int64_t      mLastAccessed;
     int64_t      mCreationTime;
+    int64_t      mCookieStaleThreshold;
     bool mIsSession;
     bool mIsSecure;
     bool mIsHttpOnly;
     mozilla::OriginAttributes mOriginAttributes;
 };
 
 #endif // nsCookie_h__
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -3488,17 +3488,17 @@ nsCookieService::AddInternal(const nsCoo
         "cookie has already expired");
       return;
     }
 
     // check if we have to delete an old cookie.
     nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey);
     if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) {
       nsListIter iter;
-      FindStaleCookie(entry, currentTime, iter);
+      FindStaleCookie(entry, currentTime, aHostURI, iter);
       oldCookie = iter.Cookie();
 
       // remove the oldest cookie from the domain
       RemoveCookieFromList(iter);
       COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain");
       purgedList = CreatePurgeList(oldCookie);
 
     } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) {
@@ -4356,39 +4356,119 @@ nsCookieService::CookieExists(nsICookie2
   return NS_OK;
 }
 
 // For a given base domain, find either an expired cookie or the oldest cookie
 // by lastAccessed time.
 void
 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry,
                                  int64_t aCurrentTime,
+                                 nsIURI* aSource,
                                  nsListIter &aIter)
 {
-  aIter.entry = nullptr;
-
-  int64_t oldestTime = 0;
+  bool requireHostMatch = true;
+  nsAutoCString baseDomain, sourceHost, sourcePath;
+  if (aSource) {
+    GetBaseDomain(aSource, baseDomain, requireHostMatch);
+    aSource->GetAsciiHost(sourceHost);
+    aSource->GetPath(sourcePath);
+  }
+
   const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies();
+
+  int64_t oldestNonMatchingSessionCookieTime = 0;
+  nsListIter oldestNonMatchingSessionCookie;
+  oldestNonMatchingSessionCookie.entry = nullptr;
+
+  int64_t oldestSessionCookieTime = 0;
+  nsListIter oldestSessionCookie;
+  oldestSessionCookie.entry = nullptr;
+
+  int64_t oldestNonMatchingNonSessionCookieTime = 0;
+  nsListIter oldestNonMatchingNonSessionCookie;
+  oldestNonMatchingNonSessionCookie.entry = nullptr;
+
+  int64_t oldestCookieTime = 0;
+  nsListIter oldestCookie;
+  oldestCookie.entry = nullptr;
+
   for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) {
     nsCookie *cookie = cookies[i];
 
     // If we found an expired cookie, we're done.
     if (cookie->Expiry() <= aCurrentTime) {
       aIter.entry = aEntry;
       aIter.index = i;
       return;
     }
 
+    // Update our various records of oldest cookies fitting several restrictions:
+    // * session cookies
+    // * non-session cookies
+    // * cookies with paths and domains that don't match the cookie triggering this purge
+
+    uint32_t cookiePathLen = cookie->Path().Length();
+    if (cookiePathLen > 0 && cookie->Path().Last() == '/')
+      --cookiePathLen;
+
+    // This cookie is a candidate for eviction if we have no information about
+    // the source request, or if it is not a path or domain match against the
+    // source request.
+    bool isPrimaryEvictionCandidate = true;
+    if (aSource) {
+      bool pathMatches = StringBeginsWith(sourcePath, Substring(cookie->Path(), 0, cookiePathLen));
+      bool domainMatches = cookie->RawHost() == sourceHost ||
+          (cookie->IsDomain() && StringEndsWith(sourceHost, cookie->Host()));
+      isPrimaryEvictionCandidate = !pathMatches || !domainMatches;
+    }
+
+    int64_t lastAccessed = cookie->LastAccessed();
+    if (cookie->IsSession()) {
+      if (!oldestSessionCookie.entry || oldestSessionCookieTime > lastAccessed) {
+        oldestSessionCookieTime = lastAccessed;
+        oldestSessionCookie.entry = aEntry;
+        oldestSessionCookie.index = i;
+      }
+
+      if (isPrimaryEvictionCandidate &&
+          (!oldestNonMatchingSessionCookie.entry ||
+           oldestNonMatchingSessionCookieTime > lastAccessed)) {
+        oldestNonMatchingSessionCookieTime = lastAccessed;
+        oldestNonMatchingSessionCookie.entry = aEntry;
+        oldestNonMatchingSessionCookie.index = i;
+      }
+    } else if (isPrimaryEvictionCandidate &&
+               (!oldestNonMatchingNonSessionCookie.entry ||
+                oldestNonMatchingNonSessionCookieTime > lastAccessed)) {
+      oldestNonMatchingNonSessionCookieTime = lastAccessed;
+      oldestNonMatchingNonSessionCookie.entry = aEntry;
+      oldestNonMatchingNonSessionCookie.index = i;
+    }
+
     // Check if we've found the oldest cookie so far.
-    if (!aIter.entry || oldestTime > cookie->LastAccessed()) {
-      oldestTime = cookie->LastAccessed();
-      aIter.entry = aEntry;
-      aIter.index = i;
+    if (!oldestCookie.entry || oldestCookieTime > lastAccessed) {
+      oldestCookieTime = lastAccessed;
+      oldestCookie.entry = aEntry;
+      oldestCookie.index = i;
     }
   }
+
+  // Prefer to evict the oldest session cookies with a non-matching path/domain,
+  // followed by the oldest session cookie with a matching path/domain,
+  // followed by the oldest non-session cookie with a non-matching path/domain,
+  // resorting to the oldest non-session cookie with a matching path/domain.
+  if (oldestNonMatchingSessionCookie.entry) {
+    aIter = oldestNonMatchingSessionCookie;
+  } else if (oldestSessionCookie.entry) {
+    aIter = oldestSessionCookie;
+  } else if (oldestNonMatchingNonSessionCookie.entry) {
+    aIter = oldestNonMatchingNonSessionCookie;
+  } else {
+    aIter = oldestCookie;
+  }
 }
 
 // count the number of cookies stored by a particular host. this is provided by the
 // nsICookieManager2 interface.
 NS_IMETHODIMP
 nsCookieService::CountCookiesFromHost(const nsACString &aHost,
                                       uint32_t         *aCountFromHost)
 {
--- a/netwerk/cookie/nsCookieService.h
+++ b/netwerk/cookie/nsCookieService.h
@@ -307,17 +307,17 @@ class nsCookieService final : public nsI
     CookieStatus                  CheckPrefs(nsIURI *aHostURI, bool aIsForeign, const char *aCookieHeader);
     bool                          CheckDomain(nsCookieAttributes &aCookie, nsIURI *aHostURI, const nsCString &aBaseDomain, bool aRequireHostMatch);
     static bool                   CheckPath(nsCookieAttributes &aCookie, nsIURI *aHostURI);
     static bool                   CheckPrefixes(nsCookieAttributes &aCookie, bool aSecureRequest);
     static bool                   GetExpiry(nsCookieAttributes &aCookie, int64_t aServerTime, int64_t aCurrentTime);
     void                          RemoveAllFromMemory();
     already_AddRefed<nsIArray>    PurgeCookies(int64_t aCurrentTimeInUsec);
     bool                          FindCookie(const nsCookieKey& aKey, const nsAFlatCString &aHost, const nsAFlatCString &aName, const nsAFlatCString &aPath, nsListIter &aIter);
-    static void                   FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsListIter &aIter);
+    void                          FindStaleCookie(nsCookieEntry *aEntry, int64_t aCurrentTime, nsIURI* aSource, nsListIter &aIter);
     void                          NotifyRejected(nsIURI *aHostURI);
     void                          NotifyThirdParty(nsIURI *aHostURI, bool aAccepted, nsIChannel *aChannel);
     void                          NotifyChanged(nsISupports *aSubject, const char16_t *aData);
     void                          NotifyPurged(nsICookie2* aCookie);
     already_AddRefed<nsIArray>    CreatePurgeList(nsICookie2* aCookie);
     void                          UpdateCookieOldestTime(DBState* aDBState, nsCookie* aCookie);
 
     nsresult                      GetCookiesWithOriginAttributes(const mozilla::OriginAttributesPattern& aPattern, const nsCString& aBaseDomain, nsISimpleEnumerator **aEnumerator);
new file mode 100644
--- /dev/null
+++ b/netwerk/cookie/test/unit/test_eviction.js
@@ -0,0 +1,272 @@
+var {utils: Cu, interfaces: Ci, classes: Cc} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const BASE_HOSTNAMES = ["example.org", "example.co.uk"];
+const SUBDOMAINS = ["", "pub.", "www.", "other."];
+
+const cs = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService);
+const cm = cs.QueryInterface(Ci.nsICookieManager2);
+
+function run_test() {
+    var tests = [];
+    Services.prefs.setIntPref("network.cookie.staleThreshold", 0);
+    for (var host of BASE_HOSTNAMES) {
+        var base = SUBDOMAINS[0] + host;
+        var sub = SUBDOMAINS[1] + host;
+        var other = SUBDOMAINS[2] + host;
+        var another = SUBDOMAINS[3] + host;
+        tests.push([host, test_basic_eviction.bind(this, base, sub, other, another)]);
+        add_task(function* a() {
+            var t = tests.splice(0, 1)[0];
+            do_print('testing with host ' + t[0]);
+            yield t[1]();
+            cm.removeAll();
+        });
+        tests.push([host, test_domain_or_path_matches_not_both.bind(this, base, sub, other, another)]);
+        add_task(function*() {
+            var t = tests.splice(0, 1)[0];
+            do_print('testing with host ' + t[0]);
+            yield t[1]();
+            cm.removeAll();
+        });
+    }
+    add_task(function*() {
+        yield test_localdomain();
+    });
+
+    run_next_test();
+}
+
+// Verify that subdomains of localhost are treated as separate hosts and aren't considered
+// candidates for eviction.
+function* test_localdomain() {
+    Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
+
+    const BASE_URI = Services.io.newURI("http://localhost", null, null);
+    const BASE_BAR = Services.io.newURI("http://localhost/bar", null, null);
+    const OTHER_URI = Services.io.newURI("http://other.localhost", null, null);
+    const OTHER_BAR = Services.io.newURI("http://other.localhost/bar", null, null);
+    
+    yield setCookie("session_no_path", null, null, null, BASE_URI);
+    yield setCookie("session_bar_path", null, "/bar", null, BASE_BAR);
+
+    yield setCookie("session_no_path", null, null, null, OTHER_URI);
+    yield setCookie("session_bar_path", null, "/bar", null, OTHER_BAR);
+
+    verifyCookies(['session_no_path',
+                   'session_bar_path'], BASE_URI);
+    verifyCookies(['session_no_path',
+                   'session_bar_path'], OTHER_URI);
+
+    yield setCookie("session_another_no_path", null, null, null, BASE_URI);
+    verifyCookies(['session_no_path',
+                   'session_another_no_path'], BASE_URI);
+
+    yield setCookie("session_another_no_path", null, null, null, OTHER_URI);
+    verifyCookies(['session_no_path',
+                   'session_another_no_path'], OTHER_URI);
+}
+
+// Ensure that cookies are still considered candidates for eviction if either the domain
+// or path matches, but not both.
+function* test_domain_or_path_matches_not_both(base_host,
+                                               subdomain_host,
+                                               other_subdomain_host,
+                                               another_subdomain_host) {
+    Services.prefs.setIntPref("network.cookie.maxPerHost", 2);
+
+    const BASE_URI = Services.io.newURI("http://" + base_host, null, null);
+    const PUB_FOO_PATH = Services.io.newURI("http://" + subdomain_host + "/foo", null, null);
+    const WWW_BAR_PATH = Services.io.newURI("http://" + other_subdomain_host + "/bar", null, null);
+    const OTHER_BAR_PATH = Services.io.newURI("http://" + another_subdomain_host + "/bar", null, null);
+    const PUB_BAR_PATH = Services.io.newURI("http://" + subdomain_host + "/bar", null, null);
+    const WWW_FOO_PATH = Services.io.newURI("http://" + other_subdomain_host + "/foo", null, null);
+
+    yield setCookie("session_pub_with_foo_path", subdomain_host, "/foo", null, PUB_FOO_PATH);
+    yield setCookie("session_www_with_bar_path", other_subdomain_host, "/bar", null, WWW_BAR_PATH);
+    verifyCookies(['session_pub_with_foo_path',
+                   'session_www_with_bar_path'], BASE_URI);
+
+    yield setCookie("session_pub_with_bar_path", subdomain_host, "/bar", null, PUB_BAR_PATH);
+    verifyCookies(['session_www_with_bar_path',
+                   'session_pub_with_bar_path'], BASE_URI);
+
+    yield setCookie("session_other_with_bar_path", another_subdomain_host, "/bar", null, OTHER_BAR_PATH);
+    verifyCookies(['session_pub_with_bar_path',
+                   'session_other_with_bar_path'], BASE_URI);
+}
+
+function* test_basic_eviction(base_host, subdomain_host, other_subdomain_host) {
+    Services.prefs.setIntPref("network.cookie.maxPerHost", 5);
+
+    const BASE_URI = Services.io.newURI("http://" + base_host, null, null);
+    const SUBDOMAIN_URI = Services.io.newURI("http://" + subdomain_host, null, null);
+    const OTHER_SUBDOMAIN_URI = Services.io.newURI("http://" + other_subdomain_host, null, null);
+    const FOO_PATH = Services.io.newURI("http://" + base_host + "/foo", null, null);
+    const BAR_PATH = Services.io.newURI("http://" + base_host + "/bar", null, null);
+    const ALL_SUBDOMAINS = '.' + base_host;
+    const OTHER_SUBDOMAIN = other_subdomain_host;
+
+    // Initialize the set of cookies with a mix of non-session cookies with no path,
+    // and session cookies with explicit paths. Any subsequent cookies added will cause
+    // existing cookies to be evicted.
+    yield setCookie("non_session_non_path_non_domain", null, null, 100000, BASE_URI);
+    yield setCookie("non_session_non_path_subdomain", ALL_SUBDOMAINS, null, 100000, SUBDOMAIN_URI);
+    yield setCookie("session_non_path_pub_domain", OTHER_SUBDOMAIN, null, null, OTHER_SUBDOMAIN_URI);
+    yield setCookie("session_foo_path", null, "/foo", null, FOO_PATH);
+    yield setCookie("session_bar_path", null, "/bar", null, BAR_PATH);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'session_non_path_pub_domain',
+                   'session_foo_path',
+                   'session_bar_path'], BASE_URI);
+
+    // Ensure that cookies set for the / path appear more recent.
+    cs.getCookieString(OTHER_SUBDOMAIN_URI, null)
+    verifyCookies(['non_session_non_path_non_domain',
+                   'session_foo_path',
+                   'session_bar_path',
+                   'non_session_non_path_subdomain',
+                   'session_non_path_pub_domain'], BASE_URI);
+
+    // Evict oldest session cookie that does not match example.org/foo (session_bar_path)
+    yield setCookie("session_foo_path_2", null, "/foo", null, FOO_PATH);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'session_foo_path',
+                   'non_session_non_path_subdomain',
+                   'session_non_path_pub_domain',
+                   'session_foo_path_2'], BASE_URI);
+
+    // Evict oldest session cookie that does not match example.org/bar (session_foo_path)
+    yield setCookie("session_bar_path_2", null, "/bar", null, BAR_PATH);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'session_non_path_pub_domain',
+                   'session_foo_path_2',
+                   'session_bar_path_2'], BASE_URI);
+
+    // Evict oldest session cookie that does not match example.org/ (session_non_path_pub_domain)
+    yield setCookie("non_session_non_path_non_domain_2", null, null, 100000, BASE_URI);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'session_foo_path_2',
+                   'session_bar_path_2',
+                   'non_session_non_path_non_domain_2'], BASE_URI);
+
+    // Evict oldest session cookie that does not match example.org/ (session_foo_path_2)
+    yield setCookie("session_non_path_non_domain_3", null, null, null, BASE_URI);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'session_bar_path_2',
+                   'non_session_non_path_non_domain_2',
+                   'session_non_path_non_domain_3'], BASE_URI);
+
+    // Evict oldest session cookie; all such cookies match example.org/bar (session_bar_path_2)
+    yield setCookie("non_session_non_path_non_domain_3", null, null, 100000, BAR_PATH);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'non_session_non_path_non_domain_2',
+                   'session_non_path_non_domain_3',
+                   'non_session_non_path_non_domain_3'], BASE_URI);
+
+    // Evict oldest session cookie, even though it matches pub.example.org (session_non_path_non_domain_3)
+    yield setCookie("non_session_non_path_pub_domain", null, null, 100000, OTHER_SUBDOMAIN_URI);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'non_session_non_path_non_domain_2',
+                   'non_session_non_path_non_domain_3',
+                   'non_session_non_path_pub_domain'], BASE_URI);
+
+    // All session cookies have been evicted.
+    // Evict oldest non-session non-domain-matching cookie (non_session_non_path_pub_domain)
+    yield setCookie("non_session_bar_path_non_domain", null, '/bar', 100000, BAR_PATH);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'non_session_non_path_non_domain_2',
+                   'non_session_non_path_non_domain_3',
+                   'non_session_bar_path_non_domain'], BASE_URI);
+
+    // Evict oldest non-session non-path-matching cookie (non_session_bar_path_non_domain)
+    yield setCookie("non_session_non_path_non_domain_4", null, null, 100000, BASE_URI);
+    verifyCookies(['non_session_non_path_non_domain',
+                   'non_session_non_path_subdomain',
+                   'non_session_non_path_non_domain_2',
+                   'non_session_non_path_non_domain_3',
+                   'non_session_non_path_non_domain_4'], BASE_URI);
+
+    // At this point all remaining cookies are non-session cookies, have a path of /,
+    // and either don't have a domain or have one that matches subdomains.
+    // They will therefore be evicted from oldest to newest if all new cookies added share
+    // similar characteristics.
+}
+
+// Verify that the given cookie names exist, and are ordered from least to most recently accessed
+function verifyCookies(names, uri) {
+    do_check_eq(cm.countCookiesFromHost(uri.host), names.length);
+    let cookies = cm.getCookiesFromHost(uri.host, {});
+    let actual_cookies = [];
+    while (cookies.hasMoreElements()) {
+        let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
+        actual_cookies.push(cookie);
+    }
+    if (names.length != actual_cookies.length) {
+        let left = names.filter(function(n) {
+            return actual_cookies.findIndex(function(c) {
+                return c.name == n;
+            }) == -1;
+        });
+        let right = actual_cookies.filter(function(c) {
+            return names.findIndex(function(n) {
+                return c.name == n;
+            }) == -1;
+        }).map(function(c) { return c.name });
+        if (left.length) {
+            do_print("unexpected cookies: " + left);
+        }
+        if (right.length) {
+            do_print("expected cookies: " + right);
+        }
+    }
+    do_check_eq(names.length, actual_cookies.length);
+    actual_cookies.sort(function(a, b) {
+        if (a.lastAccessed < b.lastAccessed)
+            return -1;
+        if (a.lastAccessed > b.lastAccessed)
+            return 1;
+        return 0;
+    });
+    for (var i = 0; i < names.length; i++) {
+        do_check_eq(names[i], actual_cookies[i].name);
+        do_check_eq(names[i].startsWith('session'), actual_cookies[i].isSession);
+    }
+}
+
+var lastValue = 0
+function* setCookie(name, domain, path, maxAge, url) {
+    let value = name + "=" + ++lastValue;
+    var s = 'setting cookie ' + value;
+    if (domain) {
+        value += "; Domain=" + domain;
+        s += ' (d=' + domain + ')';
+    }
+    if (path) {
+        value += "; Path=" + path;
+        s += ' (p=' + path + ')';
+    }
+    if (maxAge) {
+        value += "; Max-Age=" + maxAge;
+        s += ' (non-session)';
+    } else {
+        s += ' (session)';
+    }
+    s += ' for ' + url.spec;
+    do_print(s);
+    cs.setCookieStringFromHttp(url, null, null, value, null, null);
+    return new Promise(function(resolve) {
+        // Windows XP has low precision timestamps that cause our cookie eviction
+        // algorithm to produce different results from other platforms. We work around
+        // this by ensuring that there's a clear gap between each cookie update.
+        do_timeout(10, resolve);
+    })
+}
--- a/netwerk/cookie/test/unit/xpcshell.ini
+++ b/netwerk/cookie/test/unit/xpcshell.ini
@@ -3,8 +3,9 @@ head =
 tail = 
 skip-if = toolkit == 'gonk'
 
 [test_bug643051.js]
 [test_bug1155169.js]
 [test_bug1267910.js]
 [test_parser_0001.js]
 [test_parser_0019.js]
+[test_eviction.js]
\ No newline at end of file
--- a/netwerk/protocol/http/AlternateServices.cpp
+++ b/netwerk/protocol/http/AlternateServices.cpp
@@ -2,24 +2,35 @@
 /* vim: set sw=2 ts=8 et tw=80 : */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "HttpLog.h"
 
 #include "AlternateServices.h"
+#include "LoadInfo.h"
 #include "nsEscape.h"
 #include "nsHttpConnectionInfo.h"
+#include "nsHttpChannel.h"
 #include "nsHttpHandler.h"
 #include "nsThreadUtils.h"
+#include "nsHttpTransaction.h"
 #include "NullHttpTransaction.h"
 #include "nsISSLStatusProvider.h"
 #include "nsISSLStatus.h"
 #include "nsISSLSocketControl.h"
+#include "nsIWellKnownOpportunisticUtils.h"
+
+/* RFC 7838 Alternative Services
+   http://httpwg.org/http-extensions/opsec.html
+    note that connections currently do not do mixed-scheme (the I attribute
+    in the ConnectionInfo prevents it) but could, do not honor tls-commit and should
+    not, and always require authentication
+*/
 
 namespace mozilla {
 namespace net {
 
 // function places true in outIsHTTPS if scheme is https, false if
 // http, and returns an error if neither. originScheme passed into
 // alternate service should already be normalized to those lower case
 // strings by the URI parser (and so there is an assert)- this is an extra check.
@@ -118,54 +129,61 @@ AltSvcMapping::ProcessHeader(const nsCSt
     uint32_t spdyIndex;
     SpdyInformation *spdyInfo = gHttpHandler->SpdyInfo();
     if (!(NS_SUCCEEDED(spdyInfo->GetNPNIndex(npnToken, &spdyIndex)) &&
           spdyInfo->ProtocolEnabled(spdyIndex))) {
       LOG(("Alt Svc unknown protocol %s, ignoring", npnToken.get()));
       continue;
     }
 
-    RefPtr<AltSvcMapping> mapping = new AltSvcMapping(originScheme,
-                                                        originHost, originPort,
-                                                        username, privateBrowsing,
-                                                        NowInSeconds() + maxage,
-                                                        hostname, portno, npnToken);
+    RefPtr<AltSvcMapping> mapping = new AltSvcMapping(gHttpHandler->ConnMgr()->GetStoragePtr(),
+                                                      gHttpHandler->ConnMgr()->StorageEpoch(),
+                                                      originScheme,
+                                                      originHost, originPort,
+                                                      username, privateBrowsing,
+                                                      NowInSeconds() + maxage,
+                                                      hostname, portno, npnToken);
     if (mapping->TTL() <= 0) {
       LOG(("Alt Svc invalid map"));
       mapping = nullptr;
       // since this isn't a parse error, let's clear any existing mapping
       // as that would have happened if we had accepted the parameters.
       gHttpHandler->ConnMgr()->ClearHostMapping(originHost, originPort);
     } else {
       gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps,
                                             originAttributes);
     }
   }
 }
 
-AltSvcMapping::AltSvcMapping(const nsACString &originScheme,
+AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch,
+                             const nsACString &originScheme,
                              const nsACString &originHost,
                              int32_t originPort,
                              const nsACString &username,
                              bool privateBrowsing,
                              uint32_t expiresAt,
                              const nsACString &alternateHost,
                              int32_t alternatePort,
                              const nsACString &npnToken)
-  : mAlternateHost(alternateHost)
+  : mStorage(storage)
+  , mStorageEpoch(epoch)
+  , mAlternateHost(alternateHost)
   , mAlternatePort(alternatePort)
   , mOriginHost(originHost)
   , mOriginPort(originPort)
   , mUsername(username)
   , mPrivate(privateBrowsing)
   , mExpiresAt(expiresAt)
   , mValidated(false)
-  , mRunning(false)
+  , mMixedScheme(false)
   , mNPNToken(npnToken)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) {
     LOG(("AltSvcMapping ctor %p invalid scheme\n", this));
     mExpiresAt = 0; // invalid
   }
 
   if (mAlternatePort == -1) {
     mAlternatePort = mHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT;
   }
@@ -217,19 +235,73 @@ AltSvcMapping::MakeHashKey(nsCString &ou
 
 int32_t
 AltSvcMapping::TTL()
 {
   return mExpiresAt - NowInSeconds();
 }
 
 void
+AltSvcMapping::SyncString(nsCString str)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mStorage->Put(HashKey(), str,
+                mPrivate ? DataStorage_Private : DataStorage_Persistent);
+}
+
+void
+AltSvcMapping::Sync()
+{
+  if (!mStorage) {
+    return;
+  }
+  nsCString value;
+  Serialize(value);
+
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> r;
+    r = NewRunnableMethod<nsCString>(this,
+                                     &AltSvcMapping::SyncString,
+                                     value);
+    NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL);
+    return;
+  }
+
+  mStorage->Put(HashKey(), value,
+                mPrivate ? DataStorage_Private : DataStorage_Persistent);
+}
+
+void
+AltSvcMapping::SetValidated(bool val)
+{
+  mValidated = val;
+  Sync();
+}
+
+void
+AltSvcMapping::SetMixedScheme(bool val)
+{
+  mMixedScheme = val;
+  Sync();
+}
+
+void
+AltSvcMapping::SetExpiresAt(int32_t val)
+{
+  mExpiresAt = val;
+  Sync();
+}
+
+void
 AltSvcMapping::SetExpired()
 {
+  LOG(("AltSvcMapping SetExpired %p origin %s alternate %s\n", this,
+       mOriginHost.get(), mAlternateHost.get()));
   mExpiresAt = NowInSeconds() - 1;
+  Sync();
 }
 
 bool
 AltSvcMapping::RouteEquals(AltSvcMapping *map)
 {
   MOZ_ASSERT(map->mHashKey.Equals(mHashKey));
   return mAlternateHost.Equals(map->mAlternateHost) &&
     (mAlternatePort == map->mAlternatePort) &&
@@ -240,73 +312,145 @@ void
 AltSvcMapping::GetConnectionInfo(nsHttpConnectionInfo **outCI,
                                  nsProxyInfo *pi,
                                  const NeckoOriginAttributes &originAttributes)
 {
   RefPtr<nsHttpConnectionInfo> ci =
     new nsHttpConnectionInfo(mOriginHost, mOriginPort, mNPNToken,
                              mUsername, pi, originAttributes,
                              mAlternateHost, mAlternatePort);
-  ci->SetInsecureScheme(!mHttps);
+
+  // http:// without the mixed-scheme attribute needs to be segmented in the
+  // connection manager connection information hash with this attribute
+  if (!mHttps && !mMixedScheme) {
+    ci->SetInsecureScheme(true);
+  }
   ci->SetPrivate(mPrivate);
   ci.forget(outCI);
 }
 
+void
+AltSvcMapping::Serialize(nsCString &out)
+{
+  out = mHttps ? NS_LITERAL_CSTRING("https:") : NS_LITERAL_CSTRING("http:");
+  out.Append(mOriginHost);
+  out.Append(':');
+  out.AppendInt(mOriginPort);
+  out.Append(':');
+  out.Append(mAlternateHost);
+  out.Append(':');
+  out.AppendInt(mAlternatePort);
+  out.Append(':');
+  out.Append(mUsername);
+  out.Append(':');
+  out.Append(mPrivate ? 'y' : 'n');
+  out.Append(':');
+  out.AppendInt(mExpiresAt);
+  out.Append(':');
+  out.Append(mNPNToken);
+  out.Append(':');
+  out.Append(mValidated ? 'y' : 'n');
+  out.Append(':');
+  out.AppendInt(mStorageEpoch);
+  out.Append(':');
+  out.Append(mMixedScheme ? 'y' : 'n');
+  out.Append(':');
+}
+
+AltSvcMapping::AltSvcMapping(DataStorage *storage, int32_t epoch, const nsCString &str)
+  : mStorage(storage)
+  , mStorageEpoch(epoch)
+{
+  mValidated = false;
+  nsresult code;
+
+  // The the do {} while(0) loop acts like try/catch(e){} with the break in _NS_NEXT_TOKEN
+  do {
+#ifdef _NS_NEXT_TOKEN
+COMPILER ERROR
+#endif
+    #define _NS_NEXT_TOKEN start = idx + 1; idx = str.FindChar(':', start); if (idx < 0) break;
+    int32_t start = 0;
+    int32_t idx;
+    idx = str.FindChar(':', start); if (idx < 0) break;
+    mHttps = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("https"));
+    _NS_NEXT_TOKEN;
+    mOriginHost = Substring(str, start, idx - start);
+    _NS_NEXT_TOKEN;
+    mOriginPort = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+    _NS_NEXT_TOKEN;
+    mAlternateHost = Substring(str, start, idx - start);
+    _NS_NEXT_TOKEN;
+    mAlternatePort = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+    _NS_NEXT_TOKEN;
+    mUsername = Substring(str, start, idx - start);
+    _NS_NEXT_TOKEN;
+    mPrivate = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+    _NS_NEXT_TOKEN;
+    mExpiresAt = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+    _NS_NEXT_TOKEN;
+    mNPNToken = Substring(str, start, idx - start);
+    _NS_NEXT_TOKEN;
+    mValidated = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+    _NS_NEXT_TOKEN;
+    mStorageEpoch = nsCString(Substring(str, start, idx - start)).ToInteger(&code);
+    _NS_NEXT_TOKEN;
+    mMixedScheme = Substring(str, start, idx - start).Equals(NS_LITERAL_CSTRING("y"));
+    #undef _NS_NEXT_TOKEN
+
+    MakeHashKey(mHashKey, mHttps ? NS_LITERAL_CSTRING("https") : NS_LITERAL_CSTRING("http"),
+                mOriginHost, mOriginPort, mPrivate);
+  } while (0);
+}
+
 // This is the asynchronous null transaction used to validate
-// an alt-svc advertisement
+// an alt-svc advertisement only for https://
 class AltSvcTransaction final : public NullHttpTransaction
 {
 public:
     AltSvcTransaction(AltSvcMapping *map,
                       nsHttpConnectionInfo *ci,
                       nsIInterfaceRequestor *callbacks,
                       uint32_t caps)
-    : NullHttpTransaction(ci, callbacks, caps)
+    : NullHttpTransaction(ci, callbacks, caps & ~NS_HTTP_ALLOW_KEEPALIVE)
     , mMapping(map)
-    , mRunning(false)
+    , mRunning(true)
     , mTriedToValidate(false)
     , mTriedToWrite(false)
   {
-    MOZ_ASSERT(mMapping);
     LOG(("AltSvcTransaction ctor %p map %p [%s -> %s]",
          this, map, map->OriginHost().get(), map->AlternateHost().get()));
+    MOZ_ASSERT(mMapping);
+    MOZ_ASSERT(mMapping->HTTPS());
   }
 
-  ~AltSvcTransaction()
+  ~AltSvcTransaction() override
   {
     LOG(("AltSvcTransaction dtor %p map %p running %d",
          this, mMapping.get(), mRunning));
 
     if (mRunning) {
-      MOZ_ASSERT(mMapping->IsRunning());
       MaybeValidate(NS_OK);
     }
     if (!mMapping->Validated()) {
       // try again later
       mMapping->SetExpiresAt(NowInSeconds() + 2);
     }
     LOG(("AltSvcTransaction dtor %p map %p validated %d [%s]",
          this, mMapping.get(), mMapping->Validated(),
          mMapping->HashKey().get()));
-    mMapping->SetRunning(false);
   }
 
-  void StartTransaction()
-  {
-    LOG(("AltSvcTransaction::StartTransaction() %p", this));
-
-    MOZ_ASSERT(!mRunning);
-    MOZ_ASSERT(!mMapping->IsRunning());
-    mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
-    mRunning = true;
-    mMapping->SetRunning(true);
-  }
-
+private:
+  // check on alternate route.
+  // also evaluate 'reasonable assurances' for opportunistic security
   void MaybeValidate(nsresult reason)
   {
+    MOZ_ASSERT(mMapping->HTTPS()); // http:// uses the .wk path
+
     if (mTriedToValidate) {
       return;
     }
     mTriedToValidate = true;
 
     LOG(("AltSvcTransaction::MaybeValidate() %p reason=%x running=%d conn=%p write=%d",
          this, reason, mRunning, mConnection.get(), mTriedToWrite));
 
@@ -319,56 +463,40 @@ public:
     if (NS_FAILED(reason) || !mRunning || !mConnection) {
       LOG(("AltSvcTransaction::MaybeValidate %p Failed due to precondition", this));
       return;
     }
 
     // insist on >= http/2
     uint32_t version = mConnection->Version();
     LOG(("AltSvcTransaction::MaybeValidate() %p version %d\n", this, version));
-    if (version < HTTP_VERSION_2) {
+    if (version != HTTP_VERSION_2) {
       LOG(("AltSvcTransaction::MaybeValidate %p Failed due to protocol version", this));
       return;
     }
 
     nsCOMPtr<nsISupports> secInfo;
     mConnection->GetSecurityInfo(getter_AddRefs(secInfo));
     nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
-    bool bypassAuth = socketControl
-                    ? socketControl->GetBypassAuthentication()
-                    : false;
 
-    LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p bypass=%d",
-         this, socketControl.get(), bypassAuth));
-
-    if (bypassAuth) {
-      if (mMapping->HTTPS()) {
-        MOZ_ASSERT(false); // cannot happen but worth the runtime sanity check
-        LOG(("AltSvcTransaction::MaybeValidate %p"
-             "somehow indicates bypassAuth on https:// origin\n", this));
-        return;
-      }
-
-      LOG(("AltSvcTransaction::MaybeValidate() %p "
-           "validating alternate service because relaxed", this));
-      mMapping->SetValidated(true);
-      return;
-    }
+    LOG(("AltSvcTransaction::MaybeValidate() %p socketControl=%p\n",
+         this, socketControl.get()));
 
     if (socketControl->GetFailedVerification()) {
       LOG(("AltSvcTransaction::MaybeValidate() %p "
            "not validated due to auth error", this));
       return;
     }
 
     LOG(("AltSvcTransaction::MaybeValidate() %p "
-         "validating alternate service with auth check", this));
+         "validating alternate service with successful auth check", this));
     mMapping->SetValidated(true);
   }
 
+public:
   void Close(nsresult reason) override
   {
     LOG(("AltSvcTransaction::Close() %p reason=%x running %d",
          this, reason, mRunning));
 
     MaybeValidate(reason);
     if (!mMapping->Validated() && mConnection) {
       mConnection->DontReuse();
@@ -380,107 +508,457 @@ public:
                         uint32_t count, uint32_t *countRead) override
   {
     LOG(("AltSvcTransaction::ReadSegements() %p\n"));
     mTriedToWrite = true;
     return NullHttpTransaction::ReadSegments(reader, count, countRead);
   }
 
 private:
-  RefPtr<AltSvcMapping> mMapping;
+  RefPtr<AltSvcMapping>   mMapping;
   uint32_t                mRunning : 1;
   uint32_t                mTriedToValidate : 1;
   uint32_t                mTriedToWrite : 1;
 };
 
+class WellKnownChecker
+{
+public:
+  WellKnownChecker(nsIURI *uri, const nsCString &origin, uint32_t caps, nsHttpConnectionInfo *ci, AltSvcMapping *mapping)
+    : mWaiting(2) // waiting for 2 channels (default and alternate) to complete
+    , mOrigin(origin)
+    , mAlternatePort(ci->RoutedPort())
+    , mMapping(mapping)
+    , mCI(ci)
+    , mURI(uri)
+    , mCaps(caps)
+  {
+    LOG(("WellKnownChecker ctor %p\n", this));
+    MOZ_ASSERT(!mMapping->HTTPS());
+  }
+
+  nsresult Start()
+  {
+    LOG(("WellKnownChecker::Start %p\n", this));
+    nsCOMPtr<nsILoadInfo> loadInfo = new LoadInfo(nsContentUtils::GetSystemPrincipal(),
+                                                  nullptr, nullptr,
+                                                  nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+                                                  nsIContentPolicy::TYPE_OTHER);
+
+    RefPtr<nsHttpChannel> chan = new nsHttpChannel();
+    nsresult rv;
+
+    mTransactionAlternate = new TransactionObserver(chan, this);
+    RefPtr<nsHttpConnectionInfo> newCI = mCI->Clone();
+    rv = MakeChannel(chan, mTransactionAlternate, newCI, mURI, mCaps, loadInfo);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    chan = new nsHttpChannel();
+    mTransactionOrigin = new TransactionObserver(chan, this);
+    newCI = nullptr;
+    return MakeChannel(chan, mTransactionOrigin, newCI, mURI, mCaps, loadInfo);
+  }
+
+  void Done(TransactionObserver *finished)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    LOG(("WellKnownChecker::Done %p waiting for %d\n", this, mWaiting));
+
+    mWaiting--; // another channel is complete
+    if (!mWaiting) { // there are all complete!
+      nsAutoCString mAlternateCT, mOriginCT;
+      mTransactionOrigin->mChannel->GetContentType(mOriginCT);
+      mTransactionAlternate->mChannel->GetContentType(mAlternateCT);
+      nsCOMPtr<nsIWellKnownOpportunisticUtils> uu = do_CreateInstance(NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID);
+      bool accepted = false;
+
+      if (!mTransactionOrigin->mStatusOK) {
+        LOG(("WellKnownChecker::Done %p origin was not 200 response code\n", this));
+      } else if (!mTransactionAlternate->mAuthOK) {
+        LOG(("WellKnownChecker::Done %p alternate was not TLS authenticated\n", this));
+      } else if (!mTransactionAlternate->mStatusOK) {
+        LOG(("WellKnownChecker::Done %p alternate was not 200 response code\n", this));
+      } else if (!mTransactionAlternate->mVersionOK) {
+        LOG(("WellKnownChecker::Done %p alternate was not at least h2\n", this));
+      } else if (!mTransactionAlternate->mWKResponse.Equals(mTransactionOrigin->mWKResponse)) {
+        LOG(("WellKnownChecker::Done %p alternate and origin "
+             ".wk representations don't match\norigin: %s\alternate:%s\n", this,
+             mTransactionOrigin->mWKResponse.get(),
+             mTransactionAlternate->mWKResponse.get()));
+      } else if (!mAlternateCT.Equals(mOriginCT)) {
+        LOG(("WellKnownChecker::Done %p alternate and origin content types dont match\n", this));
+      } else if (!mAlternateCT.Equals(NS_LITERAL_CSTRING("application/json"))) {
+        LOG(("WellKnownChecker::Done %p .wk content type is %s\n", this, mAlternateCT.get()));
+      } else if (!uu) {
+        LOG(("WellKnownChecker::Done %p json parser service unavailable\n", this));
+      } else {
+        accepted = true;
+      }
+
+      if (accepted) {
+        MOZ_ASSERT(!mMapping->HTTPS()); // https:// does not use .wk
+
+        nsresult rv = uu->Verify(mTransactionAlternate->mWKResponse, mOrigin, mAlternatePort);
+        if (NS_SUCCEEDED(rv)) {
+          bool validWK = false;
+          bool mixedScheme = false;
+          int32_t lifetime = 0;
+          uu->GetValid(&validWK);
+          uu->GetLifetime(&lifetime);
+          uu->GetMixed(&mixedScheme);
+          if (!validWK) {
+            LOG(("WellKnownChecker::Done %p json parser declares invalid\n%s\n", this, mTransactionAlternate->mWKResponse.get()));
+            accepted = false;
+          }
+          if (accepted && (lifetime > 0)) {
+            if (mMapping->TTL() > lifetime) {
+              LOG(("WellKnownChecker::Done %p atl-svc lifetime reduced by .wk\n", this));
+              mMapping->SetExpiresAt(NowInSeconds() + lifetime);
+            } else {
+              LOG(("WellKnownChecker::Done %p .wk lifetime exceeded alt-svc ma so ignored\n", this));
+            }
+          }
+          if (accepted && mixedScheme) {
+            mMapping->SetMixedScheme(true);
+            LOG(("WellKnownChecker::Done %p atl-svc .wk allows mixed scheme\n", this));
+          }
+        } else {
+          LOG(("WellKnownChecker::Done %p .wk jason eval failed to run\n", this));
+          accepted = false;
+        }
+      }
+
+      MOZ_ASSERT(!mMapping->Validated());
+      if (accepted) {
+        LOG(("WellKnownChecker::Done %p Alternate for %s ACCEPTED\n", this, mOrigin.get()));
+        mMapping->SetValidated(true);
+      } else {
+        LOG(("WellKnownChecker::Done %p Alternate for %s FAILED\n", this, mOrigin.get()));
+        // try again soon
+        mMapping->SetExpiresAt(NowInSeconds() + 2);
+      }
+
+      delete this;
+    }
+  }
+
+  ~WellKnownChecker()
+  {
+    LOG(("WellKnownChecker dtor %p\n", this));
+  }
+
+private:
+  nsresult
+  MakeChannel(nsHttpChannel *chan, TransactionObserver *obs, nsHttpConnectionInfo *ci,
+              nsIURI *uri, uint32_t caps, nsILoadInfo *loadInfo)
+  {
+    nsID channelId;
+    nsLoadFlags flags;
+    if (NS_FAILED(gHttpHandler->NewChannelId(&channelId)) ||
+        NS_FAILED(chan->Init(uri, caps, nullptr, 0, nullptr, channelId)) ||
+        NS_FAILED(chan->SetAllowAltSvc(false)) ||
+        NS_FAILED(chan->SetRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_ERROR)) ||
+        NS_FAILED(chan->SetLoadInfo(loadInfo)) ||
+        NS_FAILED(chan->GetLoadFlags(&flags))) {
+      return NS_ERROR_FAILURE;
+    }
+    flags |= HttpBaseChannel::LOAD_BYPASS_CACHE;
+    if (NS_FAILED(chan->SetLoadFlags(flags))) {
+      return NS_ERROR_FAILURE;
+    }
+    chan->SetTransactionObserver(obs);
+    chan->SetConnectionInfo(ci);
+    return chan->AsyncOpen2(obs);
+  }
+
+  RefPtr<TransactionObserver>  mTransactionAlternate;
+  RefPtr<TransactionObserver>  mTransactionOrigin;
+  uint32_t                     mWaiting; // semaphore
+  nsCString                    mOrigin;
+  int32_t                      mAlternatePort;
+  RefPtr<AltSvcMapping>        mMapping;
+  RefPtr<nsHttpConnectionInfo> mCI;
+  nsCOMPtr<nsIURI>             mURI;
+  uint32_t                     mCaps;
+};
+
+NS_IMPL_ISUPPORTS(TransactionObserver, nsIStreamListener)
+
+TransactionObserver::TransactionObserver(nsHttpChannel *channel, WellKnownChecker *checker)
+  : mChannel(channel)
+  , mChecker(checker)
+  , mRanOnce(false)
+  , mAuthOK(false)
+  , mVersionOK(false)
+  , mStatusOK(false)
+{
+  LOG(("TransactionObserver ctor %p channel %p checker %p\n", this, channel, checker));
+  mChannelRef = do_QueryInterface((nsIHttpChannel *)channel);
+}
+
+void
+TransactionObserver::Complete(nsHttpTransaction *aTrans, nsresult reason)
+{
+  // socket thread
+  MOZ_ASSERT(!NS_IsMainThread());
+  if (mRanOnce) {
+    return;
+  }
+  mRanOnce = true;
+
+  RefPtr<nsAHttpConnection> conn = aTrans->GetConnectionReference();
+  LOG(("TransactionObserver::Complete %p aTrans %p reason %x conn %p\n",
+       this, aTrans, reason, conn.get()));
+  if (!conn) {
+    return;
+  }
+  uint32_t version = conn->Version();
+  mVersionOK = (((reason == NS_BASE_STREAM_CLOSED) || (reason == NS_OK)) &&
+                conn->Version() == HTTP_VERSION_2);
+
+  nsCOMPtr<nsISupports> secInfo;
+  conn->GetSecurityInfo(getter_AddRefs(secInfo));
+  nsCOMPtr<nsISSLSocketControl> socketControl = do_QueryInterface(secInfo);
+  LOG(("TransactionObserver::Complete version %u socketControl %p\n",
+       version, socketControl.get()));
+  if (!socketControl) {
+    return;
+  }
+
+  mAuthOK = !socketControl->GetFailedVerification();
+  LOG(("TransactionObserve::Complete %p trans %p authOK %d versionOK %d\n",
+       this, aTrans, mAuthOK, mVersionOK));
+}
+
+#define MAX_WK 32768
+
+NS_IMETHODIMP
+TransactionObserver::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // only consider the first 32KB.. because really.
+  mWKResponse.SetCapacity(MAX_WK);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
+                                     nsIInputStream *aStream, uint64_t aOffset, uint32_t aCount)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  uint64_t newLen = aCount + mWKResponse.Length();
+  if (newLen < MAX_WK) {
+    char *startByte =  reinterpret_cast<char *>(mWKResponse.BeginWriting()) + mWKResponse.Length();
+    uint32_t amtRead;
+    if (NS_SUCCEEDED(aStream->Read(startByte, aCount, &amtRead))) {
+      MOZ_ASSERT(mWKResponse.Length() + amtRead < MAX_WK);
+      mWKResponse.SetLength(mWKResponse.Length() + amtRead);
+      LOG(("TransactionObserver onDataAvailable %p read %d of .wk [%d]\n",
+           this, amtRead, mWKResponse.Length()));
+    } else {
+      LOG(("TransactionObserver onDataAvailable %p read error\n", this));
+    }
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+TransactionObserver::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult code)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  LOG(("TransactionObserver onStopRequest %p code %x\n", this, code));
+  if (NS_SUCCEEDED(code)) {
+    nsHttpResponseHead *hdrs = mChannel->GetResponseHead();
+    LOG(("TransactionObserver onStopRequest %p http resp %d\n",
+         this, hdrs ? hdrs->Status() : -1));
+    mStatusOK = hdrs && (hdrs->Status() == 200);
+  }
+  if (mChecker) {
+    mChecker->Done(this);
+  }
+  return NS_OK;
+}
+
+already_AddRefed<AltSvcMapping>
+AltSvcCache::LookupMapping(const nsCString &key, bool privateBrowsing)
+{
+  LOG(("AltSvcCache::LookupMapping %p %s\n", this, key.get()));
+  if (!mStorage) {
+    LOG(("AltSvcCache::LookupMapping %p no backing store\n", this));
+    return nullptr;
+  }
+  nsCString val(mStorage->Get(key,
+                              privateBrowsing ? DataStorage_Private : DataStorage_Persistent));
+  if (val.IsEmpty()) {
+    LOG(("AltSvcCache::LookupMapping %p MISS\n", this));
+    return nullptr;
+  }
+  RefPtr<AltSvcMapping> rv = new AltSvcMapping(mStorage, mStorageEpoch, val);
+  if (!rv->Validated() && (rv->StorageEpoch() != mStorageEpoch)) {
+    // this was an in progress validation abandoned in a different session
+    // rare edge case will not detect session change - that's ok as only impact
+    // will be loss of alt-svc to this origin for this session.
+    LOG(("AltSvcCache::LookupMapping %p invalid hit - MISS\n", this));
+    mStorage->Remove(key,
+                     rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+    return nullptr;
+  }
+
+  if (rv->TTL() <= 0) {
+    LOG(("AltSvcCache::LookupMapping %p expired hit - MISS\n", this));
+    mStorage->Remove(key,
+                     rv->Private() ? DataStorage_Private : DataStorage_Persistent);
+    return nullptr;
+  }
+
+  MOZ_ASSERT(rv->Private() == privateBrowsing);
+  LOG(("AltSvcCache::LookupMapping %p HIT %p\n", this, rv.get()));
+  return rv.forget();
+}
+
 void
 AltSvcCache::UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
                                      nsIInterfaceRequestor *aCallbacks,
                                      uint32_t caps,
                                      const NeckoOriginAttributes &originAttributes)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  AltSvcMapping *existing = mHash.GetWeak(map->mHashKey);
-  LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s",
-       this, map, existing, map->AlternateHost().get()));
+  if (!mStorage) {
+    return;
+  }
+  RefPtr<AltSvcMapping> existing = LookupMapping(map->HashKey(), map->Private());
+  LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p existing %p %s validated=%d",
+       this, map, existing.get(), map->AlternateHost().get(),
+       existing ? existing->Validated() : 0));
 
-  if (existing && (existing->TTL() <= 0)) {
-    LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p is expired",
-         this, map));
-    existing = nullptr;
-    mHash.Remove(map->mHashKey);
-  }
-
-  if (existing && existing->mValidated) {
-    if (existing->RouteEquals(map)) {
-      // update expires
-      LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of %p\n",
-           this, map, existing));
-      existing->SetExpiresAt(map->GetExpiresAt());
+  if (existing && existing->Validated()) {
+    if (existing->RouteEquals(map)){
+      // update expires in storage
+      // if this is http:// then a ttl can only be extended via .wk, so ignore this
+      // header path unless it is making things shorter
+      if (existing->HTTPS()) {
+        LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p updates ttl of %p\n",
+             this, map, existing.get()));
+        existing->SetExpiresAt(map->GetExpiresAt());
+      } else {
+        if (map->GetExpiresAt() < existing->GetExpiresAt()) {
+          LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p reduces ttl of %p\n",
+               this, map, existing.get()));
+          existing->SetExpiresAt(map->GetExpiresAt());
+        } else {
+          LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p tries to extend %p but"
+               " cannot as without .wk\n",
+               this, map, existing.get()));
+        }
+      }
       return;
     }
 
+    // new alternate. remove old entry and start new validation
     LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p overwrites %p\n",
-         this, map, existing));
+         this, map, existing.get()));
     existing = nullptr;
-    mHash.Remove(map->mHashKey);
+    mStorage->Remove(map->HashKey(),
+                     map->Private() ? DataStorage_Private : DataStorage_Persistent);
   }
 
-  if (existing) {
+  if (existing && !existing->Validated()) {
     LOG(("AltSvcCache::UpdateAltServiceMapping %p map %p ignored because %p "
-         "still in progress\n", this, map, existing));
+         "still in progress\n", this, map, existing.get()));
     return;
   }
 
-  mHash.Put(map->mHashKey, map);
+  // start new validation
+  MOZ_ASSERT(!map->Validated());
+  map->Sync();
 
   RefPtr<nsHttpConnectionInfo> ci;
   map->GetConnectionInfo(getter_AddRefs(ci), pi, originAttributes);
   caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
-
-  nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
+  caps |= NS_HTTP_ERROR_SOFTLY;
 
-  RefPtr<AltSvcTransaction> nullTransaction =
-    new AltSvcTransaction(map, ci, aCallbacks, caps);
-  nullTransaction->StartTransaction();
-  gHttpHandler->ConnMgr()->SpeculativeConnect(ci, callbacks, caps, nullTransaction);
+  if (map->HTTPS()) {
+    LOG(("AltSvcCache::UpdateAltServiceMapping %p validation via "
+         "speculative connect started\n", this));
+    // for https resources we only establish a connection
+    nsCOMPtr<nsIInterfaceRequestor> callbacks = new AltSvcOverride(aCallbacks);
+    RefPtr<AltSvcTransaction> nullTransaction =
+      new AltSvcTransaction(map, ci, aCallbacks, caps);
+    gHttpHandler->ConnMgr()->SpeculativeConnect(ci, callbacks, caps, nullTransaction);
+  } else {
+    // for http:// resources we fetch .well-known too
+    nsAutoCString origin (NS_LITERAL_CSTRING("http://") + map->OriginHost());
+    if (map->OriginPort() != NS_HTTP_DEFAULT_PORT) {
+      origin.Append(':');
+      origin.AppendInt(map->OriginPort());
+    }
+
+    nsCOMPtr<nsIURI> wellKnown;
+    nsAutoCString uri(origin);
+    uri.Append(NS_LITERAL_CSTRING("/.well-known/http-opportunistic"));
+    NS_NewURI(getter_AddRefs(wellKnown), uri);
+
+    WellKnownChecker *checker = new WellKnownChecker(wellKnown, origin, caps, ci, map);
+    if (NS_FAILED(checker->Start())) {
+      LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker failed to start\n", this));
+      map->SetExpired();
+      delete checker;
+      checker = nullptr;
+    } else {
+      // object deletes itself when done if started
+      LOG(("AltSvcCache::UpdateAltServiceMapping %p .wk checker started %p\n", this, checker));
+    }
+  }
 }
 
-AltSvcMapping *
+already_AddRefed<AltSvcMapping>
 AltSvcCache::GetAltServiceMapping(const nsACString &scheme, const nsACString &host,
                                   int32_t port, bool privateBrowsing)
 {
   bool isHTTPS;
   MOZ_ASSERT(NS_IsMainThread());
+  if (!mStorage) {
+    // DataStorage gives synchronous access to a memory based hash table
+    // that is backed by disk where those writes are done asynchronously
+    // on another thread
+    mStorage = DataStorage::Get(NS_LITERAL_STRING("AlternateServices.txt"));
+    if (mStorage) {
+      bool storageWillPersist = false;
+      if (NS_FAILED(mStorage->Init(storageWillPersist))) {
+        mStorage = nullptr;
+      }
+    }
+    if (!mStorage) {
+      LOG(("AltSvcCache::GetAltServiceMapping WARN NO STORAGE\n"));
+    }
+    mStorageEpoch = NowInSeconds();
+  }
+
   if (NS_FAILED(SchemeIsHTTPS(scheme, isHTTPS))) {
     return nullptr;
   }
   if (!gHttpHandler->AllowAltSvc()) {
     return nullptr;
   }
   if (!gHttpHandler->AllowAltSvcOE() && !isHTTPS) {
     return nullptr;
   }
 
   nsAutoCString key;
   AltSvcMapping::MakeHashKey(key, scheme, host, port, privateBrowsing);
-  AltSvcMapping *existing = mHash.GetWeak(key);
+  RefPtr<AltSvcMapping> existing = LookupMapping(key, privateBrowsing);
   LOG(("AltSvcCache::GetAltServiceMapping %p key=%s "
-       "existing=%p validated=%d running=%d ttl=%d",
-       this, key.get(), existing, existing ? existing->mValidated : 0,
-       existing ? existing->mRunning : 0,
+       "existing=%p validated=%d ttl=%d",
+       this, key.get(), existing.get(), existing ? existing->Validated() : 0,
        existing ? existing->TTL() : 0));
-  if (existing && (existing->TTL() <= 0)) {
-    LOG(("AltSvcCache::GetAltServiceMapping %p map %p is expired", this, existing));
-    mHash.Remove(existing->mHashKey);
+  if (existing && !existing->Validated()) {
     existing = nullptr;
   }
-  if (existing && existing->mValidated)
-    return existing;
-  return nullptr;
+  return existing.forget();
 }
 
 class ProxyClearHostMapping : public Runnable {
 public:
   explicit ProxyClearHostMapping(const nsACString &host, int32_t port)
     : mHost(host)
     , mPort(port)
     {}
@@ -501,39 +979,37 @@ AltSvcCache::ClearHostMapping(const nsAC
 {
   if (!NS_IsMainThread()) {
     nsCOMPtr<nsIRunnable> event = new ProxyClearHostMapping(host, port);
     if (event) {
       NS_DispatchToMainThread(event);
     }
     return;
   }
-
   nsAutoCString key;
-
   AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, true);
-  AltSvcMapping *existing = mHash.GetWeak(key);
+  RefPtr<AltSvcMapping> existing = LookupMapping(key, true);
   if (existing) {
     existing->SetExpired();
   }
 
   AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, true);
-  existing = mHash.GetWeak(key);
+  existing = LookupMapping(key, true);
   if (existing) {
     existing->SetExpired();
   }
 
   AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("http"), host, port, false);
-  existing = mHash.GetWeak(key);
+  existing = LookupMapping(key, false);
   if (existing) {
     existing->SetExpired();
   }
 
   AltSvcMapping::MakeHashKey(key, NS_LITERAL_CSTRING("https"), host, port, false);
-  existing = mHash.GetWeak(key);
+  existing = LookupMapping(key, false);
   if (existing) {
     existing->SetExpired();
   }
 }
 
 void
 AltSvcCache::ClearHostMapping(nsHttpConnectionInfo *ci)
 {
@@ -541,17 +1017,19 @@ AltSvcCache::ClearHostMapping(nsHttpConn
     ClearHostMapping(ci->GetOrigin(), ci->OriginPort());
   }
 }
 
 void
 AltSvcCache::ClearAltServiceMappings()
 {
     MOZ_ASSERT(NS_IsMainThread());
-    mHash.Clear();
+    if (mStorage) {
+      mStorage->Clear();
+    }
 }
 
 NS_IMETHODIMP
 AltSvcOverride::GetInterface(const nsIID &iid, void **result)
 {
   if (NS_SUCCEEDED(QueryInterface(iid, result)) && *result) {
     return NS_OK;
   }
--- a/netwerk/protocol/http/AlternateServices.h
+++ b/netwerk/protocol/http/AlternateServices.h
@@ -17,125 +17,175 @@ https://tools.ietf.org/html/draft-ietf-h
  * upon establishment of channel, cancel and retry trans that have not yet written anything
  * persistent storage (including private browsing filter)
  * memory reporter for cache, but this is rather tiny
 */
 
 #ifndef mozilla_net_AlternateServices_h
 #define mozilla_net_AlternateServices_h
 
+#include "mozilla/DataStorage.h"
 #include "nsRefPtrHashtable.h"
 #include "nsString.h"
 #include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
 #include "nsISpeculativeConnect.h"
 #include "mozilla/BasePrincipal.h"
 
+class nsILoadInfo;
+
 namespace mozilla { namespace net {
 
 class nsProxyInfo;
 class nsHttpConnectionInfo;
+class nsHttpTransaction;
+class nsHttpChannel;
+class WellKnownChecker;
 
 class AltSvcMapping
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AltSvcMapping)
-  friend class AltSvcCache;
 
 private: // ctor from ProcessHeader
-  AltSvcMapping(const nsACString &originScheme,
+  AltSvcMapping(DataStorage *storage,
+                int32_t storageEpoch,
+                const nsACString &originScheme,
                 const nsACString &originHost,
                 int32_t originPort,
                 const nsACString &username,
                 bool privateBrowsing,
                 uint32_t expiresAt,
                 const nsACString &alternateHost,
                 int32_t alternatePort,
                 const nsACString &npnToken);
+public:
+  AltSvcMapping(DataStorage *storage, int32_t storageEpoch, const nsCString &serialized);
 
-public:
   static void ProcessHeader(const nsCString &buf, const nsCString &originScheme,
                             const nsCString &originHost, int32_t originPort,
                             const nsACString &username, bool privateBrowsing,
                             nsIInterfaceRequestor *callbacks, nsProxyInfo *proxyInfo,
                             uint32_t caps, const NeckoOriginAttributes &originAttributes);
 
   const nsCString &AlternateHost() const { return mAlternateHost; }
   const nsCString &OriginHost() const { return mOriginHost; }
+  uint32_t OriginPort() const { return mOriginPort; }
   const nsCString &HashKey() const { return mHashKey; }
   uint32_t AlternatePort() const { return mAlternatePort; }
   bool Validated() { return mValidated; }
-  void SetValidated(bool val) { mValidated = val; }
-  bool IsRunning() { return mRunning; }
-  void SetRunning(bool val) { mRunning = val; }
   int32_t GetExpiresAt() { return mExpiresAt; }
-  void SetExpiresAt(int32_t val) { mExpiresAt = val; }
-  void SetExpired();
   bool RouteEquals(AltSvcMapping *map);
   bool HTTPS() { return mHttps; }
 
   void GetConnectionInfo(nsHttpConnectionInfo **outCI, nsProxyInfo *pi,
                          const NeckoOriginAttributes &originAttributes);
+
   int32_t TTL();
+  int32_t StorageEpoch() { return mStorageEpoch; }
+  bool    Private() { return mPrivate; }
 
-private:
-  virtual ~AltSvcMapping() {};
+  void SetValidated(bool val);
+  void SetMixedScheme(bool val);
+  void SetExpiresAt(int32_t val);
+  void SetExpired();
+  void Sync();
+
   static void MakeHashKey(nsCString &outKey,
                           const nsACString &originScheme,
                           const nsACString &originHost,
                           int32_t originPort,
                           bool privateBrowsing);
 
+private:
+  virtual ~AltSvcMapping() {};
+  void     SyncString(nsCString val);
+  RefPtr<DataStorage> mStorage;
+  int32_t             mStorageEpoch;
+  void Serialize (nsCString &out);
+
   nsCString mHashKey;
 
+  // If you change any of these members, update Serialize()
   nsCString mAlternateHost;
   int32_t mAlternatePort;
 
   nsCString mOriginHost;
   int32_t mOriginPort;
 
   nsCString mUsername;
   bool mPrivate;
 
-  uint32_t mExpiresAt;
+  uint32_t mExpiresAt; // alt-svc mappping
 
   bool mValidated;
-  bool mRunning;
   bool mHttps; // origin is https://
+  bool mMixedScheme; // .wk allows http and https on same con
 
-  nsCString mNPNToken;
+  nsCString        mNPNToken;
 };
 
 class AltSvcOverride : public nsIInterfaceRequestor
                      , public nsISpeculativeConnectionOverrider
 {
 public:
-    NS_DECL_THREADSAFE_ISUPPORTS
-    NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
-    NS_DECL_NSIINTERFACEREQUESTOR
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+  NS_DECL_NSIINTERFACEREQUESTOR
 
-    explicit AltSvcOverride(nsIInterfaceRequestor *aRequestor)
-      : mCallbacks(aRequestor) {}
+  explicit AltSvcOverride(nsIInterfaceRequestor *aRequestor)
+    : mCallbacks(aRequestor) {}
 
 private:
-    virtual ~AltSvcOverride() {}
-    nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+  virtual ~AltSvcOverride() {}
+  nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+};
+
+class TransactionObserver : public nsIStreamListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSIREQUESTOBSERVER
+
+  TransactionObserver(nsHttpChannel *channel, WellKnownChecker *checker);
+  void Complete(nsHttpTransaction *, nsresult);
+private:
+  friend class WellKnownChecker;
+  virtual ~TransactionObserver() {}
+
+  nsCOMPtr<nsISupports> mChannelRef;
+  nsHttpChannel        *mChannel;
+  WellKnownChecker     *mChecker;
+  nsCString             mWKResponse;
+
+  bool mRanOnce;
+  bool mAuthOK; // confirmed no TLS failure
+  bool mVersionOK; // connection h2
+  bool mStatusOK; // HTTP Status 200
 };
 
 class AltSvcCache
 {
 public:
+  AltSvcCache() : mStorageEpoch(0) {}
+  virtual ~AltSvcCache () {};
   void UpdateAltServiceMapping(AltSvcMapping *map, nsProxyInfo *pi,
                                nsIInterfaceRequestor *, uint32_t caps,
                                const NeckoOriginAttributes &originAttributes); // main thread
-  AltSvcMapping *GetAltServiceMapping(const nsACString &scheme,
-                                      const nsACString &host,
-                                      int32_t port, bool pb);
+  already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
+                                                       const nsACString &host,
+                                                       int32_t port, bool pb);
   void ClearAltServiceMappings();
   void ClearHostMapping(const nsACString &host, int32_t port);
   void ClearHostMapping(nsHttpConnectionInfo *ci);
+  DataStorage *GetStoragePtr() { return mStorage.get(); }
+  int32_t      StorageEpoch()  { return mStorageEpoch; }
 
 private:
-  nsRefPtrHashtable<nsCStringHashKey, AltSvcMapping> mHash;
+  already_AddRefed<AltSvcMapping> LookupMapping(const nsCString &key, bool privateBrowsing);
+  RefPtr<DataStorage>             mStorage;
+  int32_t                         mStorageEpoch;
 };
 
 } // namespace net
 } // namespace mozilla
 
 #endif // include guard
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -2187,21 +2187,16 @@ Http2Session::RecvAltSvc(Http2Session *s
       okToReroute = false;
     }
 
     // a little off main thread origin parser. This is a non critical function because
     // any alternate route created has to be verified anyhow
     nsAutoCString specifiedOriginHost;
     if (origin.EqualsIgnoreCase("https://", 8)) {
       specifiedOriginHost.Assign(origin.get() + 8, origin.Length() - 8);
-      if (ci->GetInsecureScheme()) {
-        // technically this is ok because it will still be confirmed before being used
-        // but let's not support it.
-        okToReroute = false;
-      }
     } else if (origin.EqualsIgnoreCase("http://", 7)) {
       specifiedOriginHost.Assign(origin.get() + 7, origin.Length() - 7);
     }
 
     int32_t colonOffset = specifiedOriginHost.FindCharInSet(":", 0);
     if (colonOffset != kNotFound) {
       specifiedOriginHost.Truncate(colonOffset);
     }
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -399,17 +399,17 @@ protected:
   nsCOMPtr<nsIStreamListener>       mCompressListener;
 
   nsHttpRequestHead                 mRequestHead;
   // Upload throttling.
   nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
   nsCOMPtr<nsIInputStream>          mUploadStream;
   nsCOMPtr<nsIRunnable>             mUploadCloneableCallback;
   nsAutoPtr<nsHttpResponseHead>     mResponseHead;
-  RefPtr<nsHttpConnectionInfo>    mConnectionInfo;
+  RefPtr<nsHttpConnectionInfo>      mConnectionInfo;
   nsCOMPtr<nsIProxyInfo>            mProxyInfo;
   nsCOMPtr<nsISupports>             mSecurityInfo;
 
   nsCString                         mSpec; // ASCII encoded URL spec
   nsCString                         mContentTypeHint;
   nsCString                         mContentCharsetHint;
   nsCString                         mUserSetCookieHeader;
 
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.js
@@ -0,0 +1,43 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID = "@mozilla.org/network/well-known-opportunistic-utils;1";
+const WELLKNOWNOPPORTUNISTICUTILS_CID = Components.ID("{b4f96c89-5238-450c-8bda-e12c26f1d150}");
+
+function WellKnownOpportunisticUtils() {
+  this.valid = false;
+  this.mixed = false;
+  this.lifetime = 0;
+}
+
+WellKnownOpportunisticUtils.prototype = {
+  classID: WELLKNOWNOPPORTUNISTICUTILS_CID,
+  contractID: WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID,
+  classDescription: "Well-Known Opportunistic Utils",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWellKnownOpportunisticUtils]),
+
+    verify: function(aJSON, aOrigin, aAlternatePort) {
+	try {
+	  let obj = JSON.parse(aJSON.toLowerCase());
+	  let ports = obj[aOrigin.toLowerCase()]['tls-ports'];
+	  if (ports.indexOf(aAlternatePort) == -1) {
+	    throw "invalid port";
+	  }
+	  this.lifetime = obj[aOrigin.toLowerCase()]['lifetime'];
+          this.mixed = obj[aOrigin.toLowerCase()]['mixed-scheme'];
+	} catch (e) {
+	  return;
+	}
+      this.valid = true;
+    },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WellKnownOpportunisticUtils]);
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/WellKnownOpportunisticUtils.manifest
@@ -0,0 +1,3 @@
+# WellKnownOpportunisticUtils.js
+component {b4f96c89-5238-450c-8bda-e12c26f1d150} WellKnownOpportunisticUtils.js
+contract @mozilla.org/network/well-known-opportunistic-utils;1 {b4f96c89-5238-450c-8bda-e12c26f1d150}
--- a/netwerk/protocol/http/moz.build
+++ b/netwerk/protocol/http/moz.build
@@ -11,16 +11,17 @@ XPIDL_SOURCES += [
     'nsIHttpAuthManager.idl',
     'nsIHttpChannel.idl',
     'nsIHttpChannelAuthProvider.idl',
     'nsIHttpChannelChild.idl',
     'nsIHttpChannelInternal.idl',
     'nsIHttpEventSink.idl',
     'nsIHttpHeaderVisitor.idl',
     'nsIHttpProtocolHandler.idl',
+    'nsIWellKnownOpportunisticUtils.idl',
 ]
 
 XPIDL_MODULE = 'necko_http'
 
 EXPORTS += [
     'nsCORSListenerProxy.h',
     'nsHttp.h',
     'nsHttpAtomList.h',
@@ -109,9 +110,11 @@ FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
     '/netwerk/base',
 ]
 
 EXTRA_COMPONENTS += [
     'PackagedAppUtils.js',
     'PackagedAppUtils.manifest',
+    'WellKnownOpportunisticUtils.js',
+    'WellKnownOpportunisticUtils.manifest',
 ]
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -79,16 +79,20 @@ typedef uint8_t nsHttpVersion;
 
 // a transaction with this flag loads without respect to whether the load
 // group is currently blocking on some resources
 #define NS_HTTP_LOAD_UNBLOCKED       (1<<8)
 
 // This flag indicates the transaction should accept associated pushes
 #define NS_HTTP_ONPUSH_LISTENER      (1<<9)
 
+// Transactions with this flag should react to errors without side effects
+// First user is to prevent clearing of alt-svc cache on failed probe
+#define NS_HTTP_ERROR_SOFTLY         (1<<10)
+
 //-----------------------------------------------------------------------------
 // some default values
 //-----------------------------------------------------------------------------
 
 #define NS_HTTP_DEFAULT_PORT  80
 #define NS_HTTPS_DEFAULT_PORT 443
 
 #define NS_HTTP_HEADER_SEPS ", \t"
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -869,16 +869,18 @@ nsHttpChannel::SetupTransaction()
     // create wrapper for this channel's notification callbacks
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
                                            getter_AddRefs(callbacks));
 
     // create the transaction object
     mTransaction = new nsHttpTransaction();
     LOG(("nsHttpChannel %p created nsHttpTransaction %p\n", this, mTransaction.get()));
+    mTransaction->SetTransactionObserver(mTransactionObserver);
+    mTransactionObserver = nullptr;
 
     // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
     if (mLoadFlags & LOAD_ANONYMOUS)
         mCaps |= NS_HTTP_LOAD_ANONYMOUS;
 
     if (mTimingEnabled)
         mCaps |= NS_HTTP_TIMING_ENABLED;
 
@@ -5729,17 +5731,17 @@ nsHttpChannel::BeginConnect()
     mRequestHead.SetOrigin(scheme, host, port);
 
     SetDoNotTrack();
 
     NeckoOriginAttributes originAttributes;
     NS_GetOriginAttributes(this, originAttributes);
 
     RefPtr<AltSvcMapping> mapping;
-    if (mAllowAltSvc && // per channel
+    if (!mConnectionInfo && mAllowAltSvc && // per channel
         (scheme.Equals(NS_LITERAL_CSTRING("http")) ||
          scheme.Equals(NS_LITERAL_CSTRING("https"))) &&
         (!proxyInfo || proxyInfo->IsDirect()) &&
         (mapping = gHttpHandler->GetAltServiceMapping(scheme,
                                                       host, port,
                                                       mPrivateBrowsing))) {
         LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n",
              this, scheme.get(), mapping->AlternateHost().get(),
@@ -5773,16 +5775,19 @@ nsHttpChannel::BeginConnect()
             message.AppendInt(mapping->AlternatePort());
             consoleService->LogStringMessage(message.get());
         }
 
         LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
         mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo, originAttributes);
         Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
         Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
+    } else if (mConnectionInfo) {
+        LOG(("nsHttpChannel %p Using channel supplied connection info", this));
+        Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
     } else {
         LOG(("nsHttpChannel %p Using default connection info", this));
 
         mConnectionInfo = new nsHttpConnectionInfo(host, port, EmptyCString(), mUsername, proxyInfo,
                                                    originAttributes, isHttps);
         Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
     }
 
@@ -7785,16 +7790,22 @@ bool nsHttpChannel::IsRedirectStatus(uin
 
 void
 nsHttpChannel::SetCouldBeSynthesized()
 {
   MOZ_ASSERT(!BypassServiceWorker());
   mResponseCouldBeSynthesized = true;
 }
 
+void
+nsHttpChannel::SetConnectionInfo(nsHttpConnectionInfo *aCI)
+{
+    mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
 NS_IMETHODIMP
 nsHttpChannel::OnPreflightSucceeded()
 {
     MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
     mIsCorsPreflightDone = 1;
     mPreflightChannel = nullptr;
 
     return ContinueConnect();
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -22,16 +22,17 @@
 #include "nsIThreadRetargetableStreamListener.h"
 #include "nsWeakReference.h"
 #include "TimingStruct.h"
 #include "ADivertableParentChannel.h"
 #include "AutoClose.h"
 #include "nsIStreamListener.h"
 #include "nsISupportsPrimitives.h"
 #include "nsICorsPreflightCallback.h"
+#include "AlternateServices.h"
 
 class nsDNSPrefetch;
 class nsICancelable;
 class nsIHttpChannelAuthProvider;
 class nsInputStreamPump;
 class nsISSLStatus;
 
 namespace mozilla { namespace net {
@@ -253,16 +254,23 @@ public: /* internal necko use only */
       uint32_t mKeep : 2;
     };
 
     void MarkIntercepted();
     NS_IMETHOD GetResponseSynthesized(bool* aSynthesized) override;
     bool AwaitingCacheCallbacks();
     void SetCouldBeSynthesized();
 
+private: // used for alternate service validation
+    RefPtr<TransactionObserver> mTransactionObserver;
+public:
+    void SetConnectionInfo(nsHttpConnectionInfo *); // clones the argument
+    void SetTransactionObserver(TransactionObserver *arg) { mTransactionObserver = arg; }
+    TransactionObserver *GetTransactionObserver() { return mTransactionObserver; }
+
 protected:
     virtual ~nsHttpChannel();
 
 private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
     nsresult BeginConnect();
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -537,17 +537,17 @@ failed_activation:
 
     return rv;
 }
 
 void
 nsHttpConnection::SetupSSL()
 {
     LOG(("nsHttpConnection::SetupSSL %p caps=0x%X %s\n",
-         this, mTransactionCaps,mConnInfo->HashKey().get()));
+         this, mTransactionCaps, mConnInfo->HashKey().get()));
 
     if (mSetupSSLCalled) // do only once
         return;
     mSetupSSLCalled = true;
 
     if (mNPNComplete)
         return;
 
@@ -628,24 +628,16 @@ nsHttpConnection::AddTransaction(nsAHttp
     bool needTunnel = transCI->UsingHttpsProxy();
     needTunnel = needTunnel && !mTLSFilter;
     needTunnel = needTunnel && transCI->UsingConnect();
     needTunnel = needTunnel && httpTransaction->QueryHttpTransaction();
 
     LOG(("nsHttpConnection::AddTransaction for SPDY%s",
          needTunnel ? " over tunnel" : ""));
 
-    // do a runtime check here just for defense in depth
-    if (transCI->GetInsecureScheme() &&
-        httpTransaction->RequestHead() && httpTransaction->RequestHead()->IsHTTPS()) {
-        LOG(("This Cannot happen - https on insecure scheme tls stream\n"));
-        MOZ_ASSERT(false, "https:// on tls insecure scheme");
-        return NS_ERROR_FAILURE;
-    }
-
     if (!mSpdySession->AddStream(httpTransaction, priority,
                                  needTunnel, mCallbacks)) {
         MOZ_ASSERT(false); // this cannot happen!
         httpTransaction->Close(NS_ERROR_ABORT);
         return NS_ERROR_FAILURE;
     }
 
     ResumeSend();
@@ -674,17 +666,17 @@ nsHttpConnection::Close(nsresult reason,
             EndIdleMonitoring();
 
         mTLSFilter = nullptr;
 
         // The connection and security errors clear out alt-svc mappings
         // in case any previously validated ones are now invalid
         if (((reason == NS_ERROR_NET_RESET) ||
              (NS_ERROR_GET_MODULE(reason) == NS_ERROR_MODULE_SECURITY))
-            && mConnInfo) {
+            && mConnInfo && !(mTransactionCaps & NS_HTTP_ERROR_SOFTLY)) {
             gHttpHandler->ConnMgr()->ClearHostMapping(mConnInfo);
         }
 
         if (mSocketTransport) {
             mSocketTransport->SetEventSink(nullptr, nullptr);
 
             // If there are bytes sitting in the input queue then read them
             // into a junk buffer to avoid generating a tcp rst by closing a
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -61,17 +61,16 @@ nsHttpConnectionInfo::nsHttpConnectionIn
 nsHttpConnectionInfo::nsHttpConnectionInfo(const nsACString &originHost,
                                            int32_t originPort,
                                            const nsACString &npnToken,
                                            const nsACString &username,
                                            nsProxyInfo *proxyInfo,
                                            const NeckoOriginAttributes &originAttributes,
                                            const nsACString &routedHost,
                                            int32_t routedPort)
-
 {
     mEndToEndSSL = true; // so DefaultPort() works
     mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort;
 
     if (!originHost.Equals(routedHost) || (originPort != routedPort)) {
         mRoutedHost = routedHost;
     }
     Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, true);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -427,16 +427,17 @@ nsHttpConnectionMgr::SpeculativeConnect(
     RefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
 
     // Wrap up the callbacks and the target to ensure they're released on the target
     // thread properly.
     nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
     NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
 
     caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
+    caps |= NS_HTTP_ERROR_SOFTLY;
     args->mTrans =
         nullTransaction ? nullTransaction : new NullHttpTransaction(ci, wrappedCallbacks, caps);
 
     if (overrider) {
         args->mOverridesOK = true;
         args->mParallelSpeculativeConnectLimit =
             overrider->GetParallelSpeculativeConnectLimit();
         args->mIgnoreIdle = overrider->GetIgnoreIdle();
@@ -2996,29 +2997,19 @@ nsHalfOpenSocket::SetupStreams(nsISocket
                                nsIAsyncOutputStream **outstream,
                                bool isBackup)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     nsresult rv;
     const char *socketTypes[1];
     uint32_t typeCount = 0;
-    bool bypassTLSAuth = false;
     const nsHttpConnectionInfo *ci = mEnt->mConnInfo;
     if (ci->FirstHopSSL()) {
         socketTypes[typeCount++] = "ssl";
-
-        if (ci->GetInsecureScheme()) { // http:// over tls
-            const nsCString &routedHost = ci->GetRoutedHost();
-            if (routedHost.Equals(ci->GetOrigin())) {
-                LOG(("nsHttpConnection::SetupSSL %p TLS-Relaxed "
-                     "with Same Host Auth Bypass", this));
-                bypassTLSAuth = true;
-            }
-        }
     } else {
         socketTypes[typeCount] = gHttpHandler->DefaultSocketType();
         if (socketTypes[typeCount]) {
             typeCount++;
         }
     }
 
     nsCOMPtr<nsISocketTransport> socketTransport;
@@ -3061,20 +3052,16 @@ nsHalfOpenSocket::SetupStreams(nsISocket
         tmpFlags = nsISocketTransport::BYPASS_CACHE;
 
     if (mCaps & NS_HTTP_LOAD_ANONYMOUS)
         tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
 
     if (ci->GetPrivate())
         tmpFlags |= nsISocketTransport::NO_PERMANENT_STORAGE;
 
-    if (bypassTLSAuth) {
-        tmpFlags |= nsISocketTransport::MITM_OK;
-    }
-
     // For backup connections, we disable IPv6. That's because some users have
     // broken IPv6 connectivity (leading to very long timeouts), and disabling
     // IPv6 on the backup connection gives them a much better user experience
     // with dual-stack hosts, though they still pay the 250ms delay for each new
     // connection. This strategy is also known as "happy eyeballs".
     if (mEnt->mPreferIPv6) {
         tmpFlags |= nsISocketTransport::DISABLE_IPV4;
     }
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -243,19 +243,19 @@ public:
                                  nsIInterfaceRequestor *callbacks,
                                  uint32_t caps,
                                  const NeckoOriginAttributes &originAttributes)
     {
         mConnMgr->UpdateAltServiceMapping(map, proxyInfo, callbacks, caps,
                                           originAttributes);
     }
 
-    AltSvcMapping *GetAltServiceMapping(const nsACString &scheme,
-                                        const nsACString &host,
-                                        int32_t port, bool pb)
+    already_AddRefed<AltSvcMapping> GetAltServiceMapping(const nsACString &scheme,
+                                                         const nsACString &host,
+                                                         int32_t port, bool pb)
     {
         return mConnMgr->GetAltServiceMapping(scheme, host, port, pb);
     }
 
     //
     // The HTTP handler caches pointers to specific XPCOM services, and
     // provides the following helper routines for accessing those services:
     //
@@ -628,16 +628,17 @@ private:
 private:
     nsresult SpeculativeConnectInternal(nsIURI *aURI,
                                         nsIInterfaceRequestor *aCallbacks,
                                         bool anonymous);
 
     // UUID generator for channelIds
     nsCOMPtr<nsIUUIDGenerator> mUUIDGen;
 
+public:
     nsresult NewChannelId(nsID *channelId);
 };
 
 extern nsHttpHandler *gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
 //                  HTTPS handler (even though they share the same impl).
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -151,17 +151,19 @@ nsHttpTransaction::nsHttpTransaction()
 #endif
     mSelfAddr.raw.family = PR_AF_UNSPEC;
     mPeerAddr.raw.family = PR_AF_UNSPEC;
 }
 
 nsHttpTransaction::~nsHttpTransaction()
 {
     LOG(("Destroying nsHttpTransaction @%p\n", this));
-
+    if (mTransactionObserver) {
+        mTransactionObserver->Complete(this, NS_OK);
+    }
     if (mPushedStream) {
         mPushedStream->OnPushFailed();
         mPushedStream = nullptr;
     }
 
     if (mTokenBucketCancel) {
         mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
         mTokenBucketCancel = nullptr;
@@ -922,16 +924,21 @@ nsHttpTransaction::Close(nsresult reason
         return;
     }
 
     if (mClosed) {
         LOG(("  already closed\n"));
         return;
     }
 
+    if (mTransactionObserver) {
+        mTransactionObserver->Complete(this, reason);
+        mTransactionObserver = nullptr;
+    }
+
     if (mTokenBucketCancel) {
         mTokenBucketCancel->Cancel(reason);
         mTokenBucketCancel = nullptr;
     }
 
     if (mActivityDistributor) {
         // report the reponse is complete if not already reported
         if (!mResponseIsComplete)
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -12,16 +12,17 @@
 #include "EventTokenBucket.h"
 #include "nsCOMPtr.h"
 #include "nsThreadUtils.h"
 #include "nsIInterfaceRequestor.h"
 #include "TimingStruct.h"
 #include "Http2Push.h"
 #include "mozilla/net/DNS.h"
 #include "ARefBase.h"
+#include "AlternateServices.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsINetworkInterface.h"
 #include "nsProxyRelease.h"
 #endif
 
 //-----------------------------------------------------------------------------
 
@@ -452,16 +453,20 @@ public:
     void SetTunnelProvider(ASpdySession *provider) { mTunnelProvider = provider; }
     ASpdySession *TunnelProvider() { return mTunnelProvider; }
     nsIInterfaceRequestor *SecurityCallbacks() { return mCallbacks; }
 
 private:
     RefPtr<ASpdySession> mTunnelProvider;
 
 public:
+    void SetTransactionObserver(TransactionObserver *arg) { mTransactionObserver = arg; }
+private:
+    RefPtr<TransactionObserver> mTransactionObserver;
+public:
     void GetNetworkAddresses(NetAddr &self, NetAddr &peer);
 
 private:
     NetAddr                         mSelfAddr;
     NetAddr                         mPeerAddr;
 
     bool                            m0RTTInProgress;
 };
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/http/nsIWellKnownOpportunisticUtils.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+  For parsing JSON from http://httpwg.org/http-extensions/opsec.html
+*/
+
+#include "nsISupports.idl"
+
+%{C++
+#define NS_WELLKNOWNOPPORTUNISTICUTILS_CONTRACTID "@mozilla.org/network/well-known-opportunistic-utils;1"
+%}
+
+[scriptable, uuid(b4f96c89-5238-450c-8bda-e12c26f1d150)]
+interface nsIWellKnownOpportunisticUtils : nsISupports
+{
+    void verify(in ACString aJSON,
+               in ACString  aOrigin,
+               in long      aAlternatePort);
+
+   readonly attribute bool valid;
+   readonly attribute bool mixed; /* mixed-scheme */
+   readonly attribute long lifetime;
+};
--- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp
@@ -80,22 +80,29 @@ WebSocketChannelParent::RecvAsyncOpen(co
   uint32_t appId = NECKO_NO_APP_ID;
   NeckoOriginAttributes attrs;
 
   rv = LoadInfoArgsToLoadInfo(aLoadInfoArgs, getter_AddRefs(loadInfo));
   if (NS_FAILED(rv)) {
     goto fail;
   }
 
-  rv = loadInfo->GetOriginAttributes(&attrs);
-  if (NS_FAILED(rv)) {
-    goto fail;
+  if (loadInfo) {
+    rv = loadInfo->GetOriginAttributes(&attrs);
+    if (NS_FAILED(rv)) {
+      goto fail;
+    }
+
+    appId = attrs.mAppId;
+  } else {
+    // If the WebSocket is a server-side socket, then
+    // loadInfo will be null (since it's an incoming connection).
+    // AppID is irrelevant in these circumstances.
+    appId = NECKO_UNKNOWN_APP_ID;
   }
-
-  appId = attrs.mAppId;
   if (appId != NECKO_UNKNOWN_APP_ID &&
       appId != NECKO_NO_APP_ID) {
     gIOService->IsAppOffline(appId, &appOffline);
     if (appOffline) {
       goto fail;
     }
   }
 
--- a/netwerk/test/unit/test_altsvc.js
+++ b/netwerk/test/unit/test_altsvc.js
@@ -50,16 +50,17 @@ function run_test() {
   // for both h2FooRoute and h2BarRoute though it is only valid for
   // 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");
 
   h1Foo = new HttpServer();
   h1Foo.registerPathHandler("/altsvc-test", h1Server);
+  h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
   h1Foo.start(-1);
   h1Foo.identity.setPrimary("http", "foo.example.com", h1Foo.identity.primaryPort);
 
   h1Bar = new HttpServer();
   h1Bar.registerPathHandler("/altsvc-test", h1Server);
   h1Bar.start(-1);
   h1Bar.identity.setPrimary("http", "bar.example.com", h1Bar.identity.primaryPort);
 
@@ -92,16 +93,29 @@ function h1Server(metadata, response) {
     var hval = "h2=" + metadata.getHeader("x-altsvc");
     response.setHeader("Alt-Svc", hval, false);
   } catch (e) {}
 
   var body = "Q: What did 0 say to 8? A: Nice Belt!\n";
   response.bodyOutputStream.write(body, body.length);
 }
 
+function h1ServerWK(metadata, response) {
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "application/json", false);
+  response.setHeader("Connection", "close", false);
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Access-Control-Allow-Origin", "*", false);
+  response.setHeader("Access-Control-Allow-Method", "GET", false);
+  response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false);
+
+  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");
 }
@@ -156,16 +170,17 @@ Listener.prototype = {
   },
 
   onStopRequest: function testOnStopRequest(request, ctx, status) {
     var routed = "";
     try {
       routed = request.getRequestHeader("Alt-Used");
     } catch (e) {}
     dump("routed is " + routed + "\n");
+    do_check_eq(Components.isSuccessCode(status), expectPass);
 
     if (waitFor != 0) {
       do_check_eq(routed, "");
       do_test_pending();
       do_timeout(waitFor, doTest);
       waitFor = 0;
       xaltsvc = "NA";
     } else if (xaltsvc == "NA") {
@@ -293,33 +308,39 @@ function doTest7()
   xaltsvc = '';
   expectPass = false;
   nextTest = doTest8;
   do_test_pending();
   doTest();
 }
 
 // http://bar via h2 on bar
+// should not use TLS/h2 because h2BarRoute is not auth'd for bar
+// however the test ought to PASS (i.e. get a 200) because fallback
+// to plaintext happens.. thus the timeout
 function doTest8()
 {
   dump("doTest8()\n");
   origin = httpBarOrigin;
   xaltsvc = h2BarRoute;
   expectPass = true;
+  waitFor = 500;
   nextTest = doTest9;
   do_test_pending();
   doTest();
 }
 
-// http://bar served from h2=:port
+// http://bar served from h2=:port, which is like the bar route in 8
 function doTest9()
 {
   dump("doTest9()\n");
   origin = httpBarOrigin;
   xaltsvc = h2Route;
+  expectPass = true;
+  waitFor = 500;
   nextTest = doTest10;
   do_test_pending();
   doTest();
   xaltsvc = h2BarRoute;
 }
 
 // check again https://bar should fail because host bar has cert for foo
 function doTest10()
@@ -337,14 +358,14 @@ function doTest10()
 // cert for foo. Fail in this case means alt-svc is not used, but content
 // is served
 function doTest11()
 {
   dump("doTest11()\n");
   origin = httpBarOrigin;
   xaltsvc = h2FooRoute;
   expectPass = true;
-  waitFor = 1000;
+  waitFor = 500;
   nextTest = testsDone;
   do_test_pending();
   doTest();
 }
 
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -606,27 +606,27 @@ var altsvcClientListener = {
   onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
    read_stream(stream, cnt);
   },
 
   onStopRequest: function test_onStopR(request, ctx, status) {
     var isHttp2Connection = checkIsHttp2(request.QueryInterface(Components.interfaces.nsIHttpChannel));
     if (!isHttp2Connection) {
       dump("/altsvc1 not over h2 yet - retry\n");
-      var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort + "/altsvc1")
+      var chan = makeChan("http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1")
                 .QueryInterface(Components.interfaces.nsIHttpChannel);
       // we use this header to tell the server to issue a altsvc frame for the
       // speficied origin we will use in the next part of the test
       chan.setRequestHeader("x-redirect-origin",
-                 "http://localhost:" + httpserv2.identity.primaryPort, false);
+                 "http://foo.example.com:" + httpserv2.identity.primaryPort, false);
       chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
       chan.asyncOpen2(altsvcClientListener);
     } else {
       do_check_true(isHttp2Connection);
-      var chan = makeChan("http://localhost:" + httpserv2.identity.primaryPort + "/altsvc2")
+      var chan = makeChan("http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2")
                 .QueryInterface(Components.interfaces.nsIHttpChannel);
       chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
       chan.asyncOpen2(altsvcClientListener2);
     }
   }
 };
 
 var altsvcClientListener2 = {
@@ -637,17 +637,17 @@ var altsvcClientListener2 = {
   onDataAvailable: function test_ODA(request, cx, stream, offset, cnt) {
    read_stream(stream, cnt);
   },
 
   onStopRequest: function test_onStopR(request, ctx, status) {
     var isHttp2Connection = checkIsHttp2(request.QueryInterface(Components.interfaces.nsIHttpChannel));
     if (!isHttp2Connection) {
       dump("/altsvc2 not over h2 yet - retry\n");
-      var chan = makeChan("http://localhost:" + httpserv2.identity.primaryPort + "/altsvc2")
+      var chan = makeChan("http://foo.example.com:" + httpserv2.identity.primaryPort + "/altsvc2")
                 .QueryInterface(Components.interfaces.nsIHttpChannel);
       chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
       chan.asyncOpen2(altsvcClientListener2);
     } else {
       do_check_true(isHttp2Connection);
       run_next_test();
       do_test_finished();
     }
@@ -658,29 +658,52 @@ function altsvcHttp1Server(metadata, res
   response.setStatusLine(metadata.httpVersion, 200, "OK");
   response.setHeader("Content-Type", "text/plain", false);
   response.setHeader("Connection", "close", false);
   response.setHeader("Alt-Svc", 'h2=":' + serverPort + '"', false);
   var body = "this is where a cool kid would write something neat.\n";
   response.bodyOutputStream.write(body, body.length);
 }
 
+function h1ServerWK(metadata, response) {
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "application/json", false);
+  response.setHeader("Connection", "close", false);
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Access-Control-Allow-Origin", "*", false);
+  response.setHeader("Access-Control-Allow-Method", "GET", false);
+
+  var body = '{"http://foo.example.com:' + httpserv.identity.primaryPort + '": { "tls-ports": [' + serverPort + '] }}';
+  response.bodyOutputStream.write(body, body.length);
+}
+
 function altsvcHttp1Server2(metadata, response) {
 // this server should never be used thanks to an alt svc frame from the
 // h2 server.. but in case of some async lag in setting the alt svc route
 // up we have it.
   response.setStatusLine(metadata.httpVersion, 200, "OK");
   response.setHeader("Content-Type", "text/plain", false);
   response.setHeader("Connection", "close", false);
   var body = "hanging.\n";
   response.bodyOutputStream.write(body, body.length);
 }
 
+function h1ServerWK2(metadata, response) {
+  response.setStatusLine(metadata.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "application/json", false);
+  response.setHeader("Connection", "close", false);
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader("Access-Control-Allow-Origin", "*", false);
+  response.setHeader("Access-Control-Allow-Method", "GET", false);
+
+  var body = '{"http://foo.example.com:' + httpserv2.identity.primaryPort + '": { "tls-ports": [' + serverPort + '] }}';
+  response.bodyOutputStream.write(body, body.length);
+}
 function test_http2_altsvc() {
-  var chan = makeChan("http://localhost:" + httpserv.identity.primaryPort + "/altsvc1")
+  var chan = makeChan("http://foo.example.com:" + httpserv.identity.primaryPort + "/altsvc1")
            .QueryInterface(Components.interfaces.nsIHttpChannel);
   chan.asyncOpen2(altsvcClientListener);
 }
 
 var Http2PushApiListener = function() {};
 
 Http2PushApiListener.prototype = {
   checksPending: 9, // 4 onDataAvailable and 5 onStop
@@ -1012,30 +1035,39 @@ 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");
   do_check_neq(serverPort, null);
   dump("using port " + serverPort + "\n");
 
   // Set to allow the cert presented by our H2 server
   do_get_profile();
   prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
   speculativeLimit = prefs.getIntPref("network.http.speculative-parallel-limit");
   prefs.setIntPref("network.http.speculative-parallel-limit", 0);
 
+  // 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. Some older tests in
+  // this suite use localhost with a TOFU exception, but new ones should use
+  // foo.example.com
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                  .getService(Ci.nsIX509CertDB);
+  addCertFromFile(certdb, "CA.cert.der", "CTu,u,u");
+
   addCertOverride("localhost", serverPort,
                   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");
@@ -1046,22 +1078,42 @@ function run_test() {
 
   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);
+  httpserv.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK);
   httpserv.start(-1);
+  httpserv.identity.setPrimary("http", "foo.example.com", httpserv.identity.primaryPort);
 
   httpserv2 = new HttpServer();
   httpserv2.registerPathHandler("/altsvc2", altsvcHttp1Server2);
+  httpserv2.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK2);
   httpserv2.start(-1);
+  httpserv2.identity.setPrimary("http", "foo.example.com", httpserv2.identity.primaryPort);
 
   // And make go!
   run_next_test();
 }
+
+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());
+  fstream.close();
+  return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+  let certFile = do_get_file(filename, false);
+  let der = readFile(certFile);
+  certdb.addCert(der, trustString, null);
+}
--- a/python/mozbuild/mozbuild/mozinfo.py
+++ b/python/mozbuild/mozbuild/mozinfo.py
@@ -73,40 +73,33 @@ def build_dict(config, env=os.environ):
     # hardcoded list of 64-bit CPUs
     if p in ["x86_64", "ppc64"]:
         d["bits"] = 64
     # hardcoded list of known 32-bit CPUs
     elif p in ["x86", "arm", "ppc"]:
         d["bits"] = 32
     # other CPUs will wind up with unknown bits
 
+    d['debug'] = substs.get('MOZ_DEBUG') == '1'
+    d['nightly_build'] = substs.get('NIGHTLY_BUILD') == '1'
+    d['release_build'] = substs.get('RELEASE_BUILD') == '1'
+    d['pgo'] = substs.get('MOZ_PGO') == '1'
+    d['crashreporter'] = bool(substs.get('MOZ_CRASHREPORTER'))
+    d['datareporting'] = bool(substs.get('MOZ_DATA_REPORTING'))
+    d['healthreport'] = substs.get('MOZ_SERVICES_HEALTHREPORT') == '1'
+    d['sync'] = substs.get('MOZ_SERVICES_SYNC') == '1'
+    d['asan'] = substs.get('MOZ_ASAN') == '1'
+    d['tsan'] = substs.get('MOZ_TSAN') == '1'
+    d['telemetry'] = substs.get('MOZ_TELEMETRY_REPORTING') == '1'
+    d['tests_enabled'] = substs.get('ENABLE_TESTS') == "1"
     d['bin_suffix'] = substs.get('BIN_SUFFIX', '')
-
-    # This is where all bool-like values go.
-    for info_name, subst_name in [
-            ('debug', 'MOZ_DEBUG'),
-            ('nightly_build', 'NIGHTLY_BUILD'),
-            ('release_build', 'RELEASE_BUILD'),
-            ('pgo', 'MOZ_PGO'),
-            ('crashreporter', 'MOZ_CRASHREPORTER'),
-            ('datareporting', 'MOZ_DATA_REPORTING'),
-            ('healthreport', 'MOZ_SERVICES_HEALTHREPORT'),
-            ('sync', 'MOZ_SERVICES_SYNC'),
-            ('asan', 'MOZ_ASAN'),
-            ('tsan', 'MOZ_TSAN'),
-            ('telemetry', 'MOZ_TELEMETRY_REPORTING'),
-            ('tests_enabled', 'ENABLE_TESTS'),
-            ('addon_signing', 'MOZ_ADDON_SIGNING'),
-            ('require_signing', 'MOZ_REQUIRE_SIGNING'),
-            ('official', 'MOZILLA_OFFICIAL'),
-            ('sm_promise', 'SPIDERMONKEY_PROMISE'),
-            ('rust', 'MOZ_RUST'),
-    ]:
-        d[info_name] = bool(substs.get(subst_name))
-
+    d['addon_signing'] = substs.get('MOZ_ADDON_SIGNING') == '1'
+    d['require_signing'] = substs.get('MOZ_REQUIRE_SIGNING') == '1'
+    d['official'] = bool(substs.get('MOZILLA_OFFICIAL'))
+    d['sm_promise'] = bool(substs.get('SPIDERMONKEY_PROMISE'))
 
     def guess_platform():
         if d['buildapp'] in ('browser', 'mulet'):
             p = d['os']
             if p == 'mac':
                 p = 'macosx64'
             elif d['bits'] == 64:
                 p = '{}64'.format(p)
--- a/security/certverifier/ExtendedValidation.cpp
+++ b/security/certverifier/ExtendedValidation.cpp
@@ -1,25 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ExtendedValidation.h"
 
+#include "base64.h"
 #include "cert.h"
 #include "certdb.h"
-#include "base64.h"
 #include "hasht.h"
+#include "mozilla/ArrayUtils.h"
+#include "pk11pub.h"
 #include "pkix/pkixtypes.h"
-#include "pk11pub.h"
-#include "secerr.h"
 #include "prerror.h"
 #include "prinit.h"
+#include "secerr.h"
 
 extern mozilla::LazyLogModule gPIPNSSLog;
 
 #define CONST_OID static const unsigned char
 #define OI(x) { siDEROID, (unsigned char*) x, sizeof x }
 
 struct nsMyTrustedEVInfo
 {
@@ -1268,18 +1269,17 @@ RegisterOID(const SECItem& oidItem, cons
   od.mechanism = CKM_INVALID_MECHANISM;
   od.supportedExtension = INVALID_CERT_EXTENSION;
   return SECOID_AddEntry(&od);
 }
 
 static bool
 isEVPolicy(SECOidTag policyOIDTag)
 {
-  for (size_t iEV = 0; iEV < PR_ARRAY_SIZE(myTrustedEVInfos); ++iEV) {
-    nsMyTrustedEVInfo& entry = myTrustedEVInfos[iEV];
+  for (const nsMyTrustedEVInfo& entry : myTrustedEVInfos) {
     if (policyOIDTag == entry.oid_tag) {
       return true;
     }
   }
 
   return false;
 }
 
@@ -1289,34 +1289,33 @@ bool
 CertIsAuthoritativeForEVPolicy(const UniqueCERTCertificate& cert,
                                const mozilla::pkix::CertPolicyId& policy)
 {
   PR_ASSERT(cert);
   if (!cert) {
     return false;
   }
 
-  for (size_t iEV = 0; iEV < PR_ARRAY_SIZE(myTrustedEVInfos); ++iEV) {
-    nsMyTrustedEVInfo& entry = myTrustedEVInfos[iEV];
+  for (const nsMyTrustedEVInfo& entry : myTrustedEVInfos) {
     if (entry.cert && CERT_CompareCerts(cert.get(), entry.cert.get())) {
       const SECOidData* oidData = SECOID_FindOIDByTag(entry.oid_tag);
       if (oidData && oidData->oid.len == policy.numBytes &&
           !memcmp(oidData->oid.data, policy.bytes, policy.numBytes)) {
         return true;
       }
     }
   }
 
   return false;
 }
 
 static PRStatus
 IdentityInfoInit()
 {
-  for (size_t iEV = 0; iEV < PR_ARRAY_SIZE(myTrustedEVInfos); ++iEV) {
+  for (size_t iEV = 0; iEV < mozilla::ArrayLength(myTrustedEVInfos); ++iEV) {
     nsMyTrustedEVInfo& entry = myTrustedEVInfos[iEV];
 
     mozilla::ScopedAutoSECItem derIssuer;
     SECStatus rv = ATOB_ConvertAsciiToItem(&derIssuer, entry.issuer_base64);
     PR_ASSERT(rv == SECSuccess);
     if (rv != SECSuccess) {
       return PR_FAILURE;
     }
@@ -1395,18 +1394,17 @@ void
 EnsureIdentityInfoLoaded()
 {
   (void) PR_CallOnce(&sIdentityInfoCallOnce, IdentityInfoInit);
 }
 
 void
 CleanupIdentityInfo()
 {
-  for (size_t iEV = 0; iEV < PR_ARRAY_SIZE(myTrustedEVInfos); ++iEV) {
-    nsMyTrustedEVInfo &entry = myTrustedEVInfos[iEV];
+  for (nsMyTrustedEVInfo& entry : myTrustedEVInfos) {
     entry.cert = nullptr;
   }
 
   memset(&sIdentityInfoCallOnce, 0, sizeof(PRCallOnceType));
 }
 
 // Find the first policy OID that is known to be an EV policy OID.
 SECStatus
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -491,43 +491,49 @@ function handleRequest(req, res) {
     });
     push.writeHead(200, pushResponseHeaders);
     push.end("ok");
   }
 
   else if (u.pathname === "/altsvc1") {
     if (req.httpVersionMajor != 2 ||
       req.scheme != "http" ||
-      req.headers['alt-used'] != ("localhost:" + serverPort)) {
-      res.setHeader('Connection', 'close');
+      req.headers['alt-used'] != ("foo.example.com:" + serverPort)) {
       res.writeHead(400);
       res.end("WHAT?");
       return;
    }
    // test the alt svc frame for use with altsvc2
-   res.altsvc("localhost", serverPort, "h2", 3600, req.headers['x-redirect-origin']);
+   res.altsvc("foo.example.com", serverPort, "h2", 3600, req.headers['x-redirect-origin']);
   }
 
   else if (u.pathname === "/altsvc2") {
     if (req.httpVersionMajor != 2 ||
       req.scheme != "http" ||
-      req.headers['alt-used'] != ("localhost:" + serverPort)) {
-      res.setHeader('Connection', 'close');
+      req.headers['alt-used'] != ("foo.example.com:" + serverPort)) {
       res.writeHead(400);
       res.end("WHAT?");
       return;
    }
   }
 
   // for use with test_altsvc.js
   else if (u.pathname === "/altsvc-test") {
     res.setHeader('Cache-Control', 'no-cache');
     res.setHeader('Alt-Svc', 'h2=' + req.headers['x-altsvc']);
   }
 
+  else if (u.pathname === "/.well-known/http-opportunistic") {
+    res.setHeader('Cache-Control', 'no-cache');
+    res.setHeader('Content-Type', 'application/json');
+    res.writeHead(200, "OK");
+    res.end('{"http://' + req.headers['host'] + '": { "tls-ports": [' + serverPort + '] }}');
+    return;
+  }
+
   // for PushService tests.
   else if (u.pathname === "/pushSubscriptionSuccess/subscribe") {
     res.setHeader("Location",
                   'https://localhost:' + serverPort + '/pushSubscriptionSuccesss');
     res.setHeader("Link",
                   '</pushEndpointSuccess>; rel="urn:ietf:params:push", ' +
                   '</receiptPushEndpointSuccess>; rel="urn:ietf:params:push:receipt"');
     res.writeHead(201, "OK");
deleted file mode 100644
--- a/toolkit/crashreporter/test/unit/test_crash_rust_panic.js
+++ /dev/null
@@ -1,12 +0,0 @@
-function run_test()
-{
-  // Try crashing with a Rust panic
-  do_crash(function() {
-             Components.classes["@mozilla.org/xpcom/debug;1"].getService(Components.interfaces.nsIDebug2).rustPanic("OH NO");
-           },
-           function(mdump, extra) {
-             //TODO: check some extra things?
-           },
-          // process will exit with a zero exit status
-          true);
-}
--- a/toolkit/crashreporter/test/unit/xpcshell.ini
+++ b/toolkit/crashreporter/test/unit/xpcshell.ini
@@ -4,18 +4,16 @@ tail =
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   crasher_subprocess_head.js
   crasher_subprocess_tail.js
 
 [test_crash_moz_crash.js]
 [test_crash_purevirtual.js]
 [test_crash_runtimeabort.js]
-[test_crash_rust_panic.js]
-skip-if = !rust
 [test_crash_after_js_oom_reported.js]
 [test_crash_after_js_oom_recovered.js]
 [test_crash_after_js_oom_reported_2.js]
 [test_crash_after_js_large_allocation_failure.js]
 [test_crash_after_js_large_allocation_failure_reporting.js]
 [test_crash_oom.js]
 [test_oom_annotation_windows.js]
 skip-if = os != 'win'
--- a/toolkit/library/rust/lib.rs
+++ b/toolkit/library/rust/lib.rs
@@ -1,14 +1,5 @@
 // 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/.
 
 extern crate mp4parse_capi;
-
-use std::ffi::CStr;
-use std::os::raw::c_char;
-
-/// Used to implement `nsIDebug2::RustPanic` for testing purposes.
-#[no_mangle]
-pub extern fn intentional_panic(message: *const c_char) {
-    panic!("{}", unsafe { CStr::from_ptr(message) }.to_string_lossy());
-}
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -1203,35 +1203,47 @@ RemoveFile::Execute()
   // been removed by a separate instruction: bug 311099.
   int rv = NS_taccess(mFile.get(), F_OK);
   if (rv) {
     LOG(("file cannot be removed because it does not exist; skipping"));
     mSkip = 1;
     return OK;
   }
 
-  // Rename the old file. It will be removed in Finish.
-  rv = backup_create(mFile.get());
-  if (rv) {
-    LOG(("backup_create failed: %d", rv));
-    return rv;
+  if (sStagedUpdate) {
+    // Staged updates don't need backup files so just remove it.
+    rv = ensure_remove(mFile.get());
+    if (rv) {
+      return rv;
+    }
+  } else {
+    // Rename the old file. It will be removed in Finish.
+    rv = backup_create(mFile.get());
+    if (rv) {
+      LOG(("backup_create failed: %d", rv));
+      return rv;
+    }
   }
 
   return OK;
 }
 
 void
 RemoveFile::Finish(int status)
 {
-  if (mSkip)
+  if (mSkip) {
     return;
+  }
 
   LOG(("FINISH REMOVEFILE " LOG_S, mRelPath.get()));
 
-  backup_finish(mFile.get(), mRelPath.get(), status);
+  // Staged updates don't create backup files.
+  if (!sStagedUpdate) {
+    backup_finish(mFile.get(), mRelPath.get(), status);
+  }
 }
 
 class RemoveDir : public Action
 {
 public:
   RemoveDir() : mSkip(0) { }
 
   virtual int Parse(NS_tchar *line);
@@ -1396,19 +1408,25 @@ AddFile::Execute()
 {
   LOG(("EXECUTE ADD " LOG_S, mRelPath.get()));
 
   int rv;
 
   // First make sure that we can actually get rid of any existing file.
   rv = NS_taccess(mFile.get(), F_OK);
   if (rv == 0) {
-    rv = backup_create(mFile.get());
-    if (rv)
+    if (sStagedUpdate) {
+      // Staged updates don't need backup files so just remove it.
+      rv = ensure_remove(mFile.get());
+    } else {
+      rv = backup_create(mFile.get());
+    }
+    if (rv) {
       return rv;
+    }
   } else {
     rv = ensure_parent_dir(mFile.get());
     if (rv)
       return rv;
   }
 
 #ifdef XP_WIN
   char sourcefile[MAXPATHLEN];
@@ -1427,21 +1445,25 @@ AddFile::Execute()
   }
   return rv;
 }
 
 void
 AddFile::Finish(int status)
 {
   LOG(("FINISH ADD " LOG_S, mRelPath.get()));
-  // When there is an update failure and a file has been added it is removed
-  // here since there might not be a backup to replace it.
-  if (status && mAdded)
-    NS_tremove(mFile.get());
-  backup_finish(mFile.get(), mRelPath.get(), status);
+  // Staged updates don't create backup files.
+  if (!sStagedUpdate) {
+    // When there is an update failure and a file has been added it is removed
+    // here since there might not be a backup to replace it.
+    if (status && mAdded) {
+      NS_tremove(mFile.get());
+    }
+    backup_finish(mFile.get(), mRelPath.get(), status);
+  }
 }
 
 class PatchFile : public Action
 {
 public:
   PatchFile() : mPatchFile(nullptr), mPatchIndex(-1), buf(nullptr) { }
 
   virtual ~PatchFile();
@@ -1657,19 +1679,22 @@ PatchFile::Execute()
   struct NS_tstat_t ss;
   rv = NS_tstat(mFile.get(), &ss);
   if (rv) {
     LOG(("failed to read file status info: " LOG_S ", err: %d",
          mFileRelPath.get(), errno));
     return READ_ERROR;
   }
 
-  rv = backup_create(mFile.get());
-  if (rv) {
-    return rv;
+  // Staged updates don't need backup files.
+  if (!sStagedUpdate) {
+    rv = backup_create(mFile.get());
+    if (rv) {
+      return rv;
+    }
   }
 
 #if defined(HAVE_POSIX_FALLOCATE)
   AutoFile ofile(ensure_open(mFile.get(), NS_T("wb+"), ss.st_mode));
   posix_fallocate(fileno((FILE *)ofile), 0, header.dlen);
 #elif defined(XP_WIN)
   bool shouldTruncate = true;
   // Creating the file, setting the size, and then closing the file handle
@@ -1748,17 +1773,20 @@ PatchFile::Execute()
   return rv;
 }
 
 void
 PatchFile::Finish(int status)
 {
   LOG(("FINISH PATCH " LOG_S, mFileRelPath.get()));
 
-  backup_finish(mFile.get(), mFileRelPath.get(), status);
+  // Staged updates don't create backup files.
+  if (!sStagedUpdate) {
+    backup_finish(mFile.get(), mFileRelPath.get(), status);
+  }
 }
 
 class AddIfFile : public AddFile
 {
 public:
   virtual int Parse(NS_tchar *line);
   virtual int Prepare();
   virtual int Execute();
--- a/xpcom/base/moz.build
+++ b/xpcom/base/moz.build
@@ -152,11 +152,8 @@ FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../build',
     '/xpcom/ds',
 ]
 
 if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
     CXXFLAGS += CONFIG['TK_CFLAGS']
-
-if CONFIG['MOZ_RUST']:
-    DEFINES['MOZ_RUST'] = True
--- a/xpcom/base/nsDebugImpl.cpp
+++ b/xpcom/base/nsDebugImpl.cpp
@@ -142,32 +142,16 @@ nsDebugImpl::Break(const char* aFile, in
 
 NS_IMETHODIMP
 nsDebugImpl::Abort(const char* aFile, int32_t aLine)
 {
   NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, aFile, aLine);
   return NS_OK;
 }
 
-#ifdef MOZ_RUST
-// From toolkit/library/rust/lib.rs
-extern "C" void intentional_panic(const char* message);
-#endif
-
-NS_IMETHODIMP
-nsDebugImpl::RustPanic(const char* aMessage)
-{
-#ifdef MOZ_RUST
-  intentional_panic(aMessage);
-  return NS_OK;
-#else
-  return NS_ERROR_NOT_IMPLEMENTED;
-#endif
-}
-
 NS_IMETHODIMP
 nsDebugImpl::GetIsDebugBuild(bool* aResult)
 {
 #ifdef DEBUG
   *aResult = true;
 #else
   *aResult = false;
 #endif
--- a/xpcom/base/nsIDebug2.idl
+++ b/xpcom/base/nsIDebug2.idl
@@ -74,16 +74,9 @@ interface nsIDebug2 : nsISupports
     /**
      * Request the process to trigger a fatal abort.
      *
      * @param aFile file containing abort request
      * @param aLine line number of abort request
      */
     void abort(in string aFile, 
                in long aLine);
-
-    /**
-     * Request the process to trigger a fatal panic!() from Rust code.
-     *
-     * @param aMessage the string to pass to panic!().
-     */
-    void rustPanic(in string aMessage);
 };