Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Fri, 21 Jun 2019 12:34:14 +0300
changeset 479795 516ca8e19a81cc9247188952f46b9dda59bb61a8
parent 479794 b8c319d5968f957d34e359829690614a9b69b95a (current diff)
parent 479712 27cb4e06f488659fe63397e9ab4569757d3723a4 (diff)
child 479796 0fda79b09a609514e50db8ca4f5e1ecf4b81001d
child 479806 1191d1e3716d05c90b665b43de49fbe8e7c9eaf6
push id113488
push usershindli@mozilla.com
push dateFri, 21 Jun 2019 09:45:11 +0000
treeherdermozilla-inbound@0fda79b09a60 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.0a1
first release with
nightly linux32
516ca8e19a81 / 69.0a1 / 20190621093603 / files
nightly linux64
516ca8e19a81 / 69.0a1 / 20190621093603 / files
nightly mac
516ca8e19a81 / 69.0a1 / 20190621093603 / files
nightly win32
516ca8e19a81 / 69.0a1 / 20190621093603 / files
nightly win64
516ca8e19a81 / 69.0a1 / 20190621093603 / 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 inbound to mozilla-central. a=merge
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1817,18 +1817,18 @@ nsEventStatus AsyncPanZoomController::On
         }
       }
     }
   }
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::HandleEndOfPan() {
-  MOZ_ASSERT(GetCurrentTouchBlock());
-  GetCurrentTouchBlock()->GetOverscrollHandoffChain()->FlushRepaints();
+  MOZ_ASSERT(GetCurrentTouchBlock() || GetCurrentPanGestureBlock());
+  GetCurrentInputBlock()->GetOverscrollHandoffChain()->FlushRepaints();
   ParentLayerPoint flingVelocity = GetVelocityVector();
 
   // Clear our velocities; if DispatchFling() gives the fling to us,
   // the fling velocity gets *added* to our existing velocity in
   // AcceptFling().
   mX.SetVelocity(0);
   mY.SetVelocity(0);
   // Clear our state so that we don't stay in the PANNING state
@@ -1840,29 +1840,29 @@ nsEventStatus AsyncPanZoomController::Ha
 
   APZC_LOG("%p starting a fling animation if %f >= %f\n", this,
            flingVelocity.Length().value,
            StaticPrefs::APZFlingMinVelocityThreshold());
 
   if (flingVelocity.Length() < StaticPrefs::APZFlingMinVelocityThreshold()) {
     // Relieve overscroll now if needed, since we will not transition to a fling
     // animation and then an overscroll animation, and relieve it then.
-    GetCurrentTouchBlock()
+    GetCurrentInputBlock()
         ->GetOverscrollHandoffChain()
         ->SnapBackOverscrolledApzc(this);
     return nsEventStatus_eConsumeNoDefault;
   }
 
   // Make a local copy of the tree manager pointer and check that it's not
   // null before calling DispatchFling(). This is necessary because Destroy(),
   // which nulls out mTreeManager, could be called concurrently.
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
     const FlingHandoffState handoffState{
-        flingVelocity, GetCurrentTouchBlock()->GetOverscrollHandoffChain(),
-        false /* not handoff */, GetCurrentTouchBlock()->GetScrolledApzc()};
+        flingVelocity, GetCurrentInputBlock()->GetOverscrollHandoffChain(),
+        false /* not handoff */, GetCurrentInputBlock()->GetScrolledApzc()};
     treeManagerLocal->DispatchFling(this, handoffState);
   }
   return nsEventStatus_eConsumeNoDefault;
 }
 
 bool AsyncPanZoomController::ConvertToGecko(const ScreenIntPoint& aPoint,
                                             LayoutDevicePoint* aOut) {
   if (APZCTreeManager* treeManagerLocal = GetApzcTreeManager()) {
@@ -2551,16 +2551,45 @@ nsEventStatus AsyncPanZoomController::On
   // Note that there is a multiplier that applies onto the "physical" pan
   // displacement (how much the user's fingers moved) that produces the
   // "logical" pan displacement (how much the page should move). For some of the
   // code below it makes more sense to use the physical displacement rather than
   // the logical displacement, and vice-versa.
   ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement;
   ParentLayerPoint logicalPanDisplacement =
       aEvent.UserMultipliedLocalPanDisplacement();
+  if (aEvent.mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+    // Pan events with page units are used by Gtk, so this replicates Gtk:
+    // https://gitlab.gnome.org/GNOME/gtk/blob/c734c7e9188b56f56c3a504abee05fa40c5475ac/gtk/gtkrange.c#L3065-3073
+    CSSSize pageScrollSize;
+    CSSToParentLayerScale2D zoom;
+    {
+      // Grab the lock to access the frame metrics.
+      RecursiveMutexAutoLock lock(mRecursiveMutex);
+      pageScrollSize = mScrollMetadata.GetPageScrollAmount() /
+                       Metrics().GetDevPixelsPerCSSPixel();
+      zoom = Metrics().GetZoom();
+    }
+    // scrollUnit* is in units of "ParentLayer pixels per page proportion"...
+    auto scrollUnitWidth = std::min(std::pow(pageScrollSize.width, 2.0 / 3.0),
+                                    pageScrollSize.width / 2.0) *
+                           zoom.xScale;
+    auto scrollUnitHeight = std::min(std::pow(pageScrollSize.height, 2.0 / 3.0),
+                                     pageScrollSize.height / 2.0) *
+                            zoom.yScale;
+    // ... and pan displacements are in units of "page proportion count"
+    // here, so the products of them and scrollUnit* are in ParentLayer pixels
+    ParentLayerPoint physicalPanDisplacementPL(
+        physicalPanDisplacement.x * scrollUnitWidth,
+        physicalPanDisplacement.y * scrollUnitHeight);
+    physicalPanDisplacement = ToScreenCoordinates(physicalPanDisplacementPL,
+        aEvent.mLocalPanStartPoint);
+    logicalPanDisplacement.x *= scrollUnitWidth;
+    logicalPanDisplacement.y *= scrollUnitHeight;
+  }
 
   MOZ_ASSERT(GetCurrentPanGestureBlock());
   AdjustDeltaForAllowedScrollDirections(
       logicalPanDisplacement,
       GetCurrentPanGestureBlock()->GetAllowedScrollDirections());
 
   // We need to update the axis velocity in order to get a useful display port
   // size and position. We need to do so even if this is a momentum pan (i.e.
@@ -2604,16 +2633,22 @@ nsEventStatus AsyncPanZoomController::On
   APZC_LOG("%p got a pan-end in state %d\n", this, mState);
 
   // Call into OnPan in order to process any delta included in this event.
   OnPan(aEvent, true);
 
   mX.EndTouch(aEvent.mTime);
   mY.EndTouch(aEvent.mTime);
 
+  // Use HandleEndOfPan for fling on platforms that don't
+  // emit momentum events (Gtk).
+  if (aEvent.mSimulateMomentum) {
+    return HandleEndOfPan();
+  }
+
   // Drop any velocity on axes where we don't have room to scroll anyways
   // (in this APZC, or an APZC further in the handoff chain).
   // This ensures that we don't enlarge the display port unnecessarily.
   MOZ_ASSERT(GetCurrentPanGestureBlock());
   RefPtr<const OverscrollHandoffChain> overscrollHandoffChain =
       GetCurrentPanGestureBlock()->GetOverscrollHandoffChain();
   if (!overscrollHandoffChain->CanScrollInDirection(
           this, ScrollDirection::eHorizontal)) {
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -162,35 +162,16 @@ struct ParamTraits<mozilla::layers::Comp
     WriteParam(msg, param.mHandle);
   }
   static bool Read(const Message* msg, PickleIterator* iter,
                    paramType* result) {
     return ReadParam(msg, iter, &result->mHandle);
   }
 };
 
-// Helper class for reading bitfields.
-// If T has bitfields members, derive ParamTraits<T> from BitfieldHelper<T>.
-template <typename ParamType>
-struct BitfieldHelper {
-  // We need this helper because we can't get the address of a bitfield to
-  // pass directly to ReadParam. So instead we read it into a temporary bool
-  // and set the bitfield using a setter function
-  static bool ReadBoolForBitfield(const Message* aMsg, PickleIterator* aIter,
-                                  ParamType* aResult,
-                                  void (ParamType::*aSetter)(bool)) {
-    bool value;
-    if (ReadParam(aMsg, aIter, &value)) {
-      (aResult->*aSetter)(value);
-      return true;
-    }
-    return false;
-  }
-};
-
 template <>
 struct ParamTraits<mozilla::layers::FrameMetrics>
     : BitfieldHelper<mozilla::layers::FrameMetrics> {
   typedef mozilla::layers::FrameMetrics paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mScrollId);
     WriteParam(aMsg, aParam.mPresShellResolution);
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -1083,11 +1083,30 @@ struct CrossOriginPolicyValidator {
   }
 };
 
 template <>
 struct ParamTraits<nsILoadInfo::CrossOriginPolicy>
     : EnumSerializer<nsILoadInfo::CrossOriginPolicy,
                      CrossOriginPolicyValidator> {};
 
+// Helper class for reading bitfields.
+// If T has bitfields members, derive ParamTraits<T> from BitfieldHelper<T>.
+template <typename ParamType>
+struct BitfieldHelper {
+  // We need this helper because we can't get the address of a bitfield to
+  // pass directly to ReadParam. So instead we read it into a temporary bool
+  // and set the bitfield using a setter function
+  static bool ReadBoolForBitfield(const Message* aMsg, PickleIterator* aIter,
+                                  ParamType* aResult,
+                                  void (ParamType::*aSetter)(bool)) {
+    bool value;
+    if (ReadParam(aMsg, aIter, &value)) {
+      (aResult->*aSetter)(value);
+      return true;
+    }
+    return false;
+  }
+};
+
 } /* namespace IPC */
 
 #endif /* __IPC_GLUE_IPCMESSAGEUTILS_H__ */
--- a/js/src/gc/GCEnum.h
+++ b/js/src/gc/GCEnum.h
@@ -107,17 +107,24 @@ enum class ZealMode {
   _(ModuleBindingMap)                      \
   _(BaselineScript)                        \
   _(IonScript)                             \
   _(ArgumentsData)                         \
   _(RareArgumentsData)                     \
   _(RegExpStatics)                         \
   _(RegExpSharedBytecode)                  \
   _(TypedArrayElements)                    \
-  _(TypeDescrTraceList)
+  _(TypeDescrTraceList)                    \
+  _(NativeIterator)                        \
+  _(JitScript)                             \
+  _(ObjectGroupAddendum)                   \
+  _(ScriptDebugScript)                     \
+  _(BreakpointSite)                        \
+  _(ForOfPIC)                              \
+  _(ForOfPICStub)
 
 #define JS_FOR_EACH_MEMORY_USE(_)  \
   JS_FOR_EACH_PUBLIC_MEMORY_USE(_) \
   JS_FOR_EACH_INTERNAL_MEMORY_USE(_)
 
 enum class MemoryUse : uint8_t {
 #define DEFINE_MEMORY_USE(Name) Name,
   JS_FOR_EACH_MEMORY_USE(DEFINE_MEMORY_USE)
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -696,17 +696,18 @@ void MemoryTracker::checkEmptyOnDestroy(
   }
 
   MOZ_ASSERT(ok);
 }
 
 inline bool MemoryTracker::allowMultipleAssociations(MemoryUse use) const {
   // For most uses only one association is possible for each GC thing. Allow a
   // one-to-many relationship only where necessary.
-  return use == MemoryUse::RegExpSharedBytecode;
+  return use == MemoryUse::RegExpSharedBytecode ||
+         use == MemoryUse::BreakpointSite || use == MemoryUse::ForOfPICStub;
 }
 
 void MemoryTracker::trackMemory(Cell* cell, size_t nbytes, MemoryUse use) {
   MOZ_ASSERT(cell->isTenured());
 
   LockGuard<Mutex> lock(mutex);
 
   Key key{cell, use};
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1826,17 +1826,17 @@ bool BaselineCacheIRCompiler::emitGuardA
   // Ensure the iterator is reusable: see NativeIterator::isReusable.
   masm.branchIfNativeIteratorNotReusable(niScratch, failure->label());
 
   // Pre-write barrier for store to 'objectBeingIterated_'.
   Address iterObjAddr(niScratch, NativeIterator::offsetOfObjectBeingIterated());
   EmitPreBarrier(masm, iterObjAddr, MIRType::Object);
 
   // Mark iterator as active.
-  Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlags());
+  Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlagsAndCount());
   masm.storePtr(obj, iterObjAddr);
   masm.or32(Imm32(NativeIterator::Flags::Active), iterFlagsAddr);
 
   // Post-write barrier for stores to 'objectBeingIterated_'.
   emitPostBarrierSlot(output,
                       TypedOrValueRegister(MIRType::Object, AnyRegister(obj)),
                       scratch1);
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -3140,17 +3140,17 @@ void jit::DestroyJitScripts(FreeOp* fop,
 
   if (script->hasBaselineScript()) {
     BaselineScript* baseline = script->baselineScript();
     script->clearBaselineScript();
     jit::BaselineScript::Destroy(fop, baseline);
   }
 
   if (script->hasJitScript()) {
-    JitScript::Destroy(script->zone(), script->jitScript());
+    script->releaseJitScript();
   }
 }
 
 void jit::TraceJitScripts(JSTracer* trc, JSScript* script) {
   if (script->hasIonScript()) {
     jit::IonScript::Trace(trc, script->ionScript());
   }
 
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -2205,17 +2205,17 @@ bool IonCacheIRCompiler::emitGuardAndGet
   // Ensure the iterator is reusable: see NativeIterator::isReusable.
   masm.branchIfNativeIteratorNotReusable(niScratch, failure->label());
 
   // Pre-write barrier for store to 'objectBeingIterated_'.
   Address iterObjAddr(niScratch, NativeIterator::offsetOfObjectBeingIterated());
   EmitPreBarrier(masm, iterObjAddr, MIRType::Object);
 
   // Mark iterator as active.
-  Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlags());
+  Address iterFlagsAddr(niScratch, NativeIterator::offsetOfFlagsAndCount());
   masm.storePtr(obj, iterObjAddr);
   masm.or32(Imm32(NativeIterator::Flags::Active), iterFlagsAddr);
 
   // Post-write barrier for stores to 'objectBeingIterated_'.
   emitPostBarrierSlot(output,
                       TypedOrValueRegister(MIRType::Object, AnyRegister(obj)),
                       scratch1);
 
