Merge inbound to mozilla-central r=merge a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Mon, 06 Nov 2017 12:45:47 +0200
changeset 443554 dc45ee24c55d1061951956321bd8481d517ce22a
parent 443553 70f38f59f9fa45dda5d70d44881dc1d906de15f6 (current diff)
parent 443525 382939cc1ff5dc276d7c7d70d14fece9fbd59e77 (diff)
child 443555 958eef714e2b68bdfd364fa3ff39ed1a79cb616a
child 443563 ec85e0d367466bb953492b6d6cc6861d66d8e8ee
child 443580 4af5ef27d314d0deeeb87e70798b765a6acc8efb
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central r=merge a=merge
dom/media/test/mochitest.ini
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -924,17 +924,17 @@ D3D11DXVA2Manager::CopyToImage(IMFSample
     new D3D11ShareHandleImage(gfx::IntSize(mWidth, mHeight), aRegion);
   bool ok = image->AllocateTexture(mTextureClientAllocator, mDevice);
   NS_ENSURE_TRUE(ok, E_FAIL);
 
   RefPtr<TextureClient> client = image->GetTextureClient(ImageBridgeChild::GetSingleton().get());
   NS_ENSURE_TRUE(client, E_FAIL);
 
   RefPtr<IDXGIKeyedMutex> mutex;
-  HRESULT hr;
+  HRESULT hr = S_OK;
   RefPtr<ID3D11Texture2D> texture = image->GetTexture();
 
   texture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
 
   {
     AutoTextureLock(mutex, hr, 2000);
     if (mutex && (FAILED(hr) || hr == WAIT_TIMEOUT || hr == WAIT_ABANDONED)) {
       return hr;
@@ -982,17 +982,19 @@ D3D11DXVA2Manager::CopyToImage(IMFSample
       NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
     }
   }
 
   if (!mutex && mDevice != DeviceManagerDx::Get()->GetCompositorDevice() && mSyncObject) {
     // It appears some race-condition may allow us to arrive here even when mSyncObject
     // is null. It's better to avoid that crash.
     client->SyncWithObject(mSyncObject);
-    mSyncObject->Synchronize();
+    if (!mSyncObject->Synchronize(true)) {
+      return DXGI_ERROR_DEVICE_RESET;
+    }
   }
 
   image.forget(aOutImage);
 
   return S_OK;
 }
 
 HRESULT
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -110,45 +110,52 @@ WMFMediaDataDecoder::Decode(MediaRawData
 
 RefPtr<MediaDataDecoder::DecodePromise>
 WMFMediaDataDecoder::ProcessError(HRESULT aError, const char* aReason)
 {
   if (!mRecordedError) {
     SendTelemetry(aError);
     mRecordedError = true;
   }
+
+  //TODO: For the error DXGI_ERROR_DEVICE_RESET, we could return
+  // NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER to get the latest device. Maybe retry
+  // up to 3 times.
   return DecodePromise::CreateAndReject(
     MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
                 RESULT_DETAIL("%s:%x", aReason, aError)),
     __func__);
 }
 
 RefPtr<MediaDataDecoder::DecodePromise>
 WMFMediaDataDecoder::ProcessDecode(MediaRawData* aSample)
 {
   DecodedData results;
   HRESULT hr = mMFTManager->Input(aSample);
   if (hr == MF_E_NOTACCEPTING) {
-    ProcessOutput(results);
+    hr = ProcessOutput(results);
+    if (FAILED(hr)) {
+      return ProcessError(hr, "MFTManager::Output(1)");
+    }
     hr = mMFTManager->Input(aSample);
   }
 
   if (FAILED(hr)) {
     NS_WARNING("MFTManager rejected sample");
     return ProcessError(hr, "MFTManager::Input");
   }
 
   mDrainStatus = DrainStatus::DRAINABLE;
   mLastStreamOffset = aSample->mOffset;
 
   hr = ProcessOutput(results);
   if (SUCCEEDED(hr) || hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
     return DecodePromise::CreateAndResolve(Move(results), __func__);
   }
-  return ProcessError(hr, "MFTManager::Output");
+  return ProcessError(hr, "MFTManager::Output(2)");
 }
 
 HRESULT
 WMFMediaDataDecoder::ProcessOutput(DecodedData& aResults)
 {
   RefPtr<MediaData> output;
   HRESULT hr = S_OK;
   while (SUCCEEDED(hr = mMFTManager->Output(mLastStreamOffset, output))) {
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -103,11 +103,11 @@ load buffer-source-resampling-start-1.ht
 load buffer-source-slow-resampling-1.html
 load doppler-1.html
 HTTP load media-element-source-seek-1.html
 load offline-buffer-source-ended-1.html
 load oscillator-ended-1.html
 load oscillator-ended-2.html
 skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876
 # This needs to run at the end to avoid leaking busted state into other tests.
-load 691096-1.html
+skip-if(Android) load 691096-1.html # Bug 1365451
 load 1236639.html
 test-pref(media.navigator.permission.disabled,true) load 1388372.html
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -838,16 +838,17 @@ skip-if = android_version == '17' || and
 tags=msg
 [test_mediarecorder_principals.html]
 skip-if = (os == 'linux' && bits == 64) || toolkit == 'android' # See bug 1266345, android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_4ch_audiocontext.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_addtracked_stream.html]
+skip-if = toolkit == 'android' # Bug 1408241
 tags=msg capturestream
 [test_mediarecorder_record_audiocontext.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_audiocontext_mlk.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_audionode.html]
--- a/gfx/layers/SyncObject.h
+++ b/gfx/layers/SyncObject.h
@@ -23,49 +23,51 @@ typedef uintptr_t SyncHandle;
 class SyncObjectHost : public RefCounted<SyncObjectHost>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SyncObjectHost)
   virtual ~SyncObjectHost() { }
 
   static already_AddRefed<SyncObjectHost> CreateSyncObjectHost(
 #ifdef XP_WIN
-                                                                       ID3D11Device* aDevice = nullptr
+                                                               ID3D11Device* aDevice = nullptr
 #endif
-                                                                      );
+                                                              );
 
   virtual bool Init() = 0;
 
   virtual SyncHandle GetSyncHandle() = 0;
 
+  // Return false for failed synchronization.
   virtual bool Synchronize() = 0;
 
 protected:
   SyncObjectHost() { }
 };
 
 class SyncObjectClient : public external::AtomicRefCounted<SyncObjectClient>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SyncObjectClient)
   virtual ~SyncObjectClient() { }
 
   static already_AddRefed<SyncObjectClient> CreateSyncObjectClient(SyncHandle aHandle
 #ifdef XP_WIN
-                                                                     , ID3D11Device* aDevice = nullptr
+                                                                   , ID3D11Device* aDevice = nullptr
 #endif
-                                                                    );
+                                                                  );
 
   enum class SyncType {
     D3D11,
   };
 
   virtual SyncType GetSyncType() = 0;
 