--- a/js/src/jit/JitScript.cpp
+++ b/js/src/jit/JitScript.cpp
@@ -35,19 +35,20 @@ static size_t NumTypeSets(JSScript* scri
   if (JSFunction* fun = script->functionNonDelazifying()) {
     num += fun->nargs();
   }
 
   return num;
 }
 
 JitScript::JitScript(JSScript* script, uint32_t typeSetOffset,
-                     uint32_t bytecodeTypeMapOffset)
+                     uint32_t bytecodeTypeMapOffset, uint32_t allocBytes)
     : typeSetOffset_(typeSetOffset),
-      bytecodeTypeMapOffset_(bytecodeTypeMapOffset) {
+      bytecodeTypeMapOffset_(bytecodeTypeMapOffset),
+      allocBytes_(allocBytes) {
   setTypesGeneration(script->zone()->types.generation);
 
   uint8_t* base = reinterpret_cast<uint8_t*>(this);
   DefaultInitializeElements<StackTypeSet>(base + typeSetOffset, numTypeSets());
 }
 
 bool JSScript::createJitScript(JSContext* cx) {
   MOZ_ASSERT(!jitScript_);
@@ -93,34 +94,35 @@ bool JSScript::createJitScript(JSContext
   MOZ_ASSERT(uintptr_t(raw) % alignof(JitScript) == 0);
   if (!raw) {
     return false;
   }
 
   uint32_t typeSetOffset = sizeof(JitScript) + numICEntries() * sizeof(ICEntry);
   uint32_t bytecodeTypeMapOffset =
       typeSetOffset + numTypeSets * sizeof(StackTypeSet);
-  UniquePtr<JitScript> jitScript(
-      new (raw) JitScript(this, typeSetOffset, bytecodeTypeMapOffset));
+  UniquePtr<JitScript> jitScript(new (raw) JitScript(
+      this, typeSetOffset, bytecodeTypeMapOffset, allocSize.value()));
 
   // Sanity check the length computations.
   MOZ_ASSERT(jitScript->numICEntries() == numICEntries());
   MOZ_ASSERT(jitScript->numTypeSets() == numTypeSets);
 
   // We need to call prepareForDestruction on JitScript before we |delete| it.
   auto prepareForDestruction = mozilla::MakeScopeExit(
       [&] { jitScript->prepareForDestruction(cx->zone()); });
 
   if (!jitScript->initICEntriesAndBytecodeTypeMap(cx, this)) {
     return false;
   }
 
   MOZ_ASSERT(!jitScript_);
   prepareForDestruction.release();
   jitScript_ = jitScript.release();
+  AddCellMemory(this, allocSize.value(), MemoryUse::JitScript);
 
   // We have a JitScript so we can set the script's jitCodeRaw_ pointer to the
   // Baseline Interpreter code.
   updateJitCodeRaw(cx->runtime());
 
 #ifdef DEBUG
   AutoSweepJitScript sweep(this);
   StackTypeSet* typeArray = jitScript_->typeArrayDontCheckGeneration();
@@ -145,18 +147,24 @@ bool JSScript::createJitScript(JSContext
 }
 
 void JSScript::maybeReleaseJitScript() {
   if (!jitScript_ || zone()->types.keepJitScripts || hasBaselineScript() ||
       jitScript_->active()) {
     return;
   }
 
+  releaseJitScript();
+}
+
+void JSScript::releaseJitScript() {
   MOZ_ASSERT(!hasIonScript());
 
+  RemoveCellMemory(this, jitScript_->allocBytes(), MemoryUse::JitScript);
+
   JitScript::Destroy(zone(), jitScript_);
   jitScript_ = nullptr;
   updateJitCodeRaw(runtimeFromMainThread());
 }
 
 void JitScript::trace(JSTracer* trc) {
   // Mark all IC stub codes hanging off the IC stub entries.
   for (size_t i = 0; i < numICEntries(); i++) {
--- a/js/src/jit/JitScript.h
+++ b/js/src/jit/JitScript.h
@@ -110,16 +110,19 @@ class alignas(uintptr_t) JitScript final
 
   // Offset of the bytecode type map.
   uint32_t bytecodeTypeMapOffset_ = 0;
 
   // This field is used to avoid binary searches for the sought entry when
   // bytecode map queries are in linear order.
   uint32_t bytecodeTypeMapHint_ = 0;
 
+  // The size of this allocation.
+  uint32_t allocBytes_ = 0;
+
   struct Flags {
     // Flag set when discarding JIT code to indicate this script is on the stack
     // and type information and JIT code should not be discarded.
     bool active : 1;
 
     // Generation for type sweeping. If out of sync with the TypeZone's
     // generation, this JitScript needs to be swept.
     bool typesGeneration : 1;
@@ -142,17 +145,17 @@ class alignas(uintptr_t) JitScript final
   uint32_t typesGeneration() const { return uint32_t(flags_.typesGeneration); }
   void setTypesGeneration(uint32_t generation) {
     MOZ_ASSERT(generation <= 1);
     flags_.typesGeneration = generation;
   }
 
  public:
   JitScript(JSScript* script, uint32_t typeSetOffset,
-            uint32_t bytecodeTypeMapOffset);
+            uint32_t bytecodeTypeMapOffset, uint32_t allocBytes);
 
 #ifdef DEBUG
   ~JitScript() {
     // The contents of the fallback stub space are removed and freed
     // separately after the next minor GC. See prepareForDestruction.
     MOZ_ASSERT(fallbackStubSpace_.isEmpty());
   }
 #endif
@@ -322,16 +325,18 @@ class alignas(uintptr_t) JitScript final
   ICEntry& icEntryFromPCOffset(uint32_t pcOffset);
   ICEntry& icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry);
 
   MOZ_MUST_USE bool addDependentWasmImport(JSContext* cx,
                                            wasm::Instance& instance,
                                            uint32_t idx);
   void removeDependentWasmImport(wasm::Instance& instance, uint32_t idx);
   void unlinkDependentWasmImports();
+
+  size_t allocBytes() const { return allocBytes_; }
 };
 
 // Ensures no JitScripts are purged in the current zone.
 class MOZ_RAII AutoKeepJitScripts {
   TypeZone& zone_;
   bool prev_;
 
   AutoKeepJitScripts(const AutoKeepJitScripts&) = delete;
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3457,17 +3457,17 @@ void MacroAssembler::debugAssertObjHasFi
   assumeUnreachable("Expected a fixed slot");
   bind(&hasFixedSlots);
 #endif
 }
 
 void MacroAssembler::branchIfNativeIteratorNotReusable(Register ni,
                                                        Label* notReusable) {
   // See NativeIterator::isReusable.
-  Address flagsAddr(ni, NativeIterator::offsetOfFlags());
+  Address flagsAddr(ni, NativeIterator::offsetOfFlagsAndCount());
 
 #ifdef DEBUG
   Label niIsInitialized;
   branchTest32(Assembler::NonZero, flagsAddr,
                Imm32(NativeIterator::Flags::Initialized), &niIsInitialized);
   assumeUnreachable(
       "Expected a NativeIterator that's been completely "
       "initialized");
@@ -3525,17 +3525,17 @@ void MacroAssembler::iteratorMore(Regist
 }
 
 void MacroAssembler::iteratorClose(Register obj, Register temp1, Register temp2,
                                    Register temp3) {
   LoadNativeIterator(*this, obj, temp1);
 
   // Clear active bit.
   and32(Imm32(~NativeIterator::Flags::Active),
-        Address(temp1, NativeIterator::offsetOfFlags()));
+        Address(temp1, NativeIterator::offsetOfFlagsAndCount()));
 
   // Reset property cursor.
   loadPtr(Address(temp1, NativeIterator::offsetOfGuardsEnd()), temp2);
   storePtr(temp2, Address(temp1, NativeIterator::offsetOfPropertyCursor()));
 
   // Unlink from the iterator list.
   const Register next = temp2;
   const Register prev = temp3;
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -587,35 +587,43 @@ static PropertyIteratorObject* NewProper
   // CodeGenerator::visitIteratorStartO assumes the iterator object is not
   // inside the nursery when deciding whether a barrier is necessary.
   MOZ_ASSERT(!js::gc::IsInsideNursery(res));
 
   MOZ_ASSERT(res->numFixedSlots() == PropertyIteratorObject::NUM_FIXED_SLOTS);
   return res;
 }
 
+static inline size_t ExtraStringCount(size_t propertyCount, size_t guardCount) {
+  static_assert(sizeof(ReceiverGuard) == 2 * sizeof(GCPtrFlatString),
+                "NativeIterators are allocated in space for 1) themselves, "
+                "2) the properties a NativeIterator iterates (as "
+                "GCPtrFlatStrings), and 3) |numGuards| HeapReceiverGuard "
+                "objects; the additional-length calculation below assumes "
+                "this size-relationship when determining the extra space to "
+                "allocate");
+
+  return propertyCount + guardCount * 2;
+}
+
+static inline size_t AllocationSize(size_t propertyCount, size_t guardCount) {
+  return sizeof(NativeIterator) +
+         ExtraStringCount(propertyCount, guardCount) * sizeof(GCPtrFlatString);
+}
+
 static PropertyIteratorObject* CreatePropertyIterator(
     JSContext* cx, Handle<JSObject*> objBeingIterated, HandleIdVector props,
     uint32_t numGuards, uint32_t guardKey) {
   Rooted<PropertyIteratorObject*> propIter(cx, NewPropertyIteratorObject(cx));
   if (!propIter) {
     return nullptr;
   }
 
-  static_assert(sizeof(ReceiverGuard) == 2 * sizeof(GCPtrFlatString),
-                "NativeIterators are allocated in space for 1) themselves, "
-                "2) the properties a NativeIterator iterates (as "
-                "GCPtrFlatStrings), and 3) |numGuards| HeapReceiverGuard "
-                "objects; the additional-length calculation below assumes "
-                "this size-relationship when determining the extra space to "
-                "allocate");
-
-  size_t extraCount = props.length() + numGuards * 2;
-  void* mem =
-      cx->pod_malloc_with_extra<NativeIterator, GCPtrFlatString>(extraCount);
+  void* mem = cx->pod_malloc_with_extra<NativeIterator, GCPtrFlatString>(
+      ExtraStringCount(props.length(), numGuards));
   if (!mem) {
     return nullptr;
   }
 
   // This also registers |ni| with |propIter|.
   bool hadError = false;
   NativeIterator* ni = new (mem) NativeIterator(
       cx, propIter, objBeingIterated, props, numGuards, guardKey, &hadError);
@@ -675,24 +683,33 @@ NativeIterator::NativeIterator(JSContext
       // NativeIterator initially acts (before full initialization) as if it
       // contains no guards...
       guardsEnd_(guardsBegin()),
       // ...and no properties.
       propertyCursor_(
           reinterpret_cast<GCPtrFlatString*>(guardsBegin() + numGuards)),
       propertiesEnd_(propertyCursor_),
       guardKey_(guardKey),
-      flags_(0)  // note: no Flags::Initialized
+      flagsAndCount_(0)  // note: no Flags::Initialized
 {
   MOZ_ASSERT(!*hadError);
 
   // NOTE: This must be done first thing: PropertyIteratorObject::finalize
   //       can only free |this| (and not leak it) if this has happened.
   propIter->setNativeIterator(this);
 
+  if (!setInitialPropertyCount(props.length())) {
+    ReportAllocationOverflow(cx);
+    *hadError = true;
+    return;
+  }
+
+  size_t nbytes = AllocationSize(props.length(), numGuards);
+  AddCellMemory(propIter, nbytes, MemoryUse::NativeIterator);
+
   for (size_t i = 0, len = props.length(); i < len; i++) {
     JSFlatString* str = IdToString(cx, props[i]);
     if (!str) {
       *hadError = true;
       return;
     }
 
     // Placement-new the next property string at the end of the currently
@@ -749,16 +766,21 @@ NativeIterator::NativeIterator(JSContext
   // |guardsEnd_| is now guaranteed to point at the start of properties, so
   // we can mark this initialized.
   MOZ_ASSERT(static_cast<void*>(guardsEnd_) == propertyCursor_);
   markInitialized();
 
   MOZ_ASSERT(!*hadError);
 }
 
+inline size_t NativeIterator::allocationSize() const {
+  size_t numGuards = guardsEnd() - guardsBegin();
+  return AllocationSize(initialPropertyCount(), numGuards);
+}
+
 /* static */
 bool IteratorHashPolicy::match(PropertyIteratorObject* obj,
                                const Lookup& lookup) {
   NativeIterator* ni = obj->getNativeIterator();
   if (ni->guardKey() != lookup.key || ni->guardCount() != lookup.numGuards) {
     return false;
   }
 
@@ -1054,17 +1076,17 @@ void PropertyIteratorObject::trace(JSTra
           obj->as<PropertyIteratorObject>().getNativeIterator()) {
     ni->trace(trc);
   }
 }
 
 void PropertyIteratorObject::finalize(FreeOp* fop, JSObject* obj) {
   if (NativeIterator* ni =
           obj->as<PropertyIteratorObject>().getNativeIterator()) {
-    fop->free_(ni);
+    fop->free_(obj, ni, ni->allocationSize(), MemoryUse::NativeIterator);
   }
 }
 
 const ClassOps PropertyIteratorObject::classOps_ = {nullptr, /* addProperty */
                                                     nullptr, /* delProperty */
                                                     nullptr, /* enumerate */
                                                     nullptr, /* newEnumerate */
                                                     nullptr, /* resolve */
--- a/js/src/vm/Iteration.h
+++ b/js/src/vm/Iteration.h
@@ -82,17 +82,23 @@ struct NativeIterator {
     // currently reusable.  (An active |NativeIterator| can't be stolen
     // *right now*; a |NativeIterator| that's had its properties mutated
     // can never be reused, because it would give incorrect results.)
     static constexpr uint32_t NotReusable =
         Active | HasUnvisitedPropertyDeletion;
   };
 
  private:
-  uint32_t flags_ = 0;  // consists of Flags bits
+  static constexpr uint32_t FlagsBits = 3;
+  static constexpr uint32_t FlagsMask = (1 << FlagsBits) - 1;
+  static constexpr uint32_t PropCountLimit = 1 << (32 - FlagsBits);
+
+  // Stores Flags bits in the lower bits and the initial property count above
+  // them.
+  uint32_t flagsAndCount_ = 0;
 
   /* While in compartment->enumerators, these form a doubly linked list. */
   NativeIterator* next_ = nullptr;
   NativeIterator* prev_ = nullptr;
 
   // END OF PROPERTIES
 
   // No further fields appear after here *in NativeIterator*, but this class
@@ -218,58 +224,77 @@ struct NativeIterator {
 
   void incCursor() {
     MOZ_ASSERT(isInitialized());
     propertyCursor_++;
   }
 
   uint32_t guardKey() const { return guardKey_; }
 
-  bool isInitialized() const { return flags_ & Flags::Initialized; }
+  bool isInitialized() const { return flags() & Flags::Initialized; }
+
+  size_t allocationSize() const;
 
  private:
+  uint32_t flags() const { return flagsAndCount_ & FlagsMask; }
+
+  uint32_t initialPropertyCount() const { return flagsAndCount_ >> FlagsBits; }
+
+  void setFlags(uint32_t flags) {
+    MOZ_ASSERT((flags & ~FlagsMask) == 0);
+    flagsAndCount_ = (initialPropertyCount() << FlagsBits) | flags;
+  }
+
+  MOZ_MUST_USE bool setInitialPropertyCount(uint32_t count) {
+    if (count >= PropCountLimit) {
+      return false;
+    }
+    flagsAndCount_ = (count << FlagsBits) | flags();
+    return true;
+  }
+
   void markInitialized() {
-    MOZ_ASSERT(flags_ == 0);
-    flags_ = Flags::Initialized;
+    MOZ_ASSERT(flags() == 0);
+    setFlags(Flags::Initialized);
   }
 
  public:
   bool isActive() const {
     MOZ_ASSERT(isInitialized());
 
-    return flags_ & Flags::Active;
+    return flags() & Flags::Active;
   }
 
   void markActive() {
     MOZ_ASSERT(isInitialized());
 
-    flags_ |= Flags::Active;
+    flagsAndCount_ |= Flags::Active;
   }
 
   void markInactive() {
     MOZ_ASSERT(isInitialized());
 
-    flags_ &= ~Flags::Active;
+    flagsAndCount_ &= ~Flags::Active;
   }
 
   bool isReusable() const {
     MOZ_ASSERT(isInitialized());
 
     // Cached NativeIterators are reusable if they're not currently active
     // and their properties array hasn't been mutated, i.e. if only
     // |Flags::Initialized| is set.  Using |Flags::NotReusable| to test
     // would also work, but this formulation is safer against memory
     // corruption.
-    return flags_ == Flags::Initialized;
+    return flags() == Flags::Initialized;
   }
 
   void markHasUnvisitedPropertyDeletion() {
     MOZ_ASSERT(isInitialized());
 
-    flags_ |= Flags::HasUnvisitedPropertyDeletion;
+    flagsAndCount_ |= Flags::HasUnvisitedPropertyDeletion;
   }
 
   void link(NativeIterator* other) {
     // The NativeIterator sentinel doesn't have to be linked, because it's
     // the start of the list.  Anything else added should have been
     // initialized.
     MOZ_ASSERT(isInitialized());
 
@@ -305,18 +330,18 @@ struct NativeIterator {
   static constexpr size_t offsetOfPropertyCursor() {
     return offsetof(NativeIterator, propertyCursor_);
   }
 
   static constexpr size_t offsetOfPropertiesEnd() {
     return offsetof(NativeIterator, propertiesEnd_);
   }
 
-  static constexpr size_t offsetOfFlags() {
-    return offsetof(NativeIterator, flags_);
+  static constexpr size_t offsetOfFlagsAndCount() {
+    return offsetof(NativeIterator, flagsAndCount_);
   }
 
   static constexpr size_t offsetOfNext() {
     return offsetof(NativeIterator, next_);
   }
 
   static constexpr size_t offsetOfPrev() {
     return offsetof(NativeIterator, prev_);
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -4652,34 +4652,35 @@ DebugScript* JSScript::releaseDebugScrip
   clearFlag(MutableFlags::HasDebugScript);
   return debug;
 }
 
 void JSScript::destroyDebugScript(FreeOp* fop) {
   if (hasDebugScript()) {
 #ifdef DEBUG
     for (jsbytecode* pc = code(); pc < codeEnd(); pc++) {
-      if (BreakpointSite* site = getBreakpointSite(pc)) {
-        /* Breakpoints are swept before finalization. */
-        MOZ_ASSERT(site->firstBreakpoint() == nullptr);
-        MOZ_ASSERT(getBreakpointSite(pc) == nullptr);
-      }
+      MOZ_ASSERT(!getBreakpointSite(pc));
     }
 #endif
-    fop->free_(releaseDebugScript());
-  }
+    freeDebugScript(fop);
+  }
+}
+
+void JSScript::freeDebugScript(FreeOp* fop) {
+  MOZ_ASSERT(hasDebugScript());
+  fop->free_(this, releaseDebugScript(), DebugScript::allocSize(length()),
+             MemoryUse::ScriptDebugScript);
 }
 
 DebugScript* JSScript::getOrCreateDebugScript(JSContext* cx) {
   if (hasDebugScript()) {
     return debugScript();
   }
 
-  size_t nbytes =
-      offsetof(DebugScript, breakpoints) + length() * sizeof(BreakpointSite*);
+  size_t nbytes = DebugScript::allocSize(length());
   UniqueDebugScript debug(
       reinterpret_cast<DebugScript*>(cx->pod_calloc<uint8_t>(nbytes)));
   if (!debug) {
     return nullptr;
   }
 
   /* Create realm's debugScriptMap if necessary. */
   if (!realm()->debugScriptMap) {
@@ -4694,16 +4695,17 @@ DebugScript* JSScript::getOrCreateDebugS
   DebugScript* borrowed = debug.get();
   if (!realm()->debugScriptMap->putNew(this, std::move(debug))) {
     ReportOutOfMemory(cx);
     return nullptr;
   }
 
   setFlag(MutableFlags::HasDebugScript);  // safe to set this;  we can't fail
                                           // after this point
+  AddCellMemory(this, nbytes, MemoryUse::ScriptDebugScript);
 
   /*
    * Ensure that any Interpret() instances running on this script have
    * interrupts enabled. The interrupts must stay enabled until the
    * debug state is destroyed.
    */
   for (ActivationIterator iter(cx); !iter.done(); ++iter) {
     if (iter->isInterpreter()) {
@@ -4739,17 +4741,17 @@ bool JSScript::incrementGeneratorObserve
 void JSScript::decrementGeneratorObserverCount(js::FreeOp* fop) {
   DebugScript* debug = debugScript();
   MOZ_ASSERT(debug);
   MOZ_ASSERT(debug->generatorObserverCount > 0);
 
   debug->generatorObserverCount--;
 
   if (!debug->needed()) {
-    fop->free_(releaseDebugScript());
+    destroyDebugScript(fop);
   }
 }
 
 bool JSScript::incrementStepperCount(JSContext* cx) {
   cx->check(this);
   MOZ_ASSERT(cx->realm()->isDebuggee());
 
   AutoRealm ar(cx, this);
@@ -4778,17 +4780,17 @@ void JSScript::decrementStepperCount(Fre
   debug->stepperCount--;
 
   if (debug->stepperCount == 0) {
     if (hasBaselineScript()) {
       baseline->toggleDebugTraps(this, nullptr);
     }
 
     if (!debug->needed()) {
-      fop->free_(releaseDebugScript());
+      freeDebugScript(fop);
     }
   }
 }
 
 BreakpointSite* JSScript::getOrCreateBreakpointSite(JSContext* cx,
                                                     jsbytecode* pc) {
   AutoRealm ar(cx, this);
 
@@ -4800,32 +4802,35 @@ BreakpointSite* JSScript::getOrCreateBre
   BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)];
 
   if (!site) {
     site = cx->new_<JSBreakpointSite>(this, pc);
     if (!site) {
       return nullptr;
     }
     debug->numSites++;
+    AddCellMemory(this, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite);
   }
 
   return site;
 }
 
 void JSScript::destroyBreakpointSite(FreeOp* fop, jsbytecode* pc) {
   DebugScript* debug = debugScript();
   BreakpointSite*& site = debug->breakpoints[pcToOffset(pc)];
   MOZ_ASSERT(site);
 
+  RemoveCellMemory(this, sizeof(JSBreakpointSite), MemoryUse::BreakpointSite);
+
   fop->delete_(site);
   site = nullptr;
 
   debug->numSites--;
   if (!debug->needed()) {
-    fop->free_(releaseDebugScript());
+    freeDebugScript(fop);
   }
 }
 
 void JSScript::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg,
                                   JSObject* handler) {
   if (!hasAnyBreakpointsOrStepMode()) {
     return;
   }
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -293,16 +293,21 @@ class DebugScript {
 
   /*
    * True if this DebugScript carries any useful information. If false, it
    * should be removed from its JSScript.
    */
   bool needed() const {
     return generatorObserverCount > 0 || stepperCount > 0 || numSites > 0;
   }
+
+  static size_t allocSize(size_t codeLength) {
+    return offsetof(DebugScript, breakpoints) +
+           codeLength * sizeof(BreakpointSite*);
+  }
 };
 
 using UniqueDebugScript = js::UniquePtr<DebugScript, JS::FreePolicy>;
 using DebugScriptMap = HashMap<JSScript*, UniqueDebugScript,
                                DefaultHasher<JSScript*>, SystemAllocPolicy>;
 
 class ScriptSource;
 
@@ -2656,16 +2661,17 @@ class JSScript : public js::gc::TenuredC
 
   /* Ensure the script has a JitScript. */
   inline bool ensureHasJitScript(JSContext* cx, js::jit::AutoKeepJitScripts&);
 
   bool hasJitScript() const { return jitScript_ != nullptr; }
   js::jit::JitScript* jitScript() { return jitScript_; }
 
   void maybeReleaseJitScript();
+  void releaseJitScript();
 
   inline js::GlobalObject& global() const;
   inline bool hasGlobal(const js::GlobalObject* global) const;
   js::GlobalObject& uninlinedGlobal() const;
 
   uint32_t bodyScopeIndex() const { return scriptData_->bodyScopeIndex; }
 
   js::Scope* bodyScope() const { return getScope(bodyScopeIndex()); }
@@ -2953,16 +2959,17 @@ class JSScript : public js::gc::TenuredC
  private:
   /* Change this->stepMode to |newValue|. */
   void setNewStepMode(js::FreeOp* fop, uint32_t newValue);
 
   js::DebugScript* getOrCreateDebugScript(JSContext* cx);
   js::DebugScript* debugScript();
   js::DebugScript* releaseDebugScript();
   void destroyDebugScript(js::FreeOp* fop);
+  void freeDebugScript(js::FreeOp* fop);
 
   bool hasDebugScript() const { return hasFlag(MutableFlags::HasDebugScript); }
 
  public:
   bool hasBreakpointsAt(jsbytecode* pc);
   bool hasAnyBreakpointsOrStepMode() { return hasDebugScript(); }
 
   // See comment above 'debugMode' in Realm.h for explanation of
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -43,24 +43,26 @@ ObjectGroup::ObjectGroup(const Class* cl
   /* Windows may not appear on prototype chains. */
   MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
   MOZ_ASSERT(JS::StringIsASCII(clasp->name));
 
   setGeneration(zone()->types.generation);
 }
 
 void ObjectGroup::finalize(FreeOp* fop) {
-  if (newScriptDontCheckGeneration()) {
-    newScriptDontCheckGeneration()->clear();
+  if (auto newScript = newScriptDontCheckGeneration()) {
+    newScript->clear();
+    fop->delete_(this, newScript, newScript->gcMallocBytes(),
+                 MemoryUse::ObjectGroupAddendum);
   }
-  fop->delete_(newScriptDontCheckGeneration());
   if (maybePreliminaryObjectsDontCheckGeneration()) {
     maybePreliminaryObjectsDontCheckGeneration()->clear();
   }
-  fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
+  fop->delete_(this, maybePreliminaryObjectsDontCheckGeneration(),
+               MemoryUse::ObjectGroupAddendum);
 }
 
 void ObjectGroup::setProtoUnchecked(TaggedProto proto) {
   proto_ = proto;
   MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(),
                 proto_.toObject()->isDelegate());
 }
 
@@ -73,21 +75,37 @@ size_t ObjectGroup::sizeOfExcludingThis(
     mozilla::MallocSizeOf mallocSizeOf) const {
   size_t n = 0;
   if (TypeNewScript* newScript = newScriptDontCheckGeneration()) {
     n += newScript->sizeOfIncludingThis(mallocSizeOf);
   }
   return n;
 }
 
+static inline size_t AddendumAllocSize(ObjectGroup::AddendumKind kind,
+                                       void* addendum) {
+  if (kind == ObjectGroup::Addendum_NewScript) {
+    auto newScript = static_cast<TypeNewScript*>(addendum);
+    return newScript->gcMallocBytes();
+  }
+  if (kind == ObjectGroup::Addendum_PreliminaryObjects) {
+    return sizeof(PreliminaryObjectArrayWithTemplate);
+  }
+  // Other addendum kinds point to GC memory tracked elsewhere.
+  return 0;
+}
+
 void ObjectGroup::setAddendum(AddendumKind kind, void* addendum,
                               bool writeBarrier /* = true */) {
   MOZ_ASSERT(!needsSweep());
   MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT));
 
+  RemoveCellMemory(this, AddendumAllocSize(addendumKind(), addendum_),
+                   MemoryUse::ObjectGroupAddendum);
+
   if (writeBarrier) {
     // Manually trigger barriers if we are clearing new script or
     // preliminary object information. Other addendums are immutable.
     AutoSweepObjectGroup sweep(this);
     switch (addendumKind()) {
       case Addendum_PreliminaryObjects:
         PreliminaryObjectArrayWithTemplate::writeBarrierPre(
             maybePreliminaryObjects(sweep));
@@ -100,16 +118,19 @@ void ObjectGroup::setAddendum(AddendumKi
       default:
         MOZ_ASSERT(addendumKind() == kind);
     }
   }
 
   flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK;
   flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT;
   addendum_ = addendum;
+
+  AddCellMemory(this, AddendumAllocSize(kind, addendum),
+                MemoryUse::ObjectGroupAddendum);
 }
 
 /* static */
 bool ObjectGroup::useSingletonForClone(JSFunction* fun) {
   if (!fun->isInterpreted()) {
     return false;
   }
 
--- a/js/src/vm/PIC.cpp
+++ b/js/src/vm/PIC.cpp
@@ -14,16 +14,35 @@
 #include "vm/Realm.h"
 #include "vm/SelfHosting.h"
 
 #include "vm/JSObject-inl.h"
 #include "vm/NativeObject-inl.h"
 
 using namespace js;
 
+template <typename Category>
+void PICChain<Category>::addStub(JSObject* obj, CatStub* stub) {
+  MOZ_ASSERT(stub);
+  MOZ_ASSERT(!stub->next());
+
+  AddCellMemory(obj, sizeof(CatStub), MemoryUse::ForOfPICStub);
+
+  if (!stubs_) {
+    stubs_ = stub;
+    return;
+  }
+
+  CatStub* cur = stubs_;
+  while (cur->next()) {
+    cur = cur->next();
+  }
+  cur->append(stub);
+}
+
 bool js::ForOfPIC::Chain::initialize(JSContext* cx) {
   MOZ_ASSERT(!initialized_);
 
   // Get the canonical Array.prototype
   RootedNativeObject arrayProto(
       cx, GlobalObject::getOrCreateArrayPrototype(cx, cx->global()));
   if (!arrayProto) {
     return false;
@@ -101,17 +120,17 @@ bool js::ForOfPIC::Chain::tryOptimizeArr
   if (!initialized_) {
     // If PIC is not initialized, initialize it.
     if (!initialize(cx)) {
       return false;
     }
 
   } else if (!disabled_ && !isArrayStateStillSane()) {
     // Otherwise, if array state is no longer sane, reinitialize.
-    reset();
+    reset(cx);
 
     if (!initialize(cx)) {
       return false;
     }
   }
   MOZ_ASSERT(initialized_);
 
   // If PIC is disabled, don't bother trying to optimize.
@@ -137,28 +156,28 @@ bool js::ForOfPIC::Chain::tryOptimizeArr
   if (array->lookup(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator))) {
     return true;
   }
 
   // If the number of stubs is about to exceed the limit, throw away entire
   // existing cache before adding new stubs.  We shouldn't really have heavy
   // churn on these.
   if (numStubs() >= MAX_STUBS) {
-    eraseChain();
+    eraseChain(cx);
   }
 
   // Good to optimize now, create stub to add.
   RootedShape shape(cx, array->lastProperty());
   Stub* stub = cx->new_<Stub>(shape);
   if (!stub) {
     return false;
   }
 
   // Add the stub.
-  addStub(stub);
+  addStub(picObject_, stub);
 
   *optimized = true;
   return true;
 }
 
 bool js::ForOfPIC::Chain::tryOptimizeArrayIteratorNext(JSContext* cx,
                                                        bool* optimized) {
   MOZ_ASSERT(optimized);
@@ -167,17 +186,17 @@ bool js::ForOfPIC::Chain::tryOptimizeArr
 
   if (!initialized_) {
     // If PIC is not initialized, initialize it.
     if (!initialize(cx)) {
       return false;
     }
   } else if (!disabled_ && !isArrayNextStillSane()) {
     // Otherwise, if array iterator state is no longer sane, reinitialize.
-    reset();
+    reset(cx);
 
     if (!initialize(cx)) {
       return false;
     }
   }
   MOZ_ASSERT(initialized_);
 
   // If PIC is disabled, don't bother trying to optimize.
@@ -217,92 +236,91 @@ bool js::ForOfPIC::Chain::isArrayStateSt
   if (arrayProto_->getSlot(arrayProtoIteratorSlot_) != canonicalIteratorFunc_) {
     return false;
   }
 
   // Chain to isArrayNextStillSane.
   return isArrayNextStillSane();
 }
 
-void js::ForOfPIC::Chain::reset() {
+void js::ForOfPIC::Chain::reset(JSContext* cx) {
   // Should never reset a disabled_ stub.
   MOZ_ASSERT(!disabled_);
 
   // Erase the chain.
-  eraseChain();
+  eraseChain(cx);
 
   arrayProto_ = nullptr;
   arrayIteratorProto_ = nullptr;
 
   arrayProtoShape_ = nullptr;
   arrayProtoIteratorSlot_ = -1;
   canonicalIteratorFunc_ = UndefinedValue();
 
   arrayIteratorProtoShape_ = nullptr;
   arrayIteratorProtoNextSlot_ = -1;
   canonicalNextFunc_ = UndefinedValue();
 
   initialized_ = false;
 }
 
-void js::ForOfPIC::Chain::eraseChain() {
+void js::ForOfPIC::Chain::eraseChain(JSContext* cx) {
   // Should never need to clear the chain of a disabled stub.
   MOZ_ASSERT(!disabled_);
-
-  // Free all stubs.
-  Stub* stub = stubs_;
-  while (stub) {
-    Stub* next = stub->next();
-    js_delete(stub);
-    stub = next;
-  }
-  stubs_ = nullptr;
+  freeAllStubs(cx->defaultFreeOp());
 }
 
 // Trace the pointers stored directly on the stub.
 void js::ForOfPIC::Chain::trace(JSTracer* trc) {
+  TraceEdge(trc, &picObject_, "ForOfPIC object");
+
   if (!initialized_ || disabled_) {
     return;
   }
 
   TraceEdge(trc, &arrayProto_, "ForOfPIC Array.prototype.");
   TraceEdge(trc, &arrayIteratorProto_, "ForOfPIC ArrayIterator.prototype.");
 
   TraceEdge(trc, &arrayProtoShape_, "ForOfPIC Array.prototype shape.");
   TraceEdge(trc, &arrayIteratorProtoShape_,
             "ForOfPIC ArrayIterator.prototype shape.");
 
   TraceEdge(trc, &canonicalIteratorFunc_, "ForOfPIC ArrayValues builtin.");
   TraceEdge(trc, &canonicalNextFunc_,
             "ForOfPIC ArrayIterator.prototype.next builtin.");
 
-  // Free all the stubs in the chain.
-  while (stubs_) {
-    removeStub(stubs_, nullptr);
+  if (trc->isMarkingTracer()) {
+    // Free all the stubs in the chain.
+    freeAllStubs(trc->runtime()->defaultFreeOp());
   }
 }
 
-void js::ForOfPIC::Chain::sweep(FreeOp* fop) {
-  // Free all the stubs in the chain.
-  while (stubs_) {
-    Stub* next = stubs_->next();
-    fop->delete_(stubs_);
-    stubs_ = next;
-  }
-  fop->delete_(this);
-}
-
 static void ForOfPIC_finalize(FreeOp* fop, JSObject* obj) {
   MOZ_ASSERT(fop->maybeOnHelperThread());
   if (ForOfPIC::Chain* chain =
           ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
-    chain->sweep(fop);
+    chain->finalize(fop, obj);
   }
 }
 
+void js::ForOfPIC::Chain::finalize(FreeOp* fop, JSObject* obj) {
+  freeAllStubs(fop);
+  fop->delete_(obj, this, MemoryUse::ForOfPIC);
+}
+
+void js::ForOfPIC::Chain::freeAllStubs(FreeOp* fop) {
+  Stub* stub = stubs_;
+  while (stub) {
+    Stub* next = stub->next();
+    fop->delete_(picObject_, stub, MemoryUse::ForOfPICStub);
+    stub = next;
+  }
+  stubs_ = nullptr;
+}
+
 static void ForOfPIC_traceObject(JSTracer* trc, JSObject* obj) {
   if (ForOfPIC::Chain* chain =
           ForOfPIC::fromJSObject(&obj->as<NativeObject>())) {
     chain->trace(trc);
   }
 }
 
 static const ClassOps ForOfPICClassOps = {nullptr,
@@ -325,20 +343,21 @@ const Class ForOfPIC::class_ = {
 NativeObject* js::ForOfPIC::createForOfPICObject(JSContext* cx,
                                                  Handle<GlobalObject*> global) {
   cx->check(global);
   NativeObject* obj = NewNativeObjectWithGivenProto(cx, &ForOfPIC::class_,
                                                     nullptr, TenuredObject);
   if (!obj) {
     return nullptr;
   }
-  ForOfPIC::Chain* chain = cx->new_<ForOfPIC::Chain>();
+  ForOfPIC::Chain* chain = cx->new_<ForOfPIC::Chain>(obj);
   if (!chain) {
     return nullptr;
   }
+  InitObjectPrivate(obj, chain, MemoryUse::ForOfPIC);
   obj->setPrivate(chain);
   return obj;
 }
 
 /* static */ js::ForOfPIC::Chain* js::ForOfPIC::create(JSContext* cx) {
   MOZ_ASSERT(!cx->global()->getForOfPICObject());
   Rooted<GlobalObject*> global(cx, cx->global());
   NativeObject* obj = GlobalObject::getOrCreateForOfPICObject(cx, global);
--- a/js/src/vm/PIC.h
+++ b/js/src/vm/PIC.h
@@ -59,49 +59,25 @@ class PICChain {
 
   PICChain() : stubs_(nullptr) {}
   // PICs should never be copy constructed.
   PICChain(const PICChain<Category>& other) = delete;
 
  public:
   CatStub* stubs() const { return stubs_; }
 
-  void addStub(CatStub* stub) {
-    MOZ_ASSERT(stub);
-    MOZ_ASSERT(!stub->next());
-    if (!stubs_) {
-      stubs_ = stub;
-      return;
-    }
-
-    CatStub* cur = stubs_;
-    while (cur->next()) {
-      cur = cur->next();
-    }
-    cur->append(stub);
-  }
+  void addStub(JSObject* obj, CatStub* stub);
 
   unsigned numStubs() const {
     unsigned count = 0;
     for (CatStub* stub = stubs_; stub; stub = stub->next()) {
       count++;
     }
     return count;
   }
-
-  void removeStub(CatStub* stub, CatStub* previous) {
-    if (previous) {
-      MOZ_ASSERT(previous->next() == stub);
-      previous->next_ = stub->next();
-    } else {
-      MOZ_ASSERT(stub == stubs_);
-      stubs_ = stub->next();
-    }
-    js_delete(stub);
-  }
 };
 
 /*
  *  ForOfPIC defines a PIC category for optimizing for-of operations.
  */
 struct ForOfPIC {
   /* Forward declarations so template-substitution works. */
   class Stub;
@@ -153,16 +129,19 @@ struct ForOfPIC {
    *  ArrayIterator.prototype's slot number for 'next'
    *  ArrayIterator.prototype's canonical value for 'next'
    *      (arrayIteratorProtoNextSlot_, canonicalNextFunc_)
    *      To quickly retrieve and ensure that the 'next' method for
    *      ArrayIterator objects has not changed.
    */
   class Chain : public BaseChain {
    private:
+    // Pointer to owning JSObject for memory accounting purposes.
+    GCPtrObject picObject_;
+
     // Pointer to canonical Array.prototype and ArrayIterator.prototype
     GCPtrNativeObject arrayProto_;
     GCPtrNativeObject arrayIteratorProto_;
 
     // Shape of matching Array.prototype object, and slot containing
     // the @@iterator for it, and the canonical value.
     GCPtrShape arrayProtoShape_;
     uint32_t arrayProtoIteratorSlot_;
@@ -179,18 +158,19 @@ struct ForOfPIC {
 
     // Disabled flag is set when we don't want to try optimizing anymore
     // because core objects were changed.
     bool disabled_;
 
     static const unsigned MAX_STUBS = 10;
 
    public:
-    Chain()
+    explicit Chain(JSObject* picObject)
         : BaseChain(),
+          picObject_(picObject),
           arrayProto_(nullptr),
           arrayIteratorProto_(nullptr),
           arrayProtoShape_(nullptr),
           arrayProtoIteratorSlot_(-1),
           canonicalIteratorFunc_(UndefinedValue()),
           arrayIteratorProtoShape_(nullptr),
           arrayIteratorProtoNextSlot_(-1),
           initialized_(false),
@@ -202,17 +182,17 @@ struct ForOfPIC {
     // Try to optimize this chain for an object.
     bool tryOptimizeArray(JSContext* cx, HandleArrayObject array,
                           bool* optimized);
 
     // Check if %ArrayIteratorPrototype% still uses the default "next" method.
     bool tryOptimizeArrayIteratorNext(JSContext* cx, bool* optimized);
 
     void trace(JSTracer* trc);
-    void sweep(FreeOp* fop);
+    void finalize(FreeOp* fop, JSObject* obj);
 
    private:
     // Check if the global array-related objects have not been messed with
     // in a way that would disable this PIC.
     bool isArrayStateStillSane();
 
     // Check if ArrayIterator.next is still optimizable.
     inline bool isArrayNextStillSane() {
@@ -221,20 +201,22 @@ struct ForOfPIC {
              (arrayIteratorProto_->getSlot(arrayIteratorProtoNextSlot_) ==
               canonicalNextFunc_);
     }
 
     // Check if a matching optimized stub for the given object exists.
     bool hasMatchingStub(ArrayObject* obj);
 
     // Reset the PIC and all info associated with it.
-    void reset();
+    void reset(JSContext* cx);
 
     // Erase the stub chain.
-    void eraseChain();
+    void eraseChain(JSContext* cx);
+
+    void freeAllStubs(FreeOp* fop);
   };
 
   // Class for object that holds ForOfPIC chain.
   static const Class class_;
 
   static NativeObject* createForOfPICObject(JSContext* cx,
                                             Handle<GlobalObject*> global);
 
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -277,16 +277,22 @@ static inline const char* TypeIdString(j
 // be able to identify definite properties in this case.
 //
 // This layered approach is designed to maximize performance on easily
 // analyzable code, while still allowing us to determine definite properties
 // robustly when code consistently adds the same properties to objects, but in
 // complex ways which can't be understood statically.
 class TypeNewScript {
  private:
+  // Variable-length list of TypeNewScriptInitializer pointers.
+  struct InitializerList {
+    size_t length;
+    TypeNewScriptInitializer entries[1];
+  };
+
   // Scripted function which this information was computed for.
   HeapPtr<JSFunction*> function_ = {};
 
   // Any preliminary objects with the type. The analyses are not performed
   // until this array is cleared.
   PreliminaryObjectArray* preliminaryObjects = nullptr;
 
   // After the new script properties analyses have been performed, a template
@@ -298,17 +304,17 @@ class TypeNewScript {
   // Order in which definite properties become initialized. We need this in
   // case the definite properties are invalidated (such as by adding a setter
   // to an object on the prototype chain) while an object is in the middle of
   // being initialized, so we can walk the stack and fixup any objects which
   // look for in-progress objects which were prematurely set with an incorrect
   // shape. Property assignments in inner frames are preceded by a series of
   // SETPROP_FRAME entries specifying the stack down to the frame containing
   // the write.
-  TypeNewScriptInitializer* initializerList = nullptr;
+  InitializerList* initializerList = nullptr;
 
   // If there are additional properties found by the acquired properties
   // analysis which were not found by the definite properties analysis, this
   // shape contains all such additional properties (plus the definite
   // properties). When an object of this group acquires this shape, it is
   // fully initialized and its group can be changed to initializedGroup.
   HeapPtr<Shape*> initializedShape_ = {};
 
@@ -351,19 +357,23 @@ class TypeNewScript {
                     bool force = false);
 
   bool rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* group);
 
   static bool make(JSContext* cx, ObjectGroup* group, JSFunction* fun);
 
   size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
+  size_t gcMallocBytes() const;
+
   static size_t offsetOfPreliminaryObjects() {
     return offsetof(TypeNewScript, preliminaryObjects);
   }
+
+  static size_t sizeOfInitializerList(size_t length);
 };
 
 inline bool ObjectGroup::hasUnanalyzedPreliminaryObjects() {
   return (newScriptDontCheckGeneration() &&
           !newScriptDontCheckGeneration()->analyzed()) ||
          maybePreliminaryObjectsDontCheckGeneration();
 }
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3674,16 +3674,36 @@ bool TypeNewScript::make(JSContext* cx, 
 size_t TypeNewScript::sizeOfIncludingThis(
     mozilla::MallocSizeOf mallocSizeOf) const {
   size_t n = mallocSizeOf(this);
   n += mallocSizeOf(preliminaryObjects);
   n += mallocSizeOf(initializerList);
   return n;
 }
 
+/* static */ size_t TypeNewScript::sizeOfInitializerList(size_t length) {
+  // TypeNewScriptInitializer struct contains [1] element in its size.
+  size_t size = sizeof(TypeNewScript::InitializerList);
+  if (length > 0) {
+    size += (length - 1) * sizeof(TypeNewScriptInitializer);
+  }
+  return size;
+}
+
+size_t TypeNewScript::gcMallocBytes() const {
+  size_t size = sizeof(TypeNewScript);
+  if (preliminaryObjects) {
+    size += sizeof(PreliminaryObjectArray);
+  }
+  if (initializerList) {
+    size += sizeOfInitializerList(initializerList->length);
+  }
+  return size;
+}
+
 void TypeNewScript::registerNewObject(PlainObject* res) {
   MOZ_ASSERT(!analyzed());
 
   // New script objects must have the maximum number of fixed slots, so that
   // we can adjust their shape later to match the number of fixed slots used
   // by the template object we eventually create.
   MOZ_ASSERT(res->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS);
 
@@ -3830,16 +3850,18 @@ bool TypeNewScript::maybeAnalyze(JSConte
   }
 
   if (!group->newScript(sweep)) {
     return true;
   }
 
   MOZ_ASSERT(OnlyHasDataProperties(templateObject()->lastProperty()));
 
+  MOZ_ASSERT(!initializerList);
+  InitializerList* newInitializerList = nullptr;
   if (templateObject()->slotSpan() != 0) {
     // Make sure that all definite properties found are reflected in the
     // prefix shape. Otherwise, the constructor behaved differently before
     // we baseline compiled it and started observing types. Compare
     // property names rather than looking at the shapes directly, as the
     // allocation kind and other non-property parts of the template and
     // existing objects may differ.
     if (templateObject()->slotSpan() > prefixShape->slotSpan()) {
@@ -3861,35 +3883,37 @@ bool TypeNewScript::maybeAnalyze(JSConte
         shape = shape->previous();
         templateShape = templateShape->previous();
       }
       if (!templateShape->isEmptyShape()) {
         return true;
       }
     }
 
-    TypeNewScriptInitializer done(TypeNewScriptInitializer::DONE, 0);
-
-    if (!initializerVector.append(done)) {
-      return false;
-    }
-
-    initializerList = group->zone()->pod_calloc<TypeNewScriptInitializer>(
-        initializerVector.length());
-    if (!initializerList) {
+    size_t nbytes = sizeOfInitializerList(initializerVector.length());
+    newInitializerList = reinterpret_cast<InitializerList*>(
+        group->zone()->pod_calloc<uint8_t>(nbytes));
+    if (!newInitializerList) {
       ReportOutOfMemory(cx);
       return false;
     }
-    PodCopy(initializerList, initializerVector.begin(),
+    newInitializerList->length = initializerVector.length();
+    PodCopy(newInitializerList->entries, initializerVector.begin(),
             initializerVector.length());
   }
 
+  RemoveCellMemory(group, gcMallocBytes(), MemoryUse::ObjectGroupAddendum);
+
+  initializerList = newInitializerList;
+
   js_delete(preliminaryObjects);
   preliminaryObjects = nullptr;
 
+  AddCellMemory(group, gcMallocBytes(), MemoryUse::ObjectGroupAddendum);
+
   if (prefixShape->slotSpan() == templateObject()->slotSpan()) {
     // The definite properties analysis found exactly the properties that
     // are held in common by the preliminary objects. No further analysis
     // is needed.
     group->addDefiniteProperties(cx, templateObject()->lastProperty());
 
     destroyNewScript.release();
     return true;
@@ -4001,44 +4025,45 @@ bool TypeNewScript::rollbackPartiallyIni
     bool pastProperty = false;
 
     // Index in pcOffsets of the outermost frame.
     int callDepth = pcOffsets.length() - 1;
 
     // Index in pcOffsets of the frame currently being checked for a SETPROP.
     int setpropDepth = callDepth;
 
-    for (TypeNewScriptInitializer* init = initializerList;; init++) {
-      if (init->kind == TypeNewScriptInitializer::SETPROP) {
-        if (!pastProperty && pcOffsets[setpropDepth] < init->offset) {
+    for (size_t i = 0; i < initializerList->length; i++) {
+      const TypeNewScriptInitializer init = initializerList->entries[i];
+      if (init.kind == TypeNewScriptInitializer::SETPROP) {
+        if (!pastProperty && pcOffsets[setpropDepth] < init.offset) {
           // Have not yet reached this setprop.
           break;
         }
         // This setprop has executed, reset state for the next one.
         numProperties++;
         pastProperty = false;
         setpropDepth = callDepth;
-      } else if (init->kind == TypeNewScriptInitializer::SETPROP_FRAME) {
+      } else if (init.kind == TypeNewScriptInitializer::SETPROP_FRAME) {
         if (!pastProperty) {
-          if (pcOffsets[setpropDepth] < init->offset) {
+          if (pcOffsets[setpropDepth] < init.offset) {
             // Have not yet reached this inner call.
             break;
-          } else if (pcOffsets[setpropDepth] > init->offset) {
+          } else if (pcOffsets[setpropDepth] > init.offset) {
             // Have advanced past this inner call.
             pastProperty = true;
           } else if (setpropDepth == 0) {
             // Have reached this call but not yet in it.
             break;
           } else {
             // Somewhere inside this inner call.
             setpropDepth--;
           }
         }
       } else {
-        MOZ_ASSERT(init->kind == TypeNewScriptInitializer::DONE);
+        MOZ_ASSERT(init.kind == TypeNewScriptInitializer::DONE);
         finished = true;
         break;
       }
     }
 
     if (!finished) {
       (void)NativeObject::rollbackProperties(cx, obj, numProperties);
       found = true;
--- a/layout/generic/nsGridContainerFrame.cpp
+++ b/layout/generic/nsGridContainerFrame.cpp
@@ -803,17 +803,17 @@ struct nsGridContainerFrame::UsedTrackSi
    * sizes for an axis (if it's already true then this method is a NOP).
    */
   void ResolveTrackSizesForAxis(nsGridContainerFrame* aFrame, LogicalAxis aAxis,
                                 gfxContext& aRC);
 
   /** Helper function for the above method */
   void ResolveSubgridTrackSizesForAxis(nsGridContainerFrame* aFrame,
                                        LogicalAxis aAxis, Subgrid* aSubgrid,
-                                       gfxContext& aRC, nscoord aCBSize);
+                                       gfxContext& aRC, nscoord aContentBoxSize);
 
   // This only has valid sizes when mCanResolveLineRangeSize is true in
   // the same axis.  It may have zero tracks (a grid with only abs.pos.
   // subgrids/items may have zero tracks).
   PerLogicalAxis<nsTArray<TrackSize>> mSizes;
   // True if mSizes can be used to resolve line range sizes in an axis.
   PerLogicalAxis<bool> mCanResolveLineRangeSize;
 
@@ -2016,25 +2016,25 @@ struct nsGridContainerFrame::Tracks {
 
   /**
    * Implements "12.3. Track Sizing Algorithm"
    * http://dev.w3.org/csswg/css-grid/#algo-track-sizing
    */
   void CalculateSizes(GridReflowInput& aState,
                       nsTArray<GridItemInfo>& aGridItems,
                       const TrackSizingFunctions& aFunctions,
-                      nscoord aContentSize, LineRange GridArea::*aRange,
+                      nscoord aContentBoxSize, LineRange GridArea::*aRange,
                       SizingConstraint aConstraint);
 
   /**
    * Apply 'align/justify-content', whichever is relevant for this axis.
    * https://drafts.csswg.org/css-align-3/#propdef-align-content
    */
   void AlignJustifyContent(const nsStylePosition* aStyle, WritingMode aWM,
-                           nscoord aContentSize, bool aIsSubgridded);
+                           nscoord aContentBoxSize, bool aIsSubgridded);
 
   nscoord GridLineEdge(uint32_t aLine, GridLineSide aSide) const {
     if (MOZ_UNLIKELY(mSizes.IsEmpty())) {
       // https://drafts.csswg.org/css-grid/#grid-definition
       // "... the explicit grid still contains one grid line in each axis."
       MOZ_ASSERT(aLine == 0, "We should only resolve line 1 in an empty grid");
       return nscoord(0);
     }
@@ -3005,56 +3005,59 @@ void nsGridContainerFrame::UsedTrackSize
     return;
   }
   if (aFrame->IsSubgrid(aAxis)) {
     CopyUsedTrackSizes(mSizes[aAxis], parent, parentSizes, aFrame, subgrid,
                        aAxis);
     mCanResolveLineRangeSize[aAxis] = true;
   } else {
     const auto& range = subgrid->mArea.LineRangeForAxis(parentAxis);
-    nscoord cbSize = range.ToLength(parentSizes->mSizes[parentAxis]);
-    ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, cbSize);
+    nscoord contentBoxSize = range.ToLength(parentSizes->mSizes[parentAxis]);
+    auto parentWM = aFrame->GetParent()->GetWritingMode();
+    contentBoxSize -= subgrid->mMarginBorderPadding.StartEnd(parentAxis, parentWM);
+    contentBoxSize = std::max(nscoord(0), contentBoxSize);
+    ResolveSubgridTrackSizesForAxis(aFrame, aAxis, subgrid, aRC, contentBoxSize);
   }
 }
 
 void nsGridContainerFrame::UsedTrackSizes::ResolveSubgridTrackSizesForAxis(
     nsGridContainerFrame* aFrame, LogicalAxis aAxis, Subgrid* aSubgrid,
-    gfxContext& aRC, nscoord aCBSize) {
+    gfxContext& aRC, nscoord aContentBoxSize) {
   GridReflowInput state(aFrame, aRC);
   state.mGridItems = aSubgrid->mGridItems;
   Grid grid;
   grid.mGridColEnd = aSubgrid->mGridColEnd;
   grid.mGridRowEnd = aSubgrid->mGridRowEnd;
-  state.CalculateTrackSizesForAxis(aAxis, grid, aCBSize,
+  state.CalculateTrackSizesForAxis(aAxis, grid, aContentBoxSize,
                                    SizingConstraint::NoConstraint);
   const auto& tracks = aAxis == eLogicalAxisInline ? state.mCols : state.mRows;
   mSizes[aAxis] = tracks.mSizes;
   mCanResolveLineRangeSize[aAxis] = tracks.mCanResolveLineRangeSize;
   MOZ_ASSERT(mCanResolveLineRangeSize[aAxis]);
 }
 
 void nsGridContainerFrame::GridReflowInput::CalculateTrackSizesForAxis(
-    LogicalAxis aAxis, const Grid& aGrid, nscoord aContentSize,
+    LogicalAxis aAxis, const Grid& aGrid, nscoord aContentBoxSize,
     SizingConstraint aConstraint) {
   auto& tracks = aAxis == eLogicalAxisInline ? mCols : mRows;
   const auto& sizingFunctions =
       aAxis == eLogicalAxisInline ? mColFunctions : mRowFunctions;
   const auto& gapStyle = aAxis == eLogicalAxisInline ? mGridStyle->mColumnGap
                                                      : mGridStyle->mRowGap;
   uint32_t gridEnd =
       aAxis == eLogicalAxisInline ? aGrid.mGridColEnd : aGrid.mGridRowEnd;
   Maybe<SubgridFallbackTrackSizingFunctions> fallbackTrackSizing;
 
   bool useParentGaps = false;
   const bool isSubgriddedAxis = mFrame->IsSubgrid(aAxis);
   if (MOZ_LIKELY(!isSubgriddedAxis)) {
-    tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentSize);
+    tracks.Initialize(sizingFunctions, gapStyle, gridEnd, aContentBoxSize);
   } else {
-    tracks.mGridGap = nsLayoutUtils::ResolveGapToLength(gapStyle, aContentSize);
-    tracks.mContentBoxSize = aContentSize;
+    tracks.mGridGap = nsLayoutUtils::ResolveGapToLength(gapStyle, aContentBoxSize);
+    tracks.mContentBoxSize = aContentBoxSize;
     const auto* subgrid = mFrame->GetProperty(Subgrid::Prop());
     tracks.mSizes.SetLength(gridEnd);
     auto* parent = mFrame->ParentGridContainerForSubgrid();
     auto parentAxis = subgrid->mIsOrthogonal ? GetOrthogonalAxis(aAxis) : aAxis;
     const auto* parentSizes = parent->GetUsedTrackSizes();
     if (parentSizes && parentSizes->mCanResolveLineRangeSize[parentAxis]) {
       CopyUsedTrackSizes(tracks.mSizes, parent, parentSizes, mFrame, subgrid,
                          aAxis);
@@ -3063,17 +3066,17 @@ void nsGridContainerFrame::GridReflowInp
       fallbackTrackSizing.emplace(mFrame, subgrid, parent, parentAxis);
       tracks.Initialize(
           TrackSizingFunctions(fallbackTrackSizing->mMinSizingFunctions,
                                fallbackTrackSizing->mMaxSizingFunctions,
                                *fallbackTrackSizing->mAutoMinSizing,
                                *fallbackTrackSizing->mAutoMaxSizing,
                                fallbackTrackSizing->mHasRepeatAuto,
                                fallbackTrackSizing->mRepeatAutoIndex),
-          gapStyle, gridEnd, aContentSize);
+          gapStyle, gridEnd, aContentBoxSize);
     }
   }
 
   // We run the Track Sizing Algorithm in non-subgridded axes, and in some
   // cases in a subgridded axis when our parent track sizes aren't resolved yet.
   if (MOZ_LIKELY(!isSubgriddedAxis) || fallbackTrackSizing.isSome()) {
     const size_t origGridItemCount = mGridItems.Length();
     if (mFrame->HasSubgridItems(aAxis)) {
@@ -3084,27 +3087,27 @@ void nsGridContainerFrame::GridReflowInp
         fallbackTrackSizing.isSome()
             ? TrackSizingFunctions(fallbackTrackSizing->mMinSizingFunctions,
                                    fallbackTrackSizing->mMaxSizingFunctions,
                                    *fallbackTrackSizing->mAutoMinSizing,
                                    *fallbackTrackSizing->mAutoMaxSizing,
                                    fallbackTrackSizing->mHasRepeatAuto,
                                    fallbackTrackSizing->mRepeatAutoIndex)
             : sizingFunctions,
-        aContentSize,
+        aContentBoxSize,
         aAxis == eLogicalAxisInline ? &GridArea::mCols : &GridArea::mRows,
         aConstraint);
     // XXXmats we're losing the baseline state of subgrid descendants that
     // CollectSubgridItemsForAxis added here.  We need to propagate that
     // state into the subgrid's Reflow somehow...
     mGridItems.TruncateLength(origGridItemCount);
   }
 
-  if (aContentSize != NS_UNCONSTRAINEDSIZE) {
-    tracks.AlignJustifyContent(mGridStyle, mWM, aContentSize, isSubgriddedAxis);
+  if (aContentBoxSize != NS_UNCONSTRAINEDSIZE) {
+    tracks.AlignJustifyContent(mGridStyle, mWM, aContentBoxSize, isSubgriddedAxis);
   } else if (!useParentGaps) {
     const nscoord gridGap = tracks.mGridGap;
     nscoord pos = 0;
     for (TrackSize& sz : tracks.mSizes) {
       sz.mPosition = pos;
       pos += sz.mBase + gridGap;
     }
   }
@@ -5607,17 +5610,17 @@ void nsGridContainerFrame::Tracks::Stret
         continue;
       }
     }
     break;
   }
 }
 
 void nsGridContainerFrame::Tracks::AlignJustifyContent(
-    const nsStylePosition* aStyle, WritingMode aWM, nscoord aContentSize,
+    const nsStylePosition* aStyle, WritingMode aWM, nscoord aContentBoxSize,
     bool aIsSubgriddedAxis) {
   const bool isAlign = mAxis == eLogicalAxisBlock;
   // Align-/justify-content doesn't apply in a subgridded axis.
   // Gap properties do apply though so we need to stretch/position the tracks
   // to center-align the gaps with the parent's gaps.
   if (MOZ_UNLIKELY(aIsSubgriddedAxis)) {
     auto& gap = isAlign ? aStyle->mRowGap : aStyle->mColumnGap;
     if (gap.IsNormal()) {
@@ -5697,17 +5700,17 @@ void nsGridContainerFrame::Tracks::Align
     } else {
       for (const TrackSize& sz : mSizes) {
         trackSizeSum += sz.mBase;
         if (sz.mState & TrackSize::eAutoMaxSizing) {
           ++numAutoTracks;
         }
       }
     }
-    space = aContentSize - trackSizeSum - SumOfGridGaps();
+    space = aContentBoxSize - trackSizeSum - SumOfGridGaps();
     // Use the fallback value instead when applicable.
     if (space < 0 ||
         (alignment == NS_STYLE_ALIGN_SPACE_BETWEEN && mSizes.Length() == 1)) {
       auto fallback = ::GetAlignJustifyFallbackIfAny(valueAndFallback, aWM,
                                                      isAlign, &overflowSafe);
       if (fallback) {
         alignment = fallback;
       }
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -401,16 +401,25 @@ VARCACHE_PREF(
 
 VARCACHE_PREF(
   Live,
   "apz.frame_delay.enabled",
   APZFrameDelayEnabled,
   RelaxedAtomicBool, false
 )
 
+#ifdef MOZ_WIDGET_GTK
+VARCACHE_PREF(
+  Live,
+  "apz.gtk.kinetic_scroll.enabled",
+  APZGTKKineticScrollEnabled,
+  RelaxedAtomicBool, false
+)
+#endif
+
 #if !defined(MOZ_WIDGET_ANDROID)
 # define PREF_VALUE true
 #else
 # define PREF_VALUE false
 #endif
 VARCACHE_PREF(
   Once,
   "apz.keyboard.enabled",
--- a/netwerk/build/components.conf
+++ b/netwerk/build/components.conf
@@ -335,17 +335,17 @@ Classes = [
         'cid': '{dc01dbbb-a5bb-4cbb-82bb-085cce06c0bb}',
         'contract_ids': ['@mozilla.org/network/protocol;1?name=wss'],
         'singleton': True,
         'legacy_constructor': 'mozilla::net::WebSocketSSLChannelConstructor',
     },
     {
         'cid': '{a181af0d-68b8-4308-94db-d4f859058215}',
         'contract_ids': ['@mozilla.org/network/safe-file-output-stream;1'],
-        'type': 'nsAtomicFileOutputStream',
+        'type': 'nsSafeFileOutputStream',
         'headers': ['nsFileStreams.h'],
     },
     {
         'cid': '{d6ef593d-a429-4b14-a887-d9e2f765d9ed}',
         'contract_ids': ['@mozilla.org/network/serialization-helper;1'],
         'type': 'nsSerializationHelper',
         'headers': ['nsSerializationHelper.h'],
     },
--- a/testing/web-platform/meta/css/css-ui/appearance-auto-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-auto-001.html.ini
@@ -1,5 +1,6 @@
 [appearance-auto-001.html]
   expected:
     if os == "win": FAIL
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
--- a/testing/web-platform/meta/css/css-ui/appearance-button-bevel-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-button-bevel-001.html.ini
@@ -1,7 +1,8 @@
 [appearance-button-bevel-001.html]
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-checkbox-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-checkbox-001.html.ini
@@ -1,5 +1,6 @@
 [appearance-checkbox-001.html]
   expected:
     if os == "win": FAIL
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
--- a/testing/web-platform/meta/css/css-ui/appearance-listbox-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-listbox-001.html.ini
@@ -1,5 +1,6 @@
 [appearance-listbox-001.html]
   expected:
     if os == "win": FAIL
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
--- a/testing/web-platform/meta/css/css-ui/appearance-menulist-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-menulist-001.html.ini
@@ -1,5 +1,6 @@
 [appearance-menulist-001.html]
   expected:
     if os == "win": FAIL
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
--- a/testing/web-platform/meta/css/css-ui/appearance-menulist-button-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-menulist-button-001.html.ini
@@ -1,7 +1,8 @@
 [appearance-menulist-button-001.html]
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-menulist-button-002.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-menulist-button-002.html.ini
@@ -1,6 +1,7 @@
 [appearance-menulist-button-002.html]
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if os == "win": PASS
     FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-meter-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-meter-001.html.ini
@@ -1,5 +1,6 @@
 [appearance-meter-001.html]
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-progress-bar-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-progress-bar-001.html.ini
@@ -1,5 +1,6 @@
 [appearance-progress-bar-001.html]
   disabled:
     if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-push-button-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-push-button-001.html.ini
@@ -1,3 +1,6 @@
 [appearance-push-button-001.html]
+  disabled:
+    if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-radio-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-radio-001.html.ini
@@ -1,5 +1,8 @@
 [appearance-radio-001.html]
+  disabled:
+    if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-searchfield-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-searchfield-001.html.ini
@@ -1,5 +1,8 @@
 [appearance-searchfield-001.html]
+  disabled:
+    if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-slider-horizontal-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-slider-horizontal-001.html.ini
@@ -1,5 +1,8 @@
 [appearance-slider-horizontal-001.html]
+  disabled:
+    if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-square-button-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-square-button-001.html.ini
@@ -1,5 +1,8 @@
 [appearance-square-button-001.html]
+  disabled:
+    if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
--- a/testing/web-platform/meta/css/css-ui/appearance-textarea-001.html.ini
+++ b/testing/web-platform/meta/css/css-ui/appearance-textarea-001.html.ini
@@ -1,5 +1,8 @@
 [appearance-textarea-001.html]
+  disabled:
+    if debug and os == "win" and version == "6.1.7601": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
+    if os == "win" and processor == "aarch64": https://bugzilla.mozilla.org/show_bug.cgi?id=1560360
   expected:
     if (os == "win") and debug and (processor == "x86_64"): FAIL
     if (os == "win") and not debug: FAIL
     if os == "win": FAIL
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001-ref.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Reference: subgrid auto track sizing</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1558705">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+
+.grid {
+  display: grid;
+  max-width: 260px;
+  padding: 1px 5px;
+  border: 1px solid;
+}
+.subgrid {
+  display: grid;
+  padding: 3px 5px 7px 11px;
+  border-style: solid;
+  border-width: 5px 7px 11px 3px;
+  margin: 7px 11px 3px 5px;
+  background: grey;
+}
+
+  </style>
+</head>
+<body>
+
+<article class="grid">
+  <div class="subgrid">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: 100px auto">
+  <div class="subgrid">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: auto 100px">
+  <div class="subgrid" style="grid-column:2">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: auto 100px auto">
+  <div class="subgrid" style="grid-column:2 span 2">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: auto 100px auto">
+  <div class="subgrid" style="grid-column:1 span 3">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-grid/subgrid/auto-track-sizing-001.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+  <meta charset="utf-8">
+  <title>Test: subgrid auto track sizing</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1558705">
+  <link rel="help" href="https://drafts.csswg.org/css-grid-2">
+  <link rel="match" href="auto-track-sizing-001-ref.html">
+  <style>
+html,body {
+  color:black; background-color:white; font:16px/1 monospace; padding:0; margin:0;
+}
+
+.grid {
+  display: grid;
+  max-width: 260px;
+  padding: 1px 5px;
+  border: 1px solid;
+}
+.subgrid {
+  display: grid;
+  grid-template-rows: subgrid;
+  padding: 3px 5px 7px 11px;
+  border-style: solid;
+  border-width: 5px 7px 11px 3px;
+  margin: 7px 11px 3px 5px;
+  background: grey;
+}
+
+  </style>
+</head>
+<body>
+
+<article class="grid">
+  <div class="subgrid">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: 100px auto">
+  <div class="subgrid">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: auto 100px">
+  <div class="subgrid" style="grid-column:2">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: auto 100px auto">
+  <div class="subgrid" style="grid-column:2 span 2">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+<article class="grid" style="grid-template-columns: auto 100px auto">
+  <div class="subgrid" style="grid-column:1 span 3">
+    The cat can not be separated from milk
+  </div>
+</article>
+
+</body>
+</html>
--- a/toolkit/modules/ContentDOMReference.jsm
+++ b/toolkit/modules/ContentDOMReference.jsm
@@ -13,129 +13,158 @@
  * The hope is that this module can eliminate the need for passing CPOW references
  * between processes during runtime.
  */
 
 var EXPORTED_SYMBOLS = ["ContentDOMReference"];
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
+XPCOMUtils.defineLazyServiceGetter(this, "finalizationService",
+                                   "@mozilla.org/toolkit/finalizationwitness;1",
+                                   "nsIFinalizationWitnessService");
+
+/**
+ * @typedef {number} ElementID
+ * @typedef {Object} ElementIdentifier
+ */
+
+const FINALIZATION_TOPIC = "content-dom-reference-finalized";
+
+// A WeakMap which ties finalization witness objects to the lifetime of the DOM
+// nodes they're meant to witness. When the DOM node in the map key is
+// finalized, the WeakMap stops holding the finalization witness in its value
+// alive, which alerts our observer that the element has been destroyed.
+const finalizerRoots = new WeakMap();
 
 /**
  * An identifier generated by ContentDOMReference is a unique pair of BrowsingContext
- * ID and a UUID. gRegistry maps BrowsingContext's to an object with the following
+ * ID and a numeric ID. gRegistry maps BrowsingContext's to an object with the following
  * properties:
  *
- *   UUIDToElement:
- *     A Map of UUID strings to WeakReference's to the elements they refer to.
+ *   IDToElement:
+ *     A Map of IDs to WeakReference's to the elements they refer to.
  *
- *   elementToUUID:
- *     A WeakMap from a DOM element to a UUID that refers to it.
+ *   elementToID:
+ *     A WeakMap from a DOM element to an ID that refers to it.
  */
 var gRegistry = new WeakMap();
 
 var ContentDOMReference = {
+  _init() {
+    const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+    Services.obs.addObserver(this, FINALIZATION_TOPIC);
+  },
+
+  observe(subject, topic, data) {
+    if (topic !== FINALIZATION_TOPIC) {
+      throw new Error("Unexpected observer topic");
+    }
+
+    let identifier = JSON.parse(data);
+    this._revoke(identifier);
+  },
+
   /**
    * Generate and return an identifier for a given DOM element.
    *
    * @param {Element} element The DOM element to generate the identifier for.
-   * @return {Object} The identifier for the DOM element that can be passed between
+   * @return {ElementIdentifier} The identifier for the DOM element that can be passed between
    * processes as a message.
    */
   get(element) {
     if (!element) {
       throw new Error("Can't create a ContentDOMReference identifier for " +
                       "non-existant nodes.");
     }
 
     let browsingContext = element.ownerGlobal.getWindowGlobalChild().browsingContext;
     let mappings = gRegistry.get(browsingContext);
     if (!mappings) {
       mappings = {
-        UUIDToElement: new Map(),
-        elementToUUID: new WeakMap(),
+        IDToElement: new Map(),
+        elementToID: new WeakMap(),
       };
       gRegistry.set(browsingContext, mappings);
     }
 
-    let uuid = mappings.elementToUUID.get(element);
-    if (uuid) {
-      // We already had this element registered, so return the pre-existing UUID.
-      return { browsingContextId: browsingContext.id, uuid };
+    let id = mappings.elementToID.get(element);
+    if (id) {
+      // We already had this element registered, so return the pre-existing ID.
+      return { browsingContextId: browsingContext.id, id };
     }
 
     // We must be registering a new element at this point.
-    uuid = gUUIDGenerator.generateUUID().toString();
-    mappings.elementToUUID.set(element, uuid);
-    mappings.UUIDToElement.set(uuid, Cu.getWeakReference(element));
+    id = Math.random();
+    mappings.elementToID.set(element, id);
+    mappings.IDToElement.set(id, Cu.getWeakReference(element));
+
+    let identifier = { browsingContextId: browsingContext.id, id };
 
-    return { browsingContextId: browsingContext.id, uuid };
+    finalizerRoots.set(
+        element,
+        finalizationService.make(FINALIZATION_TOPIC,
+                                 JSON.stringify(identifier)));
+
+    return identifier;
   },
 
   /**
    * Resolves an identifier back into the DOM Element that it was generated from.
    *
-   * @param {Object} The identifier generated via ContentDOMReference.get for a
+   * @param {ElementIdentifier} The identifier generated via ContentDOMReference.get for a
    * DOM element.
    * @return {Element} The DOM element that the identifier was generated for, or
    * null if the element does not still exist.
    */
   resolve(identifier) {
     let browsingContext = BrowsingContext.get(identifier.browsingContextId);
-    let uuid = identifier.uuid;
-    return this._resolveUUIDToElement(browsingContext, uuid);
+    let {id} = identifier;
+    return this._resolveIDToElement(browsingContext, id);
   },
 
   /**
    * Removes an identifier from the registry so that subsequent attempts
-   * to resolve it will result in null. This is generally a good idea to avoid
-   * identifiers lying around taking up space (identifiers don't keep the
-   * DOM element alive, but the weak pointers themselves consume memory, and
-   * that's what we reclaim when revoking).
+   * to resolve it will result in null. This is done automatically when the
+   * target node is GCed.
    *
-   * @param {Object} The identifier to revoke, issued by ContentDOMReference.get for
+   * @param {ElementIdentifier} The identifier to revoke, issued by ContentDOMReference.get for
    * a DOM element.
    */
-  revoke(identifier) {
+  _revoke(identifier) {
     let browsingContext = BrowsingContext.get(identifier.browsingContextId);
-    let uuid = identifier.uuid;
+    let {id} = identifier;
 
     let mappings = gRegistry.get(browsingContext);
     if (!mappings) {
       return;
     }
 
-    let element = this._resolveUUIDToElement(browsingContext, uuid);
-    if (element) {
-      mappings.elementToUUID.delete(element);
-    }
-
-    mappings.UUIDToElement.delete(uuid);
+    mappings.IDToElement.delete(id);
   },
 
   /**
-   * Private helper function that resolves a BrowsingContext and UUID (the
+   * Private helper function that resolves a BrowsingContext and ID (the
    * pair that makes up an identifier) to a DOM element.
    *
    * @param {BrowsingContext} browsingContext The BrowsingContext that was hosting
    * the DOM element at the time that the identifier was generated.
-   * @param {String} uuid The UUID generated for the DOM element.
+   * @param {ElementID} id The ID generated for the DOM element.
    *
    * @return {Element} The DOM element that the identifier was generated for, or
    * null if the element does not still exist.
    */
-  _resolveUUIDToElement(browsingContext, uuid) {
+  _resolveIDToElement(browsingContext, id) {
     let mappings = gRegistry.get(browsingContext);
     if (!mappings) {
       return null;
     }
 
-    let weakReference = mappings.UUIDToElement.get(uuid);
+    let weakReference = mappings.IDToElement.get(id);
     if (!weakReference) {
       return null;
     }
 
     return weakReference.get();
   },
 };
+
+ContentDOMReference._init();
--- a/widget/InputData.cpp
+++ b/widget/InputData.cpp
@@ -432,17 +432,18 @@ PanGestureInput::PanGestureInput()
       mType(PANGESTURE_MAYSTART),
       mLineOrPageDeltaX(0),
       mLineOrPageDeltaY(0),
       mUserDeltaMultiplierX(1.0),
       mUserDeltaMultiplierY(1.0),
       mHandledByAPZ(false),
       mFollowedByMomentum(false),
       mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false),
-      mOverscrollBehaviorAllowsSwipe(false) {}
+      mOverscrollBehaviorAllowsSwipe(false),
+      mSimulateMomentum(false) {}
 
 PanGestureInput::PanGestureInput(PanGestureType aType, uint32_t aTime,
                                  TimeStamp aTimeStamp,
                                  const ScreenPoint& aPanStartPoint,
                                  const ScreenPoint& aPanDisplacement,
                                  Modifiers aModifiers)
     : InputData(PANGESTURE_INPUT, aTime, aTimeStamp, aModifiers),
       mType(aType),
@@ -450,17 +451,18 @@ PanGestureInput::PanGestureInput(PanGest
       mPanDisplacement(aPanDisplacement),
       mLineOrPageDeltaX(0),
       mLineOrPageDeltaY(0),
       mUserDeltaMultiplierX(1.0),
       mUserDeltaMultiplierY(1.0),
       mHandledByAPZ(false),
       mFollowedByMomentum(false),
       mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(false),
-      mOverscrollBehaviorAllowsSwipe(false) {}
+      mOverscrollBehaviorAllowsSwipe(false),
+      mSimulateMomentum(false) {}
 
 bool PanGestureInput::IsMomentum() const {
   switch (mType) {
     case PanGestureInput::PANGESTURE_MOMENTUMSTART:
     case PanGestureInput::PANGESTURE_MOMENTUMPAN:
     case PanGestureInput::PANGESTURE_MOMENTUMEND:
       return true;
     default:
@@ -472,37 +474,54 @@ WidgetWheelEvent PanGestureInput::ToWidg
   WidgetWheelEvent wheelEvent(true, eWheel, aWidget);
   wheelEvent.mModifiers = this->modifiers;
   wheelEvent.mTime = mTime;
   wheelEvent.mTimeStamp = mTimeStamp;
   wheelEvent.mRefPoint = RoundedToInt(ViewAs<LayoutDevicePixel>(
       mPanStartPoint,
       PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
   wheelEvent.mButtons = 0;
-  wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
   wheelEvent.mMayHaveMomentum = true;  // pan inputs may have momentum
   wheelEvent.mIsMomentum = IsMomentum();
   wheelEvent.mLineOrPageDeltaX = mLineOrPageDeltaX;
   wheelEvent.mLineOrPageDeltaY = mLineOrPageDeltaY;
   wheelEvent.mDeltaX = mPanDisplacement.x;
   wheelEvent.mDeltaY = mPanDisplacement.y;
   wheelEvent.mFlags.mHandledByAPZ = mHandledByAPZ;
   wheelEvent.mFocusSequenceNumber = mFocusSequenceNumber;
+  if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+    // Emulate legacy widget/gtk behavior
+    wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_LINE;
+    wheelEvent.mIsNoLineOrPageDelta = true;
+    wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+    wheelEvent.mDeltaX *= 3;
+    wheelEvent.mDeltaY *= 3;
+  } else {
+    wheelEvent.mDeltaMode = WheelEvent_Binding::DOM_DELTA_PIXEL;
+  }
   return wheelEvent;
 }
 
 bool PanGestureInput::TransformToLocal(
     const ScreenToParentLayerMatrix4x4& aTransform) {
   Maybe<ParentLayerPoint> panStartPoint =
       UntransformBy(aTransform, mPanStartPoint);
   if (!panStartPoint) {
     return false;
   }
   mLocalPanStartPoint = *panStartPoint;
 
+  if (mDeltaType == PanGestureInput::PANDELTA_PAGE) {
+    // Skip transforming the pan displacement because we want
+    // raw page proportion counts.
+    mLocalPanDisplacement.x = mPanDisplacement.x;
+    mLocalPanDisplacement.y = mPanDisplacement.y;
+    return true;
+  }
+
   Maybe<ParentLayerPoint> panDisplacement =
       UntransformVector(aTransform, mPanDisplacement, mPanStartPoint);
   if (!panDisplacement) {
     return false;
   }
   mLocalPanDisplacement = *panDisplacement;
   return true;
 }
--- a/widget/InputData.h
+++ b/widget/InputData.h
@@ -327,16 +327,27 @@ class PanGestureInput : public InputData
       // MomentumPan: The actual momentum motion by mPanDisplacement.
       PANGESTURE_MOMENTUMPAN,
 
       // MomentumEnd: The momentum animation has ended, for example because the
       // momentum velocity has gone below the stopping threshold, or because the
       // user has stopped the animation by putting their fingers on a touchpad.
       PANGESTURE_MOMENTUMEND
   ));
+
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+    PanDeltaType, (
+      // There are three kinds of scroll delta modes in Gecko: "page", "line"
+      // and "pixel". Touchpad pan gestures only support "page" and "pixel".
+      //
+      // NOTE: PANDELTA_PAGE currently replicates Gtk behavior
+      // (see AsyncPanZoomController::OnPan).
+      PANDELTA_PAGE,
+      PANDELTA_PIXEL
+  ));
   // clang-format on
 
   PanGestureInput(PanGestureType aType, uint32_t aTime, TimeStamp aTimeStamp,
                   const ScreenPoint& aPanStartPoint,
                   const ScreenPoint& aPanDisplacement, Modifiers aModifiers);
 
   bool IsMomentum() const;
 
@@ -363,37 +374,49 @@ class PanGestureInput : public InputData
   // See lineOrPageDeltaX/Y on WidgetWheelEvent.
   int32_t mLineOrPageDeltaX;
   int32_t mLineOrPageDeltaY;
 
   // User-set delta multipliers.
   double mUserDeltaMultiplierX;
   double mUserDeltaMultiplierY;
 
-  bool mHandledByAPZ;
+  PanDeltaType mDeltaType = PANDELTA_PIXEL;
+
+  bool mHandledByAPZ: 1;
 
   // true if this is a PANGESTURE_END event that will be followed by a
   // PANGESTURE_MOMENTUMSTART event.
-  bool mFollowedByMomentum;
+  bool mFollowedByMomentum: 1;
 
   // If this is true, and this event started a new input block that couldn't
   // find a scrollable target which is scrollable in the horizontal component
   // of the scroll start direction, then this input block needs to be put on
   // hold until a content response has arrived, even if the block has a
   // confirmed target.
   // This is used by events that can result in a swipe instead of a scroll.
-  bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection;
+  bool mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection: 1;
 
   // This is used by APZ to communicate to the macOS widget code whether
   // the overscroll-behavior of the scroll frame handling this swipe allows
   // non-local overscroll behaviors in the horizontal direction (such as
   // swipe navigation).
-  bool mOverscrollBehaviorAllowsSwipe;
+  bool mOverscrollBehaviorAllowsSwipe: 1;
+
+  // true if APZ should do a fling animation after this pan ends, like
+  // it would with touchscreens. (For platforms that don't emit momentum events.)
+  bool mSimulateMomentum: 1;
 
-  // XXX: If adding any more bools, switch to using bitfields instead.
+  void SetHandledByAPZ(bool aHandled) { mHandledByAPZ = aHandled; }
+  void SetFollowedByMomentum(bool aFollowed) { mFollowedByMomentum = aFollowed; }
+  void SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection(bool aRequires) {
+    mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection = aRequires;
+  }
+  void SetOverscrollBehaviorAllowsSwipe(bool aAllows) { mOverscrollBehaviorAllowsSwipe = aAllows; }
+  void SetSimulateMomentum(bool aSimulate) { mSimulateMomentum = aSimulate; }
 };
 
 /**
  * Encapsulation class for pinch events. In general, these will be generated by
  * a gesture listener by looking at SingleTouchData/MultiTouchInput instances
  * and determining whether or not the user was trying to do a gesture.
  */
 class PinchGestureInput : public InputData {
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -114,16 +114,19 @@ using namespace mozilla::widget;
 #include "GLContextProvider.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/HelpersCairo.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/KnowsCompositor.h"
 
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+
 #ifdef MOZ_X11
 #  include "GLContextGLX.h"  // for GLContextGLX::FindVisual()
 #  include "GtkCompositorWidget.h"
 #  include "gfxXlibSurface.h"
 #  include "WindowSurfaceX11Image.h"
 #  include "WindowSurfaceX11SHM.h"
 #  include "WindowSurfaceXRender.h"
 #endif  // MOZ_X11
@@ -3029,40 +3032,78 @@ gboolean nsWindow::OnKeyReleaseEvent(Gdk
 }
 
 void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
   // check to see if we should rollup
   if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) return;
 #if GTK_CHECK_VERSION(3, 4, 0)
   // check for duplicate legacy scroll event, see GNOME bug 726878
   if (aEvent->direction != GDK_SCROLL_SMOOTH &&
-      mLastScrollEventTime == aEvent->time)
+      mLastScrollEventTime == aEvent->time) {
+    LOG(("[%d] duplicate legacy scroll event %d\n", aEvent->time,
+         aEvent->direction));
     return;
+  }
 #endif
   WidgetWheelEvent wheelEvent(true, eWheel, this);
   wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
   switch (aEvent->direction) {
 #if GTK_CHECK_VERSION(3, 4, 0)
     case GDK_SCROLL_SMOOTH: {
       // As of GTK 3.4, all directional scroll events are provided by
-      // the GDK_SCROLL_SMOOTH direction on XInput2 devices.
+      // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
       mLastScrollEventTime = aEvent->time;
+
+      // Special handling for touchpads to support flings
+      // (also known as kinetic/inertial/momentum scrolling)
+      GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
+      GdkInputSource source = gdk_device_get_source(device);
+      if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) {
+        if (StaticPrefs::APZGTKKineticScrollEnabled() &&
+            gtk_check_version(3, 20, 0) == nullptr) {
+          static auto sGdkEventIsScrollStopEvent =
+              (gboolean(*)(const GdkEvent*))dlsym(
+                  RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
+
+          LOG(("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n",
+               aEvent->time, aEvent->delta_x, aEvent->delta_y, mPanInProgress));
+          PanGestureInput::PanGestureType eventType =
+              PanGestureInput::PANGESTURE_PAN;
+          if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
+            eventType = PanGestureInput::PANGESTURE_END;
+            mPanInProgress = false;
+          } else if (!mPanInProgress) {
+            eventType = PanGestureInput::PANGESTURE_START;
+            mPanInProgress = true;
+          }
+
+          LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+          PanGestureInput panEvent(
+              eventType, aEvent->time, GetEventTimeStamp(aEvent->time),
+              ScreenPoint(touchPoint.x, touchPoint.y),
+              ScreenPoint(aEvent->delta_x, aEvent->delta_y),
+              KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+          panEvent.mDeltaType = PanGestureInput::PANDELTA_PAGE;
+          panEvent.mSimulateMomentum = true;
+
+          DispatchPanGestureInput(panEvent);
+
+          return;
+        }
+
+        // Older GTK doesn't support stop events, so we can't support fling
+        wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+      }
+
       // TODO - use a more appropriate scrolling unit than lines.
       // Multiply event deltas by 3 to emulate legacy behaviour.
       wheelEvent.mDeltaX = aEvent->delta_x * 3;
       wheelEvent.mDeltaY = aEvent->delta_y * 3;
       wheelEvent.mIsNoLineOrPageDelta = true;
-      // This next step manually unsets smooth scrolling for touch devices
-      // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which
-      // represents the actual input.
-      GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
-      GdkInputSource source = gdk_device_get_source(device);
-      if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD) {
-        wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
-      }
+
       break;
     }
 #endif
     case GDK_SCROLL_UP:
       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
       break;
     case GDK_SCROLL_DOWN:
       wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -487,16 +487,18 @@ class nsWindow final : public nsBaseWidg
   nsSizeMode mSizeState;
 
   nsIntPoint mClientOffset;
 
 #if GTK_CHECK_VERSION(3, 4, 0)
   // This field omits duplicate scroll events caused by GNOME bug 726878.
   guint32 mLastScrollEventTime;
 
+  bool mPanInProgress = false;
+
   // for touch event handling
   nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch>
       mTouches;
 #endif
 
 #ifdef MOZ_X11
   Display* mXDisplay;
   Window mXWindow;
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -1127,16 +1127,39 @@ void nsBaseWidget::DispatchTouchInput(Mu
   } else {
     WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
 
     nsEventStatus status;
     DispatchEvent(&event, status);
   }
 }
 
+void nsBaseWidget::DispatchPanGestureInput(PanGestureInput& aInput) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mAPZC) {
+    MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+    uint64_t inputBlockId = 0;
+    ScrollableLayerGuid guid;
+
+    nsEventStatus result =
+        mAPZC->InputBridge()->ReceiveInputEvent(aInput, &guid, &inputBlockId);
+    if (result == nsEventStatus_eConsumeNoDefault) {
+      return;
+    }
+
+    WidgetWheelEvent event = aInput.ToWidgetWheelEvent(this);
+    ProcessUntransformedAPZEvent(&event, guid, inputBlockId, result);
+  } else {
+    WidgetWheelEvent event = aInput.ToWidgetWheelEvent(this);
+
+    nsEventStatus status;
+    DispatchEvent(&event, status);
+  }
+}
+
 nsEventStatus nsBaseWidget::DispatchInputEvent(WidgetInputEvent* aEvent) {
   MOZ_ASSERT(NS_IsMainThread());
   if (mAPZC) {
     if (APZThreadUtils::IsControllerThread()) {
       uint64_t inputBlockId = 0;
       ScrollableLayerGuid guid;
 
       nsEventStatus result = mAPZC->InputBridge()->ReceiveInputEvent(
--- a/widget/nsBaseWidget.h
+++ b/widget/nsBaseWidget.h
@@ -614,16 +614,24 @@ class nsBaseWidget : public nsIWidget, p
   /**
    * Dispatch the given MultiTouchInput through APZ to Gecko (if APZ is enabled)
    * or directly to gecko (if APZ is not enabled). This function must only
    * be called from the main thread, and if APZ is enabled, that must also be
    * the APZ controller thread.
    */
   void DispatchTouchInput(mozilla::MultiTouchInput& aInput);
 
+  /**
+   * Dispatch the given PanGestureInput through APZ to Gecko (if APZ is enabled)
+   * or directly to gecko (if APZ is not enabled). This function must only
+   * be called from the main thread, and if APZ is enabled, that must also be
+   * the APZ controller thread.
+   */
+  void DispatchPanGestureInput(mozilla::PanGestureInput& aInput);
+
 #if defined(XP_WIN)
   void UpdateScrollCapture() override;
 
   /**
    * To be overridden by derived classes to return a snapshot that can be used
    * during scrolling. Returning null means we won't update the container.
    * @return an already AddRefed SourceSurface containing the snapshot
    */
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -1206,58 +1206,74 @@ struct ParamTraits<mozilla::MouseInput> 
 template <>
 struct ParamTraits<mozilla::PanGestureInput::PanGestureType>
     : public ContiguousEnumSerializerInclusive<
           mozilla::PanGestureInput::PanGestureType,
           mozilla::PanGestureInput::PanGestureType::PANGESTURE_MAYSTART,
           mozilla::PanGestureInput::sHighestPanGestureType> {};
 
 template <>
-struct ParamTraits<mozilla::PanGestureInput> {
+struct ParamTraits<mozilla::PanGestureInput::PanDeltaType>
+    : public ContiguousEnumSerializerInclusive<
+          mozilla::PanGestureInput::PanDeltaType,
+          mozilla::PanGestureInput::PanDeltaType::PANDELTA_PAGE,
+          mozilla::PanGestureInput::sHighestPanDeltaType> {};
+
+template <>
+struct ParamTraits<mozilla::PanGestureInput>
+    : BitfieldHelper<mozilla::PanGestureInput> {
   typedef mozilla::PanGestureInput paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, static_cast<const mozilla::InputData&>(aParam));
     WriteParam(aMsg, aParam.mType);
     WriteParam(aMsg, aParam.mPanStartPoint);
     WriteParam(aMsg, aParam.mPanDisplacement);
     WriteParam(aMsg, aParam.mLocalPanStartPoint);
     WriteParam(aMsg, aParam.mLocalPanDisplacement);
     WriteParam(aMsg, aParam.mLineOrPageDeltaX);
     WriteParam(aMsg, aParam.mLineOrPageDeltaY);
     WriteParam(aMsg, aParam.mUserDeltaMultiplierX);
     WriteParam(aMsg, aParam.mUserDeltaMultiplierY);
+    WriteParam(aMsg, aParam.mDeltaType);
     WriteParam(aMsg, aParam.mHandledByAPZ);
     WriteParam(aMsg, aParam.mFollowedByMomentum);
     WriteParam(
         aMsg,
         aParam
             .mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection);
     WriteParam(aMsg, aParam.mOverscrollBehaviorAllowsSwipe);
+    WriteParam(aMsg, aParam.mSimulateMomentum);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     return ReadParam(aMsg, aIter, static_cast<mozilla::InputData*>(aResult)) &&
            ReadParam(aMsg, aIter, &aResult->mType) &&
            ReadParam(aMsg, aIter, &aResult->mPanStartPoint) &&
            ReadParam(aMsg, aIter, &aResult->mPanDisplacement) &&
            ReadParam(aMsg, aIter, &aResult->mLocalPanStartPoint) &&
            ReadParam(aMsg, aIter, &aResult->mLocalPanDisplacement) &&
            ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaX) &&
            ReadParam(aMsg, aIter, &aResult->mLineOrPageDeltaY) &&
            ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierX) &&
            ReadParam(aMsg, aIter, &aResult->mUserDeltaMultiplierY) &&
-           ReadParam(aMsg, aIter, &aResult->mHandledByAPZ) &&
-           ReadParam(aMsg, aIter, &aResult->mFollowedByMomentum) &&
-           ReadParam(
-               aMsg, aIter,
-               &aResult
-                    ->mRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) &&
-           ReadParam(aMsg, aIter, &aResult->mOverscrollBehaviorAllowsSwipe);
+           ReadParam(aMsg, aIter, &aResult->mDeltaType) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetHandledByAPZ) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetFollowedByMomentum) &&
+           ReadBoolForBitfield(
+               aMsg, aIter, aResult,
+               &paramType::
+                   SetRequiresContentResponseIfCannotScrollHorizontallyInStartDirection) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetOverscrollBehaviorAllowsSwipe) &&
+           ReadBoolForBitfield(aMsg, aIter, aResult,
+                               &paramType::SetSimulateMomentum);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::PinchGestureInput::PinchGestureType>
     : public ContiguousEnumSerializerInclusive<
           mozilla::PinchGestureInput::PinchGestureType,
           mozilla::PinchGestureInput::PinchGestureType::PINCHGESTURE_START,