-  virtual void Synchronize() = 0;
+  // Return false for failed synchronization.
+  virtual bool Synchronize(bool aFallible = false) = 0;
 
   virtual bool IsSyncObjectValid() = 0;
 
 protected:
   SyncObjectClient() { }
 };
 
 } // namespace layers
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -1692,40 +1692,45 @@ SyncObjectD3D11Client::SyncObjectD3D11Cl
     mDevice = DeviceManagerDx::Get()->GetContentDevice();
     return;
   }
 
   mDevice = aDevice;
 }
 
 bool
-SyncObjectD3D11Client::Init()
+SyncObjectD3D11Client::Init(bool aFallible)
 {
   if (mKeyedMutex) {
     return true;
   }
 
   HRESULT hr = mDevice->OpenSharedResource(
     mSyncHandle,
     __uuidof(ID3D11Texture2D),
     (void**)(ID3D11Texture2D**)getter_AddRefs(mSyncTexture));
   if (FAILED(hr) || !mSyncTexture) {
     gfxCriticalNote << "Failed to OpenSharedResource for SyncObjectD3D11: " << hexa(hr);
-    if (ShouldDevCrashOnSyncInitFailure()) {
+    if (!aFallible && ShouldDevCrashOnSyncInitFailure()) {
       gfxDevCrash(LogReason::D3D11FinalizeFrame) << "Without device reset: " << hexa(hr);
     }
     return false;
   }
 
   hr = mSyncTexture->QueryInterface(__uuidof(IDXGIKeyedMutex), getter_AddRefs(mKeyedMutex));
   if (FAILED(hr) || !mKeyedMutex) {
     // Leave both the critical error and MOZ_CRASH for now; the critical error lets
     // us "save" the hr value.  We will probably eventually replace this with gfxDevCrash.
-    gfxCriticalError() << "Failed to get KeyedMutex (2): " << hexa(hr);
-    MOZ_CRASH("GFX: Cannot get D3D11 KeyedMutex");
+    if (!aFallible) {
+      gfxCriticalError() << "Failed to get KeyedMutex (2): " << hexa(hr);
+      MOZ_CRASH("GFX: Cannot get D3D11 KeyedMutex");
+    } else {
+      gfxCriticalNote << "Failed to get KeyedMutex (3): " << hexa(hr);
+    }
+    return false;
   }
 
   return true;
 }
 
 void
 SyncObjectD3D11Client::RegisterTexture(ID3D11Texture2D* aTexture)
 {
@@ -1742,68 +1747,75 @@ SyncObjectD3D11Client::IsSyncObjectValid
   return true;
 }
 
 // We have only 1 sync object. As a thing that somehow works,
 // we copy each of the textures that need to be synced with the compositor
 // into our sync object and only use a lock for this sync object.
 // This way, we don't have to sync every texture we send to the compositor.
 // We only have to do this once per transaction.
-void
-SyncObjectD3D11Client::Synchronize()
+bool
+SyncObjectD3D11Client::Synchronize(bool aFallible)
 {
   // Since this can be called from either the Paint or Main thread.
   // We don't want this to race since we initialize the sync texture here
   // too.
   MutexAutoLock syncLock(mSyncLock);
 
   if (!mSyncedTextures.size()) {
-    return;
+    return true;
   }
-  if (!Init()) {
-    return;
+  if (!Init(aFallible)) {
+    return false;
   }
 
   HRESULT hr;
   AutoTextureLock lock(mKeyedMutex, hr, 20000);
 
   if (hr == WAIT_TIMEOUT) {
     if (DeviceManagerDx::Get()->HasDeviceReset()) {
       gfxWarning() << "AcquireSync timed out because of device reset.";
-      return;
+      return false;
     }
-    gfxDevCrash(LogReason::D3D11SyncLock) << "Timeout on the D3D11 sync lock";
+    if (aFallible) {
+      gfxWarning() << "Timeout on the D3D11 sync lock.";
+    } else {
+      gfxDevCrash(LogReason::D3D11SyncLock) << "Timeout on the D3D11 sync lock.";
+    }
+    return false;
   }
 
   D3D11_BOX box;
   box.front = box.top = box.left = 0;
   box.back = box.bottom = box.right = 1;
 
   RefPtr<ID3D11Device> dev;
   mSyncTexture->GetDevice(getter_AddRefs(dev));
 
   if (dev == DeviceManagerDx::Get()->GetContentDevice()) {
     if (DeviceManagerDx::Get()->HasDeviceReset()) {
-      return;
+      return false;
     }
   }
 
   if (dev != mDevice) {
     gfxWarning() << "Attempt to sync texture from invalid device.";
-    return;
+    return false;
   }
 
   RefPtr<ID3D11DeviceContext> ctx;
   dev->GetImmediateContext(getter_AddRefs(ctx));
 
   for (auto iter = mSyncedTextures.begin(); iter != mSyncedTextures.end(); iter++) {
     ctx->CopySubresourceRegion(mSyncTexture, 0, 0, 0, 0, *iter, 0, &box);
   }
 
   mSyncedTextures.clear();
+
+  return true;
 }
 
 uint32_t
 GetMaxTextureSizeFromDevice(ID3D11Device* aDevice)
 {
   return GetMaxTextureSizeForFeatureLevel(aDevice->GetFeatureLevel());
 }
 
--- a/gfx/layers/d3d11/TextureD3D11.h
+++ b/gfx/layers/d3d11/TextureD3D11.h
@@ -473,26 +473,26 @@ private:
   RefPtr<IDXGIKeyedMutex> mKeyedMutex;
 };
 
 class SyncObjectD3D11Client : public SyncObjectClient
 {
 public:
   explicit SyncObjectD3D11Client(SyncHandle aSyncHandle, ID3D11Device* aDevice);
 
-  virtual void Synchronize() override;
+  virtual bool Synchronize(bool aFallible) override;
 
   virtual bool IsSyncObjectValid() override;
 
   virtual SyncType GetSyncType() override { return SyncType::D3D11; }
 
   void RegisterTexture(ID3D11Texture2D* aTexture);
 
 private:
-  bool Init();
+  bool Init(bool aFallible);
 
   SyncHandle mSyncHandle;
   RefPtr<ID3D11Device> mDevice;
   RefPtr<ID3D11Texture2D> mSyncTexture;
   RefPtr<IDXGIKeyedMutex> mKeyedMutex;
   std::vector<ID3D11Texture2D*> mSyncedTextures;
   Mutex mSyncLock;
 };
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -1205,20 +1205,21 @@ class GCRuntime
     GCSchedulingTunables tunables;
     GCSchedulingState schedulingState;
 
     // State used for managing atom mark bitmaps in each zone. Protected by the
     // exclusive access lock.
     AtomMarkingRuntime atomMarking;
 
   private:
-    // When empty, chunks reside in the emptyChunks pool and are re-used as
-    // needed or eventually expired if not re-used. The emptyChunks pool gets
-    // refilled from the background allocation task heuristically so that empty
-    // chunks should always available for immediate allocation without syscalls.
+    // When chunks are empty, they reside in the emptyChunks pool and are
+    // re-used as needed or eventually expired if not re-used. The emptyChunks
+    // pool gets refilled from the background allocation task heuristically so
+    // that empty chunks should always be available for immediate allocation
+    // without syscalls.
     GCLockData<ChunkPool> emptyChunks_;
 
     // Chunks which have had some, but not all, of their arenas allocated live
     // in the available chunk lists. When all available arenas in a chunk have
     // been allocated, the chunk is removed from the available list and moved
     // to the fullChunks pool. During a GC, if all arenas are free, the chunk
     // is moved back to the emptyChunks pool and scheduled for eventual
     // release.
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -111,17 +111,18 @@ js::NurseryChunk::toChunk(JSRuntime* rt)
 
 js::Nursery::Nursery(JSRuntime* rt)
   : runtime_(rt)
   , position_(0)
   , currentStartChunk_(0)
   , currentStartPosition_(0)
   , currentEnd_(0)
   , currentChunk_(0)
-  , maxNurseryChunks_(0)
+  , maxChunkCount_(0)
+  , chunkCountLimit_(0)
   , previousPromotionRate_(0)
   , profileThreshold_(0)
   , enableProfiling_(false)
   , reportTenurings_(0)
   , minorGCTriggerReason_(JS::gcreason::NO_REASON)
   , minorGcCount_(0)
   , freeMallocedBuffersTask(nullptr)
 #ifdef JS_GC_ZEAL
@@ -135,25 +136,28 @@ js::Nursery::init(uint32_t maxNurseryByt
     if (!mallocedBuffers.init())
         return false;
 
     freeMallocedBuffersTask = js_new<FreeMallocedBuffersTask>(runtime()->defaultFreeOp());
     if (!freeMallocedBuffersTask || !freeMallocedBuffersTask->init())
         return false;
 
     /* maxNurseryBytes parameter is rounded down to a multiple of chunk size. */
-    maxNurseryChunks_ = maxNurseryBytes >> ChunkShift;
+    chunkCountLimit_ = maxNurseryBytes >> ChunkShift;
 
     /* If no chunks are specified then the nursery is permanently disabled. */
-    if (maxNurseryChunks_ == 0)
+    if (chunkCountLimit_ == 0)
         return true;
 
-    updateNumChunksLocked(1, lock);
-    if (numChunks() == 0)
+    maxChunkCount_ = 1;
+    if (!allocateNextChunk(0, lock)) {
+        maxChunkCount_ = 0;
         return false;
+    }
+    /* After this point the Nursery has been enabled */
 
     setCurrentChunk(0);
     setStartPosition();
 
     char* env = getenv("JS_GC_PROFILE_NURSERY");
     if (env) {
         if (0 == strcmp(env, "help")) {
             fprintf(stderr, "JS_GC_PROFILE_NURSERY=N\n"
@@ -187,22 +191,27 @@ js::Nursery::~Nursery()
     js_delete(freeMallocedBuffersTask);
 }
 
 void
 js::Nursery::enable()
 {
     MOZ_ASSERT(isEmpty());
     MOZ_ASSERT(!runtime()->gc.isVerifyPreBarriersEnabled());
-    if (isEnabled() || !maxChunks())
+    if (isEnabled() || !chunkCountLimit())
         return;
 
-    updateNumChunks(1);
-    if (numChunks() == 0)
-        return;
+    {
+        AutoLockGCBgAlloc lock(runtime());
+        maxChunkCount_ = 1;
+        if (!allocateNextChunk(0, lock)) {
+            maxChunkCount_ = 0;
+            return;
+        }
+    }
 
     setCurrentChunk(0);
     setStartPosition();
 #ifdef JS_GC_ZEAL
     if (runtime()->hasZealMode(ZealMode::GenerationalGC))
         enterZealMode();
 #endif
 
@@ -210,18 +219,22 @@ js::Nursery::enable()
 }
 
 void
 js::Nursery::disable()
 {
     MOZ_ASSERT(isEmpty());
     if (!isEnabled())
         return;
-    updateNumChunks(0);
+
+    freeChunksFrom(0);
+    maxChunkCount_ = 0;
+
     currentEnd_ = 0;
+
     runtime()->gc.storeBuffer().disable();
 }
 
 bool
 js::Nursery::isEmpty() const
 {
     if (!isEnabled())
         return true;
@@ -232,17 +245,17 @@ js::Nursery::isEmpty() const
     }
     return position() == currentStartPosition_;
 }
 
 #ifdef JS_GC_ZEAL
 void
 js::Nursery::enterZealMode() {
     if (isEnabled())
-        updateNumChunks(maxNurseryChunks_);
+        maxChunkCount_ = chunkCountLimit();
 }
 
 void
 js::Nursery::leaveZealMode() {
     if (isEnabled()) {
         MOZ_ASSERT(isEmpty());
         setCurrentChunk(0);
         setStartPosition();
@@ -298,19 +311,29 @@ js::Nursery::allocate(size_t size)
 
 #ifdef JS_GC_ZEAL
     static const size_t CanarySize = (sizeof(Nursery::Canary) + CellAlignBytes - 1) & ~CellAlignMask;
     if (runtime()->gc.hasZealMode(ZealMode::CheckNursery))
         size += CanarySize;
 #endif
 
     if (currentEnd() < position() + size) {
-        if (currentChunk_ + 1 == numChunks())
+        unsigned chunkno = currentChunk_ + 1;
+        MOZ_ASSERT(chunkno <= chunkCountLimit());
+        MOZ_ASSERT(chunkno <= maxChunkCount());
+        MOZ_ASSERT(chunkno <= allocatedChunkCount());
+        if (chunkno == maxChunkCount())
             return nullptr;
-        setCurrentChunk(currentChunk_ + 1);
+        if (MOZ_UNLIKELY(chunkno == allocatedChunkCount())) {
+            AutoLockGCBgAlloc lock(runtime());
+            if (!allocateNextChunk(chunkno, lock))
+                return nullptr;
+            MOZ_ASSERT(chunkno < allocatedChunkCount());
+        }
+        setCurrentChunk(chunkno);
     }
 
     void* thing = (void*)position();
     position_ = position() + size;
 
     JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size);
 
 #ifdef JS_GC_ZEAL
@@ -519,16 +542,17 @@ js::Nursery::renderProfileJSON(JSONPrint
 
     json.property("status", "complete");
 
     json.property("reason", JS::gcreason::ExplainReason(previousGC.reason));
     json.property("bytes_tenured", previousGC.tenuredBytes);
     json.property("bytes_used", previousGC.nurseryUsedBytes);
     json.property("cur_capacity", previousGC.nurseryCapacity);
     json.property("new_capacity", spaceToEnd());
+    json.property("lazy_capacity", allocatedChunkCount() * ChunkSize);
 
     json.beginObjectProperty("phase_times");
 
 #define EXTRACT_NAME(name, text) #name,
     static const char* names[] = {
 FOR_EACH_NURSERY_PROFILE_TIME(EXTRACT_NAME)
 #undef EXTRACT_NAME
     "" };
@@ -681,17 +705,17 @@ js::Nursery::collect(JS::gcreason::Reaso
     // We ignore gcMaxBytes when allocating for minor collection. However, if we
     // overflowed, we disable the nursery. The next time we allocate, we'll fail
     // because gcBytes >= gcMaxBytes.
     if (rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes())
         disable();
     // Disable the nursery if the user changed the configuration setting.  The
     // nursery can only be re-enabled by resetting the configurationa and
     // restarting firefox.
-    if (maxNurseryChunks_ == 0)
+    if (chunkCountLimit_ == 0)
         disable();
 
     endProfile(ProfileKey::Total);
     minorGcCount_++;
 
     TimeDuration totalTime = profileDurations_[ProfileKey::Total];
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_US, totalTime.ToMicroseconds());
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON, reason);
@@ -704,17 +728,17 @@ js::Nursery::collect(JS::gcreason::Reaso
     TraceMinorGCEnd();
 
     if (enableProfiling_ && totalTime >= profileThreshold_) {
         rt->gc.stats().maybePrintProfileHeaders();
 
         fprintf(stderr, "MinorGC: %20s %5.1f%% %4u        ",
                 JS::gcreason::ExplainReason(reason),
                 promotionRate * 100,
-                numChunks());
+                maxChunkCount());
         printProfileDurations(profileDurations_);
 
         if (reportTenurings_) {
             for (auto& entry : tenureCounts.entries) {
                 if (entry.count >= reportTenurings_) {
                     fprintf(stderr, "  %d x ", entry.count);
                     entry.group->print();
                 }
@@ -909,64 +933,90 @@ js::Nursery::sweep(JSTracer* trc)
     sweepDictionaryModeObjects();
 }
 
 void
 js::Nursery::clear()
 {
 #ifdef JS_GC_ZEAL
     /* Poison the nursery contents so touching a freed object will crash. */
-    for (unsigned i = 0; i < numChunks(); i++)
+    for (unsigned i = 0; i < allocatedChunkCount(); i++)
         chunk(i).poisonAndInit(runtime(), JS_SWEPT_NURSERY_PATTERN);
 
     if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
         /* Only reset the alloc point when we are close to the end. */
-        if (currentChunk_ + 1 == numChunks())
+        if (currentChunk_ + 1 == maxChunkCount())
             setCurrentChunk(0);
     } else
 #endif
     {
 #ifdef JS_CRASH_DIAGNOSTICS
-        for (unsigned i = 0; i < numChunks(); ++i)
+        for (unsigned i = 0; i < allocatedChunkCount(); ++i)
             chunk(i).poisonAndInit(runtime(), JS_SWEPT_NURSERY_PATTERN);
 #endif
         setCurrentChunk(0);
     }
 
     /* Set current start position for isEmpty checks. */
     setStartPosition();
 }
 
 size_t
 js::Nursery::spaceToEnd() const
 {
-    unsigned lastChunk = numChunks() - 1;
+    unsigned lastChunk = maxChunkCount() - 1;
 
     MOZ_ASSERT(lastChunk >= currentStartChunk_);
     MOZ_ASSERT(currentStartPosition_ - chunk(currentStartChunk_).start() <= NurseryChunkUsableSize);
 
     size_t bytes = (chunk(currentStartChunk_).end() - currentStartPosition_) +
                    ((lastChunk - currentStartChunk_) * NurseryChunkUsableSize);
 
-    MOZ_ASSERT(bytes <= numChunks() * NurseryChunkUsableSize);
+    MOZ_ASSERT(bytes <= maxChunkCount() * NurseryChunkUsableSize);
 
     return bytes;
 }
 
 MOZ_ALWAYS_INLINE void
 js::Nursery::setCurrentChunk(unsigned chunkno)
 {
-    MOZ_ASSERT(chunkno < maxChunks());
-    MOZ_ASSERT(chunkno < numChunks());
+    MOZ_ASSERT(chunkno < chunkCountLimit());
+    MOZ_ASSERT(chunkno < allocatedChunkCount());
     currentChunk_ = chunkno;
     position_ = chunk(chunkno).start();
     currentEnd_ = chunk(chunkno).end();
     chunk(chunkno).poisonAndInit(runtime(), JS_FRESH_NURSERY_PATTERN);
 }
 
+bool
+js::Nursery::allocateNextChunk(const unsigned chunkno,
+    AutoLockGCBgAlloc& lock)
+{
+    const unsigned priorCount = allocatedChunkCount();
+    const unsigned newCount = priorCount + 1;
+
+    MOZ_ASSERT((chunkno == currentChunk_ + 1) || (chunkno == 0 && allocatedChunkCount() == 0));
+    MOZ_ASSERT(chunkno == allocatedChunkCount());
+    MOZ_ASSERT(chunkno < chunkCountLimit());
+    MOZ_ASSERT(chunkno < maxChunkCount());
+
+    if (!chunks_.resize(newCount))
+        return false;
+
+    Chunk* newChunk;
+    newChunk = runtime()->gc.getOrAllocChunk(lock);
+    if (!newChunk) {
+        chunks_.shrinkTo(priorCount);
+        return false;
+    }
+
+    chunks_[chunkno] = NurseryChunk::fromChunk(newChunk);
+    return true;
+}
+
 MOZ_ALWAYS_INLINE void
 js::Nursery::setStartPosition()
 {
     currentStartChunk_ = currentChunk_;
     currentStartPosition_ = position();
 }
 
 void
@@ -993,106 +1043,82 @@ js::Nursery::maybeResizeNursery(JS::gcre
      * This incorrect promotion rate results in better nursery sizing
      * decisions, however we should to better tuning based on the real
      * promotion rate in the future.
      */
     const float promotionRate =
         float(previousGC.tenuredBytes) / float(previousGC.nurseryCapacity);
 
     newMaxNurseryChunks = runtime()->gc.tunables.gcMaxNurseryBytes() >> ChunkShift;
-    if (newMaxNurseryChunks != maxNurseryChunks_) {
-        maxNurseryChunks_ = newMaxNurseryChunks;
+    if (newMaxNurseryChunks != chunkCountLimit_) {
+        chunkCountLimit_ = newMaxNurseryChunks;
         /* The configured maximum nursery size is changing */
-        const int extraChunks = numChunks() - newMaxNurseryChunks;
-        if (extraChunks > 0) {
+        if (maxChunkCount() > newMaxNurseryChunks) {
             /* We need to shrink the nursery */
-            shrinkAllocableSpace(extraChunks);
+            shrinkAllocableSpace(newMaxNurseryChunks);
 
             previousPromotionRate_ = promotionRate;
             return;
         }
     }
 
-    if (promotionRate > GrowThreshold)
+    if (promotionRate > GrowThreshold) {
+        // The GC nursery is an optimization and so if we fail to allocate
+        // nursery chunks we do not report an error.
         growAllocableSpace();
-    else if (promotionRate < ShrinkThreshold && previousPromotionRate_ < ShrinkThreshold)
-        shrinkAllocableSpace(1);
+    } else if (promotionRate < ShrinkThreshold && previousPromotionRate_ < ShrinkThreshold) {
+        shrinkAllocableSpace(maxChunkCount() - 1);
+    }
 
     previousPromotionRate_ = promotionRate;
 }
 
 void
 js::Nursery::growAllocableSpace()
 {
-    updateNumChunks(Min(numChunks() * 2, maxNurseryChunks_));
+    maxChunkCount_ = Min(maxChunkCount() * 2, chunkCountLimit());
 }
 
 void
-js::Nursery::shrinkAllocableSpace(unsigned removeNumChunks)
+js::Nursery::freeChunksFrom(unsigned firstFreeChunk)
 {
-#ifdef JS_GC_ZEAL
-    if (runtime()->hasZealMode(ZealMode::GenerationalGC))
-        return;
-#endif
-    updateNumChunks(Max(numChunks() - removeNumChunks, 1u));
+    MOZ_ASSERT(firstFreeChunk < chunks_.length());
+    {
+        AutoLockGC lock(runtime());
+        for (unsigned i = firstFreeChunk; i < chunks_.length(); i++)
+            runtime()->gc.recycleChunk(chunk(i).toChunk(runtime()), lock);
+    }
+    chunks_.shrinkTo(firstFreeChunk);
 }
 
 void
-js::Nursery::minimizeAllocableSpace()
+js::Nursery::shrinkAllocableSpace(unsigned newCount)
 {
 #ifdef JS_GC_ZEAL
     if (runtime()->hasZealMode(ZealMode::GenerationalGC))
         return;
 #endif
-    updateNumChunks(1);
-}
+
+    // Don't shrink the nursery to zero (use Nursery::disable() instead) and
+    // don't attempt to shrink it to the same size.
+    if ((newCount == 0) || (newCount == maxChunkCount()))
+        return;
 
-void
-js::Nursery::updateNumChunks(unsigned newCount)
-{
-    if (numChunks() != newCount) {
-        AutoLockGCBgAlloc lock(runtime());
-        updateNumChunksLocked(newCount, lock);
-    }
+    MOZ_ASSERT(newCount < maxChunkCount());
+
+    if (newCount < allocatedChunkCount())
+        freeChunksFrom(newCount);
+
+    maxChunkCount_ = newCount;
 }
 
 void
-js::Nursery::updateNumChunksLocked(unsigned newCount,
-                                   AutoLockGCBgAlloc& lock)
+js::Nursery::minimizeAllocableSpace()
 {
-    // The GC nursery is an optimization and so if we fail to allocate nursery
-    // chunks we do not report an error.
-
-    MOZ_ASSERT(newCount <= maxChunks());
-
-    unsigned priorCount = numChunks();
-    MOZ_ASSERT(priorCount != newCount);
-
-    if (newCount < priorCount) {
-        // Shrink the nursery and free unused chunks.
-        for (unsigned i = newCount; i < priorCount; i++)
-            runtime()->gc.recycleChunk(chunk(i).toChunk(runtime()), lock);
-        chunks_.shrinkTo(newCount);
-        return;
-    }
-
-    // Grow the nursery and allocate new chunks.
-    if (!chunks_.resize(newCount))
-        return;
-
-    for (unsigned i = priorCount; i < newCount; i++) {
-        auto newChunk = runtime()->gc.getOrAllocChunk(lock);
-        if (!newChunk) {
-            chunks_.shrinkTo(i);
-            return;
-        }
-
-        chunks_[i] = NurseryChunk::fromChunk(newChunk);
-        chunk(i).poisonAndInit(runtime(), JS_FRESH_NURSERY_PATTERN);
-    }
+    shrinkAllocableSpace(1);
 }
 
 bool
 js::Nursery::queueDictionaryModeObjectToSweep(NativeObject* obj)
 {
     MOZ_ASSERT(IsInsideNursery(obj));
     return dictionaryModeObjects_.append(obj);
 }
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -136,25 +136,32 @@ class Nursery
     static const size_t Alignment = gc::ChunkSize;
     static const size_t ChunkShift = gc::ChunkShift;
 
     explicit Nursery(JSRuntime* rt);
     ~Nursery();
 
     MOZ_MUST_USE bool init(uint32_t maxNurseryBytes, AutoLockGCBgAlloc& lock);
 
-    unsigned maxChunks() const { return maxNurseryChunks_; }
-    unsigned numChunks() const { return chunks_.length(); }
+    unsigned chunkCountLimit() const { return chunkCountLimit_; }
+
+    // Number of allocated (ready to use) chunks.
+    unsigned allocatedChunkCount() const { return chunks_.length(); }
 
-    bool exists() const { return maxChunks() != 0; }
-    size_t nurserySize() const { return maxChunks() << ChunkShift; }
+    // Total number of chunks and the capacity of the nursery. Chunks will be
+    // lazilly allocated and added to the chunks array up to this limit, after
+    // that the nursery must be collected, this limit may be raised during
+    // collection.
+    unsigned maxChunkCount() const { return maxChunkCount_; }
+
+    bool exists() const { return chunkCountLimit() != 0; }
 
     void enable();
     void disable();
-    bool isEnabled() const { return numChunks() != 0; }
+    bool isEnabled() const { return maxChunkCount() != 0; }
 
     /* Return true if no allocations have been made since the last collection. */
     bool isEmpty() const;
 
     /*
      * Check whether an arbitrary pointer is within the nursery. This is
      * slower than IsInsideNursery(Cell*), but works on all types of pointers.
      */
@@ -229,17 +236,17 @@ class Nursery
         MOZ_ASSERT(IsInsideNursery(cell));
         MOZ_ASSERT(isEnabled());
         return cellsWithUid_.append(cell);
     }
 
     MOZ_MUST_USE bool queueDictionaryModeObjectToSweep(NativeObject* obj);
 
     size_t sizeOfHeapCommitted() const {
-        return numChunks() * gc::ChunkSize;
+        return allocatedChunkCount() * gc::ChunkSize;
     }
     size_t sizeOfMallocedBuffers(mozilla::MallocSizeOf mallocSizeOf) const {
         if (!mallocedBuffers.initialized())
             return 0;
         size_t total = 0;
         for (MallocedBuffersSet::Range r = mallocedBuffers.all(); !r.empty(); r.popFront())
             total += mallocSizeOf(r.front());
         total += mallocedBuffers.sizeOfExcludingThis(mallocSizeOf);
@@ -248,17 +255,17 @@ class Nursery
 
     // The number of bytes from the start position to the end of the nursery.
     size_t spaceToEnd() const;
 
     // Free space remaining, not counting chunk trailers.
     MOZ_ALWAYS_INLINE size_t freeSpace() const {
         MOZ_ASSERT(currentEnd_ - position_ <= NurseryChunkUsableSize);
         return (currentEnd_ - position_) +
-               (numChunks() - currentChunk_ - 1) * NurseryChunkUsableSize;
+               (maxChunkCount() - currentChunk_ - 1) * NurseryChunkUsableSize;
     }
 
 #ifdef JS_GC_ZEAL
     void enterZealMode();
     void leaveZealMode();
 #endif
 
     /* Write profile time JSON on JSONPrinter. */
@@ -306,18 +313,28 @@ class Nursery
     uintptr_t currentStartPosition_;
 
     /* Pointer to the last byte of space in the current chunk. */
     uintptr_t currentEnd_;
 
     /* The index of the chunk that is currently being allocated from. */
     unsigned currentChunk_;
 
-    /* Maximum number of chunks to allocate for the nursery. */
-    unsigned maxNurseryChunks_;
+    /*
+     * The nursery may grow the chunks_ vector up to this size without a
+     * collection.  This allows the nursery to grow lazilly.  This limit may
+     * change during maybeResizeNursery() each collection.
+     */
+    unsigned maxChunkCount_;
+
+    /*
+     * This limit is fixed by configuration.  It represents the maximum size
+     * the nursery is permitted to tune itself to in maybeResizeNursery();
+     */
+    unsigned chunkCountLimit_;
 
     /* Promotion rate for the previous minor collection. */
     float previousPromotionRate_;
 
     /* Report minor collections taking at least this long, if enabled. */
     mozilla::TimeDuration profileThreshold_;
     bool enableProfiling_;
 
@@ -414,28 +431,29 @@ class Nursery
     using NativeObjectVector = Vector<NativeObject*, 0, SystemAllocPolicy>;
     NativeObjectVector dictionaryModeObjects_;
 
 #ifdef JS_GC_ZEAL
     struct Canary;
     Canary* lastCanary_;
 #endif
 
-    NurseryChunk* allocChunk();
-
     NurseryChunk& chunk(unsigned index) const {
         return *chunks_[index];
     }
 
     void setCurrentChunk(unsigned chunkno);
     void setStartPosition();
 
-    void updateNumChunks(unsigned newCount);
-    void updateNumChunksLocked(unsigned newCount,
-                               AutoLockGCBgAlloc& lock);
+    /*
+     * Allocate the next chunk, or the first chunk for initialization.
+     * Callers will probably want to call setCurrentChunk(0) next.
+     */
+    MOZ_MUST_USE bool allocateNextChunk(unsigned chunkno,
+        AutoLockGCBgAlloc& lock);
 
     MOZ_ALWAYS_INLINE uintptr_t currentEnd() const;
 
     uintptr_t position() const { return position_; }
 
     JSRuntime* runtime() const { return runtime_; }
 
     /* Allocates a new GC thing from the tenured generation during minor GC. */
@@ -478,19 +496,23 @@ class Nursery
      */
     void clear();
 
     void sweepDictionaryModeObjects();
 
     /* Change the allocable space provided by the nursery. */
     void maybeResizeNursery(JS::gcreason::Reason reason);
     void growAllocableSpace();
-    void shrinkAllocableSpace(unsigned removeNumChunks);
+    void shrinkAllocableSpace(unsigned newCount);
     void minimizeAllocableSpace();
 
+    // Free the chunks starting at firstFreeChunk until the end of the chunks
+    // vector. Shrinks the vector but does not update maxChunkCount().
+    void freeChunksFrom(unsigned firstFreeChunk);
+
     /* Profile recording and printing. */
     void maybeClearProfileDurations();
     void startProfile(ProfileKey key);
     void endProfile(ProfileKey key);
     static void printProfileDurations(const ProfileDurations& times);
 
     friend class TenuringTracer;
     friend class gc::MinorCollectionTracer;
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -3258,17 +3258,17 @@ public:
   PrefChangedFunc mCallback;
   Preferences::MatchKind mMatchKind;
 };
 
 class ValueObserver final
   : public nsIObserver
   , public ValueObserverHashKey
 {
-  ~ValueObserver() { Preferences::RemoveObserver(this, mPrefName.get()); }
+  ~ValueObserver() = default;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   ValueObserver(const char* aPref,
                 PrefChangedFunc aCallback,
                 Preferences::MatchKind aMatchKind)
@@ -4676,18 +4676,17 @@ pref_InitInitialObjects()
   // on the "default" channel).
   bool developerBuild = false;
 #ifndef MOZILLA_OFFICIAL
   developerBuild = !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "default");
 #endif
 
   if (!strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "nightly") ||
       !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "aurora") ||
-      !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta") ||
-      developerBuild) {
+      !strcmp(NS_STRINGIFY(MOZ_UPDATE_CHANNEL), "beta") || developerBuild) {
     PREF_SetBoolPref(kTelemetryPref, true, true);
   } else {
     PREF_SetBoolPref(kTelemetryPref, false, true);
   }
   PREF_LockPref(kTelemetryPref, true);
 #endif // MOZ_WIDGET_ANDROID
 
   NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID,
@@ -4946,62 +4945,62 @@ NotifyObserver(const char* aPref, void* 
 {
   nsCOMPtr<nsIObserver> observer = static_cast<nsIObserver*>(aClosure);
   observer->Observe(nullptr,
                     NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
                     NS_ConvertASCIItoUTF16(aPref).get());
 }
 
 static void
-RegisterPriorityCallback(PrefChangedFunc aCallback,
-                         const char* aPref,
-                         void* aClosure)
-{
-  MOZ_ASSERT(Preferences::IsServiceAvailable());
-
-  ValueObserverHashKey hashKey(aPref, aCallback, Preferences::ExactMatch);
+RegisterCallbackHelper(PrefChangedFunc aCallback,
+                       const char* aPref,
+                       void* aClosure,
+                       Preferences::MatchKind aMatchKind,
+                       bool aIsPriority)
+{
+  ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind);
   RefPtr<ValueObserver> observer;
   gObserverTable->Get(&hashKey, getter_AddRefs(observer));
   if (observer) {
     observer->AppendClosure(aClosure);
     return;
   }
 
-  observer = new ValueObserver(aPref, aCallback, Preferences::ExactMatch);
+  observer = new ValueObserver(aPref, aCallback, aMatchKind);
   observer->AppendClosure(aClosure);
-  PREF_RegisterCallback(aPref,
-                        NotifyObserver,
-                        static_cast<nsIObserver*>(observer),
-                        /* isPriority */ true);
+  PREF_RegisterCallback(
+    aPref, NotifyObserver, static_cast<nsIObserver*>(observer), aIsPriority);
   gObserverTable->Put(observer, observer);
 }
 
+// RegisterVarCacheCallback uses high priority callbacks to ensure that cache
+// observers are called prior to ordinary pref observers. Doing this ensures
+// that ordinary observers will never get stale values from cache variables.
+static void
+RegisterVarCacheCallback(PrefChangedFunc aCallback,
+                         const char* aPref,
+                         void* aClosure)
+{
+  MOZ_ASSERT(Preferences::IsServiceAvailable());
+
+  RegisterCallbackHelper(
+    aCallback, aPref, aClosure, Preferences::ExactMatch, /* isPriority */ true);
+}
+
 /* static */ nsresult
 Preferences::RegisterCallback(PrefChangedFunc aCallback,
                               const char* aPref,
                               void* aClosure,
                               MatchKind aMatchKind)
 {
   MOZ_ASSERT(aCallback);
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
 
-  ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind);
-  RefPtr<ValueObserver> observer;
-  gObserverTable->Get(&hashKey, getter_AddRefs(observer));
-  if (observer) {
-    observer->AppendClosure(aClosure);
-    return NS_OK;
-  }
-
-  observer = new ValueObserver(aPref, aCallback, aMatchKind);
-  observer->AppendClosure(aClosure);
-  nsresult rv = AddStrongObserver(observer, aPref);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  gObserverTable->Put(observer, observer);
+  RegisterCallbackHelper(
+    aCallback, aPref, aClosure, aMatchKind, /* isPriority */ false);
   return NS_OK;
 }
 
 /* static */ nsresult
 Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback,
                                      const char* aPref,
                                      void* aClosure,
                                      MatchKind aMatchKind)
@@ -5032,25 +5031,24 @@ Preferences::UnregisterCallback(PrefChan
   gObserverTable->Get(&hashKey, getter_AddRefs(observer));
   if (!observer) {
     return NS_OK;
   }
 
   observer->RemoveClosure(aClosure);
   if (observer->HasNoClosures()) {
     // Delete the callback since its list of closures is empty.
+    MOZ_ALWAYS_SUCCEEDS(
+      PREF_UnregisterCallback(aPref, NotifyObserver, observer));
+
     gObserverTable->Remove(observer);
   }
   return NS_OK;
 }
 
-// We insert cache observers using RegisterPriorityCallback to ensure they are
-// called prior to ordinary pref observers. Doing this ensures that ordinary
-// observers will never get stale values from cache variables.
-
 static void
 BoolVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *static_cast<bool*>(cache->mCacheLocation) =
     Preferences::GetBool(aPref, cache->mDefaultValueBool);
 }
 
@@ -5071,17 +5069,17 @@ Preferences::AddBoolVarCache(bool* aCach
 #ifdef DEBUG
   AssertNotAlreadyCached("bool", aPref, aCache);
 #endif
   *aCache = GetBool(aPref, aDefault);
   CacheData* data = new CacheData();
   data->mCacheLocation = aCache;
   data->mDefaultValueBool = aDefault;
   CacheDataAppendElement(data);
-  RegisterPriorityCallback(BoolVarChanged, aPref, data);
+  RegisterVarCacheCallback(BoolVarChanged, aPref, data);
   return NS_OK;
 }
 
 static void
 IntVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *static_cast<int32_t*>(cache->mCacheLocation) =
@@ -5098,17 +5096,17 @@ Preferences::AddIntVarCache(int32_t* aCa
 #ifdef DEBUG
   AssertNotAlreadyCached("int", aPref, aCache);
 #endif
   *aCache = Preferences::GetInt(aPref, aDefault);
   CacheData* data = new CacheData();
   data->mCacheLocation = aCache;
   data->mDefaultValueInt = aDefault;
   CacheDataAppendElement(data);
-  RegisterPriorityCallback(IntVarChanged, aPref, data);
+  RegisterVarCacheCallback(IntVarChanged, aPref, data);
   return NS_OK;
 }
 
 static void
 UintVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
   *static_cast<uint32_t*>(cache->mCacheLocation) =
@@ -5125,17 +5123,17 @@ Preferences::AddUintVarCache(uint32_t* a
 #ifdef DEBUG
   AssertNotAlreadyCached("uint", aPref, aCache);
 #endif
   *aCache = Preferences::GetUint(aPref, aDefault);
   CacheData* data = new CacheData();
   data->mCacheLocation = aCache;
   data->mDefaultValueUint = aDefault;
   CacheDataAppendElement(data);
-  RegisterPriorityCallback(UintVarChanged, aPref, data);
+  RegisterVarCacheCallback(UintVarChanged, aPref, data);
   return NS_OK;
 }
 
 template<MemoryOrdering Order>
 static void
 AtomicUintVarChanged(const char* aPref, void* aClosure)
 {
   CacheData* cache = static_cast<CacheData*>(aClosure);
@@ -5154,17 +5152,17 @@ Preferences::AddAtomicUintVarCache(Atomi
 #ifdef DEBUG
   AssertNotAlreadyCached("uint", aPref, aCache);
 #endif
   *aCache = Preferences::GetUint(aPref, aDefault);
   CacheData* data = new CacheData();
   data->mCacheLocation = aCache;
   data->mDefaultValueUint = aDefault;
   CacheDataAppendElement(data);
-  RegisterPriorityCallback(AtomicUintVarChanged<Order>, aPref, data);
+  RegisterVarCacheCallback(AtomicUintVarChanged<Order>, aPref, data);
   return NS_OK;
 }
 
 // Since the definition of this template function is not in a header file, we
 // need to explicitly specify the instantiations that are required. Currently
 // only the order=Relaxed variant is needed.
 template nsresult
 Preferences::AddAtomicUintVarCache(Atomic<uint32_t, Relaxed>*,
@@ -5187,17 +5185,17 @@ Preferences::AddFloatVarCache(float* aCa
 #ifdef DEBUG
   AssertNotAlreadyCached("float", aPref, aCache);
 #endif
   *aCache = Preferences::GetFloat(aPref, aDefault);
   CacheData* data = new CacheData();
   data->mCacheLocation = aCache;
   data->mDefaultValueFloat = aDefault;
   CacheDataAppendElement(data);
-  RegisterPriorityCallback(FloatVarChanged, aPref, data);
+  RegisterVarCacheCallback(FloatVarChanged, aPref, data);
   return NS_OK;
 }
 
 /* static */ nsresult
 Preferences::GetDefaultBool(const char* aPref, bool* aResult)
 {
   NS_PRECONDITION(aResult, "aResult must not be NULL");
   NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE);
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -1333,16 +1333,18 @@ nsCookieService::TryInitDB(bool aRecreat
           NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER"));
         COOKIE_LOGSTRING(LogLevel::Debug,
           ("Upgraded database to schema version 9"));
       }
 
       // No more upgrades. Update the schema version.
       rv = mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION);
       NS_ENSURE_SUCCESS(rv, RESULT_RETRY);
+
+      Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_OLD_SCHEMA, dbSchemaVersion);
       MOZ_FALLTHROUGH;
 
     case COOKIES_SCHEMA_VERSION:
       break;
 
     case 0:
       {
         NS_WARNING("couldn't get schema version!");
@@ -1424,16 +1426,17 @@ nsCookieService::TryInitDB(bool aRecreat
       // Note that we have to switch out our DBState temporarily, in case we're in
       // private browsing mode; otherwise ImportCookies() won't be happy.
       DBState* initialState = gCookieService->mDBState;
       gCookieService->mDBState = gCookieService->mDefaultDBState;
       oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME));
       gCookieService->ImportCookies(oldCookieFile);
       oldCookieFile->Remove(false);
       gCookieService->mDBState = initialState;
+      Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_OLD_SCHEMA, 0);
     });
 
   NS_DispatchToMainThread(runnable);
 
   return RESULT_OK;
 }
 
 void
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4421,16 +4421,25 @@
   "MOZ_SQLITE_COOKIES_READ_MS": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "40",
     "kind": "exponential",
     "high": 3000,
     "n_buckets": 10,
     "description": "Time spent on SQLite read() (ms) *** No longer needed (bug 1156565). Delete histogram and accumulation code! ***"
   },
+  "MOZ_SQLITE_COOKIES_OLD_SCHEMA": {
+    "record_in_processes": ["main"],
+    "expires_in_version": "62",
+    "kind": "enumerated",
+    "n_values": 10,
+    "bug_numbers": [1412218],
+    "alert_emails": ["necko@mozilla.com", "junior@mozilla.com"],
+    "description": "Old schema version of the cookie database. 0 for having legacy cookies.txt."
+  },
   "MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS": {
     "record_in_processes": ["main"],
     "expires_in_version": "never",
     "kind": "exponential",
     "alert_emails": ["necko@mozilla.com", "junior@mozilla.com"],
     "bug_numbers": [870460],
     "high": 3000,
     "n_buckets": 10,