Merge mozilla-inbound to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Tue, 21 May 2019 19:29:01 +0300
changeset 474731 3c0f78074b727fbae112b6eda111d4c4d30cc3ec
parent 474730 78571bb1f20e643cdab9791f61b0aa7b6a8bdf90 (current diff)
parent 474684 4d7ef530fffba20a2b352cff9fa23438128105a7 (diff)
child 474732 71bf2b9ca6ea2cbd97f4a07242e2ad7f20a1a2e5
child 474780 afefd7bc17fdfd069206b1b127bcca744a9be7c7
push id113168
push userrmaries@mozilla.com
push dateTue, 21 May 2019 16:39:23 +0000
treeherdermozilla-inbound@3c0f78074b72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
js/src/gc/GC.cpp
js/src/gc/Marking.cpp
js/src/jsapi-tests/testGCWeakRef.cpp
--- a/dom/webidl/MozFrameLoaderOwner.webidl
+++ b/dom/webidl/MozFrameLoaderOwner.webidl
@@ -11,16 +11,26 @@ dictionary RemotenessOptions {
 
   // Used to resume a given channel load within the target process. If present,
   // it will be used rather than the `src` & `srcdoc` attributes on the
   // frameloader to control the load behaviour.
   unsigned long long pendingSwitchID;
   boolean replaceBrowsingContext = false;
 };
 
+/**
+ * An interface implemented by elements that are 'browsing context containers'
+ * in HTML5 terms (that is, elements such as iframe that creates a new
+ * browsing context):
+ *
+ * https://html.spec.whatwg.org/#browsing-context-container
+ *
+ * Object implementing this interface must implement nsFrameLoaderOwner in
+ * native C++ code.
+ */
 [NoInterfaceObject]
 interface MozFrameLoaderOwner {
   [ChromeOnly]
   readonly attribute FrameLoader? frameLoader;
 
   [ChromeOnly]
   readonly attribute BrowsingContext? browsingContext;
 
--- a/js/src/gc/ArenaList.h
+++ b/js/src/gc/ArenaList.h
@@ -287,17 +287,17 @@ class ArenaLists {
 
   // Arena lists which have yet to be swept, but need additional foreground
   // processing before they are swept.
   ZoneData<Arena*> gcShapeArenasToUpdate;
   ZoneData<Arena*> gcAccessorShapeArenasToUpdate;
   ZoneData<Arena*> gcScriptArenasToUpdate;
   ZoneData<Arena*> gcObjectGroupArenasToUpdate;
 
-  // The list of empty arenas which are collected during sweep phase and
+  // The list of empty arenas which are collected during the sweep phase and
   // released at the end of sweeping every sweep group.
   ZoneData<Arena*> savedEmptyArenas;
 
  public:
   explicit ArenaLists(JS::Zone* zone);
   ~ArenaLists();
 
   FreeLists& freeLists() { return freeLists_.ref(); }
@@ -345,20 +345,16 @@ class ArenaLists {
 
   bool foregroundFinalize(FreeOp* fop, AllocKind thingKind,
                           js::SliceBudget& sliceBudget,
                           SortedArenaList& sweepList);
   static void backgroundFinalize(FreeOp* fop, Arena* listHead, Arena** empty);
 
   void setParallelAllocEnabled(bool enabled);
 
-  // When finalizing arenas, whether to keep empty arenas on the list or
-  // release them immediately.
-  enum KeepArenasEnum { RELEASE_ARENAS, KEEP_ARENAS };
-
  private:
   inline JSRuntime* runtime();
   inline JSRuntime* runtimeFromAnyThread();
 
   inline void queueForForegroundSweep(FreeOp* fop, const FinalizePhase& phase);
   inline void queueForBackgroundSweep(FreeOp* fop, const FinalizePhase& phase);
   inline void queueForForegroundSweep(AllocKind thingKind);
   inline void queueForBackgroundSweep(AllocKind thingKind);
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -632,65 +632,56 @@ inline size_t Arena::finalize(FreeOp* fo
 }
 
 // Finalize arenas from src list, releasing empty arenas if keepArenas wasn't
 // specified and inserting the others into the appropriate destination size
 // bins.
 template <typename T>
 static inline bool FinalizeTypedArenas(FreeOp* fop, Arena** src,
                                        SortedArenaList& dest,
-                                       AllocKind thingKind, SliceBudget& budget,
-                                       ArenaLists::KeepArenasEnum keepArenas) {
+                                       AllocKind thingKind, SliceBudget& budget) {
   // When operating in the foreground, take the lock at the top.
   Maybe<AutoLockGC> maybeLock;
   if (fop->onMainThread()) {
     maybeLock.emplace(fop->runtime());
   }
 
-  // During background sweeping free arenas are released later on in
-  // sweepBackgroundThings().
-  MOZ_ASSERT_IF(!fop->onMainThread(), keepArenas == ArenaLists::KEEP_ARENAS);
-
   size_t thingSize = Arena::thingSize(thingKind);
   size_t thingsPerArena = Arena::thingsPerArena(thingKind);
 
   while (Arena* arena = *src) {
     *src = arena->next;
     size_t nmarked = arena->finalize<T>(fop, thingKind, thingSize);
     size_t nfree = thingsPerArena - nmarked;
 
     if (nmarked) {
       dest.insertAt(arena, nfree);
-    } else if (keepArenas == ArenaLists::KEEP_ARENAS) {
+    } else {
       arena->chunk()->recycleArena(arena, dest, thingsPerArena);
-    } else {
-      fop->runtime()->gc.releaseArena(arena, maybeLock.ref());
     }
 
     budget.step(thingsPerArena);
     if (budget.isOverBudget()) {
       return false;
     }
   }
 
   return true;
 }
 
 /*
  * Finalize the list of areans.
  */
 static bool FinalizeArenas(FreeOp* fop, Arena** src, SortedArenaList& dest,
-                           AllocKind thingKind, SliceBudget& budget,
-                           ArenaLists::KeepArenasEnum keepArenas) {
+                           AllocKind thingKind, SliceBudget& budget) {
   switch (thingKind) {
 #define EXPAND_CASE(allocKind, traceKind, type, sizedType, bgFinal, nursery, \
                     compact)                                                 \
   case AllocKind::allocKind:                                                 \
-    return FinalizeTypedArenas<type>(fop, src, dest, thingKind, budget,      \
-                                     keepArenas);
+    return FinalizeTypedArenas<type>(fop, src, dest, thingKind, budget);
     FOR_EACH_ALLOCKIND(EXPAND_CASE)
 #undef EXPAND_CASE
 
     default:
       MOZ_CRASH("Invalid alloc kind");
   }
 }
 
@@ -3147,18 +3138,17 @@ void ArenaLists::backgroundFinalize(Free
 
   AllocKind thingKind = listHead->getAllocKind();
   Zone* zone = listHead->zone;
 
   size_t thingsPerArena = Arena::thingsPerArena(thingKind);
   SortedArenaList finalizedSorted(thingsPerArena);
 
   auto unlimited = SliceBudget::unlimited();
-  FinalizeArenas(fop, &listHead, finalizedSorted, thingKind, unlimited,
-                 KEEP_ARENAS);
+  FinalizeArenas(fop, &listHead, finalizedSorted, thingKind, unlimited);
   MOZ_ASSERT(!listHead);
 
   finalizedSorted.extractEmpty(empty);
 
   // When arenas are queued for background finalization, all arenas are moved
   // to arenaListsToSweep[], leaving the arenaLists[] empty. However, new
   // arenas may be allocated before background finalization finishes; now that
   // finalization is complete, we want to merge these lists back together.
@@ -5461,28 +5451,31 @@ static void SweepCompressionTasks(GCPara
   auto& pending = HelperThreadState().compressionPendingList(lock);
   for (size_t i = 0; i < pending.length(); i++) {
     if (pending[i]->shouldCancel()) {
       HelperThreadState().remove(pending, &i);
     }
   }
 }
 
+void js::gc::SweepLazyScripts(GCParallelTask* task) {
+  JSRuntime* runtime = task->runtime();
+  for (SweepGroupZonesIter zone(runtime); !zone.done(); zone.next()) {
+    for (auto i = zone->cellIter<LazyScript>(); !i.done(); i.next()) {
+      WeakHeapPtrScript* edge = &i.unbarrieredGet()->script_;
+      if (*edge && IsAboutToBeFinalized(edge)) {
+        *edge = nullptr;
+      }
+    }
+  }
+}
+
 static void SweepWeakMaps(GCParallelTask* task) {
   JSRuntime* runtime = task->runtime();
   for (SweepGroupZonesIter zone(runtime); !zone.done(); zone.next()) {
-    /* Clear all weakrefs that point to unmarked things. */
-    for (auto edge : zone->gcWeakRefs()) {
-      /* Edges may be present multiple times, so may already be nulled. */
-      if (*edge && IsAboutToBeFinalizedDuringSweep(**edge)) {
-        *edge = nullptr;
-      }
-    }
-    zone->gcWeakRefs().clear();
-
     /* No need to look up any more weakmap keys from this sweep group. */
     AutoEnterOOMUnsafeRegion oomUnsafe;
     if (!zone->gcWeakKeys().clear()) {
       oomUnsafe.crash("clearing weak keys in beginSweepingSweepGroup()");
     }
 
     zone->sweepWeakMaps();
   }
@@ -5730,16 +5723,18 @@ IncrementalProgress GCRuntime::beginSwee
 
     AutoRunParallelTask sweepCCWrappers(rt, SweepCCWrappers,
                                         PhaseKind::SWEEP_CC_WRAPPER, lock);
     AutoRunParallelTask sweepObjectGroups(rt, SweepObjectGroups,
                                           PhaseKind::SWEEP_TYPE_OBJECT, lock);
     AutoRunParallelTask sweepMisc(rt, SweepMisc, PhaseKind::SWEEP_MISC, lock);
     AutoRunParallelTask sweepCompTasks(rt, SweepCompressionTasks,
                                        PhaseKind::SWEEP_COMPRESSION, lock);
+    AutoRunParallelTask sweepLazyScripts(rt, SweepLazyScripts,
+                                         PhaseKind::SWEEP_LAZYSCRIPTS, lock);
     AutoRunParallelTask sweepWeakMaps(rt, SweepWeakMaps,
                                       PhaseKind::SWEEP_WEAKMAPS, lock);
     AutoRunParallelTask sweepUniqueIds(rt, SweepUniqueIds,
                                        PhaseKind::SWEEP_UNIQUEIDS, lock);
 
     WeakCacheTaskVector sweepCacheTasks;
     if (!PrepareWeakCacheTasks(rt, &sweepCacheTasks)) {
       SweepWeakCachesOnMainThread(rt);
@@ -5870,34 +5865,31 @@ void GCRuntime::beginSweepPhase(JS::GCRe
 
 bool ArenaLists::foregroundFinalize(FreeOp* fop, AllocKind thingKind,
                                     SliceBudget& sliceBudget,
                                     SortedArenaList& sweepList) {
   if (!arenaListsToSweep(thingKind) && incrementalSweptArenas.ref().isEmpty()) {
     return true;
   }
 
-  // Empty object arenas are not released until all foreground GC things have
-  // been swept.
-  KeepArenasEnum keepArenas =
-      IsObjectAllocKind(thingKind) ? KEEP_ARENAS : RELEASE_ARENAS;
-
+  // Empty arenas are not released until all foreground finalized GC things in
+  // the current sweep group have been finalized.  This allows finalizers for
+  // other cells in the same sweep group to call IsAboutToBeFinalized on cells
+  // in this arena.
   if (!FinalizeArenas(fop, &arenaListsToSweep(thingKind), sweepList, thingKind,
-                      sliceBudget, keepArenas)) {
+                      sliceBudget)) {
     incrementalSweptArenaKind = thingKind;
     incrementalSweptArenas = sweepList.toArenaList();
     return false;
   }
 
   // Clear any previous incremental sweep state we may have saved.
   incrementalSweptArenas.ref().clear();
 
-  if (IsObjectAllocKind(thingKind)) {
-    sweepList.extractEmpty(&savedEmptyArenas.ref());
-  }
+  sweepList.extractEmpty(&savedEmptyArenas.ref());
 
   ArenaList finalized = sweepList.toArenaList();
   arenaLists(thingKind) =
       finalized.insertListWithCursorAtEnd(arenaLists(thingKind));
 
   return true;
 }
 
@@ -5979,23 +5971,24 @@ IncrementalProgress GCRuntime::sweepType
     gcstats::AutoPhase ap(stats(), gcstats::PhaseKind::SWEEP_TYPES_END);
     zone->types.endSweep(rt);
   }
 
   return Finished;
 }
 
 IncrementalProgress GCRuntime::releaseSweptEmptyArenas(FreeOp* fop,
-                                                       SliceBudget& budget,
-                                                       Zone* zone) {
-  // Foreground finalized objects have already been finalized, and now their
+                                                       SliceBudget& budget) {
+  // Foreground finalized GC things have already been finalized, and now their
   // arenas can be reclaimed by freeing empty ones and making non-empty ones
   // available for allocation.
 
-  zone->arenas.releaseForegroundSweptEmptyArenas();
+  for (SweepGroupZonesIter zone(rt); !zone.done(); zone.next()) {
+    zone->arenas.releaseForegroundSweptEmptyArenas();
+  }
   return Finished;
 }
 
 void GCRuntime::startSweepingAtomsTable() {
   auto& maybeAtoms = maybeAtomsToSweep.ref();
   MOZ_ASSERT(maybeAtoms.isNothing());
 
   AtomsTable* atomsTable = rt->atomsForSweeping();
@@ -6554,18 +6547,18 @@ bool GCRuntime::initSweepActions() {
                   Call(&GCRuntime::sweepTypeInformation),
                   MaybeYieldInZoneLoop(ZealMode::YieldBeforeSweepingObjects),
                   ForEachAllocKind(ForegroundObjectFinalizePhase.kinds,
                                    Call(&GCRuntime::finalizeAllocKind)),
                   MaybeYieldInZoneLoop(ZealMode::YieldBeforeSweepingNonObjects),
                   ForEachAllocKind(ForegroundNonObjectFinalizePhase.kinds,
                                    Call(&GCRuntime::finalizeAllocKind)),
                   MaybeYieldInZoneLoop(ZealMode::YieldBeforeSweepingShapeTrees),
-                  Call(&GCRuntime::sweepShapeTree),
-                  Call(&GCRuntime::releaseSweptEmptyArenas))),
+                  Call(&GCRuntime::sweepShapeTree))),
+          Call(&GCRuntime::releaseSweptEmptyArenas),
           Call(&GCRuntime::endSweepingSweepGroup)));
 
   return sweepActions != nullptr;
 }
 
 IncrementalProgress GCRuntime::performSweepActions(SliceBudget& budget) {
   // Marked GC things may vary between recording and replaying, so sweep
   // actions should not perform any recorded events.
--- a/js/src/gc/GCMarker.h
+++ b/js/src/gc/GCMarker.h
@@ -250,20 +250,16 @@ class GCMarker : public JSTracer {
   void traverseObjectEdge(S source, JSObject* target) {
     traverseEdge(source, target);
   }
   template <typename S>
   void traverseStringEdge(S source, JSString* target) {
     traverseEdge(source, target);
   }
 
-  // Notes a weak graph edge for later sweeping.
-  template <typename T>
-  void noteWeakEdge(T* edge);
-
   /*
    * Care must be taken changing the mark color from gray to black. The cycle
    * collector depends on the invariant that there are no black to gray edges
    * in the GC heap. This invariant lets the CC not trace through black
    * objects. If this invariant is violated, the cycle collector may free
    * objects that are still reachable.
    */
   void setMarkColorGray();
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -624,18 +624,17 @@ class GCRuntime {
   void markIncomingCrossCompartmentPointers(MarkColor color);
   IncrementalProgress beginSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
   void sweepDebuggerOnMainThread(FreeOp* fop);
   void sweepJitDataOnMainThread(FreeOp* fop);
   IncrementalProgress endSweepingSweepGroup(FreeOp* fop, SliceBudget& budget);
   IncrementalProgress performSweepActions(SliceBudget& sliceBudget);
   IncrementalProgress sweepTypeInformation(FreeOp* fop, SliceBudget& budget,
                                            Zone* zone);
-  IncrementalProgress releaseSweptEmptyArenas(FreeOp* fop, SliceBudget& budget,
-                                              Zone* zone);
+  IncrementalProgress releaseSweptEmptyArenas(FreeOp* fop, SliceBudget& budget);
   void startSweepingAtomsTable();
   IncrementalProgress sweepAtomsTable(FreeOp* fop, SliceBudget& budget);
   IncrementalProgress sweepWeakCaches(FreeOp* fop, SliceBudget& budget);
   IncrementalProgress finalizeAllocKind(FreeOp* fop, SliceBudget& budget,
                                         Zone* zone, AllocKind kind);
   IncrementalProgress sweepShapeTree(FreeOp* fop, SliceBudget& budget,
                                      Zone* zone);
   void endSweepPhase(bool lastGC);
--- a/js/src/gc/GenerateStatsPhases.py
+++ b/js/src/gc/GenerateStatsPhases.py
@@ -123,16 +123,17 @@ PhaseKindGraphRoots = [
             PhaseKind("SWEEP_INNER_VIEWS", "Sweep Inner Views", 22),
             PhaseKind("SWEEP_CC_WRAPPER", "Sweep Cross Compartment Wrappers", 23),
             PhaseKind("SWEEP_BASE_SHAPE", "Sweep Base Shapes", 24),
             PhaseKind("SWEEP_INITIAL_SHAPE", "Sweep Initial Shapes", 25),
             PhaseKind("SWEEP_TYPE_OBJECT", "Sweep Type Objects", 26),
             PhaseKind("SWEEP_BREAKPOINT", "Sweep Breakpoints", 27),
             PhaseKind("SWEEP_REGEXP", "Sweep Regexps", 28),
             PhaseKind("SWEEP_COMPRESSION", "Sweep Compression Tasks", 62),
+            PhaseKind("SWEEP_LAZYSCRIPTS", "Sweep LazyScripts", 71),
             PhaseKind("SWEEP_WEAKMAPS", "Sweep WeakMaps", 63),
             PhaseKind("SWEEP_UNIQUEIDS", "Sweep Unique IDs", 64),
             PhaseKind("SWEEP_JIT_DATA", "Sweep JIT Data", 65),
             PhaseKind("SWEEP_WEAK_CACHES", "Sweep Weak Caches", 66),
             PhaseKind("SWEEP_MISC", "Sweep Miscellaneous", 29),
             PhaseKind("SWEEP_TYPES", "Sweep type information", 30, [
                 PhaseKind("SWEEP_TYPES_BEGIN", "Sweep type tables and compilations", 31),
                 PhaseKind("SWEEP_TYPES_END", "Free type arena", 32),
--- a/js/src/gc/Heap-inl.h
+++ b/js/src/gc/Heap-inl.h
@@ -10,17 +10,17 @@
 #include "gc/Heap.h"
 
 #include "gc/StoreBuffer.h"
 #include "gc/Zone.h"
 
 inline void js::gc::Arena::init(JS::Zone* zoneArg, AllocKind kind,
                                 const AutoLockGC& lock) {
   MOZ_ASSERT(firstFreeSpan.isEmpty());
-  MOZ_ASSERT(!zone);
+  MOZ_ASSERT((uintptr_t(zone) & 0xff) == JS_FREED_ARENA_PATTERN);
   MOZ_ASSERT(!allocated());
   MOZ_ASSERT(!onDelayedMarkingList_);
   MOZ_ASSERT(!hasDelayedBlackMarking_);
   MOZ_ASSERT(!hasDelayedGrayMarking_);
   MOZ_ASSERT(!nextDelayedMarkingArena_);
 
   MOZ_MAKE_MEM_UNDEFINED(this, ArenaSize);
 
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -270,17 +270,21 @@ class Arena {
     FreeSpan* last = firstFreeSpan.nextSpanUnchecked(this);
     last->initAsEmpty();
   }
 
   // Initialize an arena to its unallocated state. For arenas that were
   // previously allocated for some zone, use release() instead.
   void setAsNotAllocated() {
     firstFreeSpan.initAsEmpty();
-    zone = nullptr;
+
+    // Poison zone pointer to highlight UAF on released arenas in crash data.
+    AlwaysPoison(&zone, JS_FREED_ARENA_PATTERN, sizeof(zone),
+                 MemCheckKind::MakeUndefined);
+
     allocKind = size_t(AllocKind::LIMIT);
     onDelayedMarkingList_ = 0;
     hasDelayedBlackMarking_ = 0;
     hasDelayedGrayMarking_ = 0;
     nextDelayedMarkingArena_ = 0;
     bufferedCells_ = nullptr;
   }
 
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -399,20 +399,16 @@ void js::gc::AssertRootMarkingPhase(JSTr
 template <typename T>
 T* DoCallback(JS::CallbackTracer* trc, T** thingp, const char* name);
 template <typename T>
 T DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name);
 template <typename T>
 void DoMarking(GCMarker* gcmarker, T* thing);
 template <typename T>
 void DoMarking(GCMarker* gcmarker, const T& thing);
-template <typename T>
-void NoteWeakEdge(GCMarker* gcmarker, T** thingp);
-template <typename T>
-void NoteWeakEdge(GCMarker* gcmarker, T* thingp);
 
 template <typename T>
 JS_PUBLIC_API void js::gc::TraceExternalEdge(JSTracer* trc, T* thingp,
                                              const char* name) {
   MOZ_ASSERT(InternalBarrierMethods<T>::isMarkable(*thingp));
   TraceEdgeInternal(trc, ConvertToBase(thingp), name);
 }
 
@@ -442,17 +438,16 @@ FOR_EACH_PUBLIC_GC_POINTER_TYPE(INSTANTI
 FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(INSTANTIATE_PUBLIC_TRACE_FUNCTIONS)
 #undef INSTANTIATE_PUBLIC_TRACE_FUNCTIONS
 
 namespace js {
 namespace gc {
 
 #define INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(type)                          \
   template void TraceEdgeInternal<type>(JSTracer*, type*, const char*);     \
-  template void TraceWeakEdgeInternal<type>(JSTracer*, type*, const char*); \
   template void TraceRangeInternal<type>(JSTracer*, size_t len, type*,      \
                                          const char*);
 
 #define INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND(_1, type, _2, _3) \
   INSTANTIATE_INTERNAL_TRACE_FUNCTIONS(type*)
 
 JS_FOR_EACH_TRACEKIND(INSTANTIATE_INTERNAL_TRACE_FUNCTIONS_FROM_TRACEKIND)
 FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(INSTANTIATE_INTERNAL_TRACE_FUNCTIONS)
@@ -564,29 +559,16 @@ void js::gc::TraceEdgeInternal(JSTracer*
   if (trc->isTenuringTracer()) {
     return static_cast<TenuringTracer*>(trc)->traverse(thingp);
   }
   MOZ_ASSERT(trc->isCallbackTracer());
   DoCallback(trc->asCallbackTracer(), thingp, name);
 }
 
 template <typename T>
-void js::gc::TraceWeakEdgeInternal(JSTracer* trc, T* thingp, const char* name) {
-  if (!trc->isMarkingTracer()) {
-    // Non-marking tracers can select whether or not they see weak edges.
-    if (trc->traceWeakEdges()) {
-      TraceEdgeInternal(trc, thingp, name);
-    }
-    return;
-  }
-
-  NoteWeakEdge(GCMarker::fromTracer(trc), thingp);
-}
-
-template <typename T>
 void js::gc::TraceRangeInternal(JSTracer* trc, size_t len, T* vec,
                                 const char* name) {
   JS::AutoTracingIndex index(trc);
   for (auto i : IntegerRange(len)) {
     if (InternalBarrierMethods<T>::isMarkable(vec[i])) {
       TraceEdgeInternal(trc, &vec[i], name);
     }
     ++index;
@@ -757,54 +739,16 @@ JS_PUBLIC_API void js::gc::PerformIncrem
   // Mark the argument, as DoMarking above.
   ApplyGCThingTyped(thing, [gcmarker](auto thing) {
                              MOZ_ASSERT(ShouldMark(gcmarker, thing));
                              CheckTracedThing(gcmarker, thing);
                              gcmarker->traverse(thing);
                            });
 }
 
-template <typename T>
-void NoteWeakEdge(GCMarker* gcmarker, T** thingp) {
-  // Do per-type marking precondition checks.
-  if (!ShouldMark(gcmarker, *thingp)) {
-    return;
-  }
-
-  CheckTracedThing(gcmarker, *thingp);
-
-  // If the target is already marked, there's no need to store the edge.
-  if (IsMarkedUnbarriered(gcmarker->runtime(), thingp)) {
-    return;
-  }
-
-  gcmarker->noteWeakEdge(thingp);
-}
-
-template <typename T>
-void NoteWeakEdge(GCMarker* gcmarker, T* thingp) {
-  MOZ_CRASH("the gc does not support tagged pointers as weak edges");
-}
-
-template <typename T>
-void js::GCMarker::noteWeakEdge(T* edge) {
-  static_assert(IsBaseOf<Cell, typename mozilla::RemovePointer<T>::Type>::value,
-                "edge must point to a GC pointer");
-  MOZ_ASSERT((*edge)->isTenured());
-
-  // Note: we really want the *source* Zone here. The edge may start in a
-  // non-gc heap location, however, so we use the fact that cross-zone weak
-  // references are not allowed and use the *target's* zone.
-  JS::Zone::WeakEdges& weakRefs = (*edge)->asTenured().zone()->gcWeakRefs();
-  AutoEnterOOMUnsafeRegion oomUnsafe;
-  if (!weakRefs.append(reinterpret_cast<TenuredCell**>(edge))) {
-    oomUnsafe.crash("Failed to record a weak edge for sweeping.");
-  }
-}
-
 // The simplest traversal calls out to the fully generic traceChildren function
 // to visit the child edges. In the absence of other traversal mechanisms, this
 // function will rapidly grow the stack past its bounds and crash the process.
 // Thus, this generic tracing should only be used in cases where subsequent
 // tracing will not recurse.
 template <typename T>
 void js::GCMarker::markAndTraceChildren(T* thing) {
   if (ThingIsPermanentAtomOrWellKnownSymbol(thing)) {
@@ -986,18 +930,18 @@ bool js::GCMarker::mark(T* thing) {
 
 /*** Inline, Eager GC Marking ***********************************************/
 
 // Each of the eager, inline marking paths is directly preceeded by the
 // out-of-line, generic tracing code for comparison. Both paths must end up
 // traversing equivalent subgraphs.
 
 void LazyScript::traceChildren(JSTracer* trc) {
-  if (script_) {
-    TraceWeakEdge(trc, &script_, "script");
+  if (trc->traceWeakEdges()) {
+    TraceNullableEdge(trc, &script_, "script");
   }
 
   if (function_) {
     TraceEdge(trc, &function_, "function");
   }
 
   if (sourceObject_) {
     TraceEdge(trc, &sourceObject_, "sourceObject");
@@ -1024,19 +968,17 @@ void LazyScript::traceChildren(JSTracer*
     }
   }
 
   if (trc->isMarkingTracer()) {
     GCMarker::fromTracer(trc)->markImplicitEdges(this);
   }
 }
 inline void js::GCMarker::eagerlyMarkChildren(LazyScript* thing) {
-  if (thing->script_) {
-    noteWeakEdge(thing->script_.unsafeUnbarrieredForTracing());
-  }
+  // script_ is weak so is not traced here.
 
   if (thing->function_) {
     traverseEdge(thing, static_cast<JSObject*>(thing->function_));
   }
 
   if (thing->sourceObject_) {
     traverseEdge(thing, static_cast<JSObject*>(thing->sourceObject_));
   }
@@ -1543,16 +1485,17 @@ bool GCMarker::markUntilBudgetExhausted(
       if (budget.isOverBudget()) {
         return false;
       }
     }
 
     if (hasGrayEntries()) {
       AutoSetMarkColor autoSetGray(*this, MarkColor::Gray);
       do {
+        MOZ_ASSERT(!hasBlackEntries());
         processMarkStackTop(budget);
         if (budget.isOverBudget()) {
           return false;
         }
       } while (hasGrayEntries());
     }
 
     if (!hasDelayedChildren()) {
--- a/js/src/gc/Tracer.h
+++ b/js/src/gc/Tracer.h
@@ -89,31 +89,39 @@ template <typename T>
 typename PtrBaseGCType<T>::type* ConvertToBase(T* thingp) {
   return reinterpret_cast<typename PtrBaseGCType<T>::type*>(thingp);
 }
 
 // Internal methods to trace edges.
 template <typename T>
 void TraceEdgeInternal(JSTracer* trc, T* thingp, const char* name);
 template <typename T>
-void TraceWeakEdgeInternal(JSTracer* trc, T* thingp, const char* name);
-template <typename T>
 void TraceRangeInternal(JSTracer* trc, size_t len, T* vec, const char* name);
 
 #ifdef DEBUG
 void AssertRootMarkingPhase(JSTracer* trc);
 #else
 inline void AssertRootMarkingPhase(JSTracer* trc) {}
 #endif
 
 }  // namespace gc
 
-// Trace through an edge in the live object graph on behalf of tracing. The
-// effect of tracing the edge depends on the JSTracer being used. For pointer
-// types, |*thingp| must not be null.
+// Trace through a strong edge in the live object graph on behalf of
+// tracing. The effect of tracing the edge depends on the JSTracer being
+// used. For pointer types, |*thingp| must not be null.
+//
+// Note that weak edges are handled separately. GC things with weak edges must
+// not trace those edges during marking tracing (which would keep the referent
+// alive) but instead arrange for the edge to be swept by calling
+// js::gc::IsAboutToBeFinalized during sweeping. For example, see the treatment
+// of the script_ edge in LazyScript::traceChildren and
+// js::gc::SweepLazyScripts.
+//
+// GC things that are weakly held in containers can use WeakMap or a container
+// wrapped in the WeakCache<> template to perform the appropriate sweeping.
 
 template <typename T>
 inline void TraceEdge(JSTracer* trc, WriteBarriered<T>* thingp,
                       const char* name) {
   gc::TraceEdgeInternal(
       trc, gc::ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name);
 }
 
@@ -180,26 +188,16 @@ inline void TraceNullableRoot(JSTracer* 
 // separate from TraceEdge to make accidental use of such edges more obvious.
 
 template <typename T>
 inline void TraceManuallyBarrieredEdge(JSTracer* trc, T* thingp,
                                        const char* name) {
   gc::TraceEdgeInternal(trc, gc::ConvertToBase(thingp), name);
 }
 
-// Visits a WeakRef, but does not trace its referents. If *thingp is not marked
-// at the end of marking, it is replaced by nullptr. This method records
-// thingp, so the edge location must not change after this function is called.
-
-template <typename T>
-inline void TraceWeakEdge(JSTracer* trc, WeakHeapPtr<T>* thingp, const char* name) {
-  gc::TraceWeakEdgeInternal(
-      trc, gc::ConvertToBase(thingp->unsafeUnbarrieredForTracing()), name);
-}
-
 // Trace all edges contained in the given array.
 
 template <typename T>
 void TraceRange(JSTracer* trc, size_t len, WriteBarriered<T>* vec,
                 const char* name) {
   gc::TraceRangeInternal(
       trc, len, gc::ConvertToBase(vec[0].unsafeUnbarrieredForTracing()), name);
 }
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -1,14 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "gc/Verifier.h"
+
 #include "mozilla/DebugOnly.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Sprintf.h"
 
 #ifdef MOZ_VALGRIND
 #  include <valgrind/memcheck.h>
 #endif
 
@@ -442,33 +444,16 @@ void js::gc::GCRuntime::finishVerifier()
     verifyPreData = nullptr;
   }
 }
 
 #endif /* JS_GC_ZEAL */
 
 #if defined(JS_GC_ZEAL) || defined(DEBUG)
 
-// Like gc::MarkColor but allows the possibility of the cell being
-// unmarked. Order is important here, with white being 'least marked'
-// and black being 'most marked'.
-enum class CellColor : uint8_t { White = 0, Gray = 1, Black = 2 };
-
-static CellColor GetCellColor(Cell* cell) {
-  if (cell->isMarkedBlack()) {
-    return CellColor::Black;
-  }
-
-  if (cell->isMarkedGray()) {
-    return CellColor::Gray;
-  }
-
-  return CellColor::White;
-}
-
 static const char* CellColorName(CellColor color) {
   switch (color) {
     case CellColor::White:
       return "white";
     case CellColor::Black:
       return "black";
     case CellColor::Gray:
       return "gray";
@@ -793,17 +778,17 @@ bool js::gc::CheckWeakMapEntryMarking(co
   // Values belonging to other runtimes or in uncollected zones are treated as
   // black.
   CellColor valueColor = CellColor::Black;
   if (value->runtimeFromAnyThread() == zone->runtimeFromAnyThread() &&
       valueZone->isGCMarking()) {
     valueColor = GetCellColor(value);
   }
 
-  if (valueColor < Min(mapColor, keyColor)) {
+  if (valueColor < ExpectedWeakMapValueColor(mapColor, keyColor)) {
     fprintf(stderr, "WeakMap value is less marked than map and key\n");
     fprintf(stderr, "(map %p is %s, key %p is %s, value %p is %s)\n", map,
             CellColorName(mapColor), key, CellColorName(keyColor), value,
             CellColorName(valueColor));
     ok = false;
   }
 
   // Debugger weak maps map have keys in zones that are not or are
@@ -822,17 +807,17 @@ bool js::gc::CheckWeakMapEntryMarking(co
   CellColor delegateColor;
   if (delegate->zone()->isGCMarking() || delegate->zone()->isGCSweeping()) {
     delegateColor = GetCellColor(delegate);
   } else {
     // IsMarked() assumes cells in uncollected zones are marked.
     delegateColor = CellColor::Black;
   }
 
-  if (keyColor < Min(mapColor, delegateColor)) {
+  if (keyColor < ExpectedWeakMapKeyColor(mapColor, delegateColor)) {
     fprintf(stderr, "WeakMap key is less marked than map and delegate\n");
     fprintf(stderr, "(map %p is %s, delegate %p is %s, key %p is %s)\n", map,
             CellColorName(mapColor), delegate, CellColorName(delegateColor),
             key, CellColorName(keyColor));
     ok = false;
   }
 
   return ok;
new file mode 100644
--- /dev/null
+++ b/js/src/gc/Verifier.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Internal header for definitions shared between the verifier and jsapi tests.
+ */
+
+#ifndef gc_Verifier_h
+#define gc_Verifier_h
+
+#include "gc/Cell.h"
+
+namespace js {
+namespace gc {
+
+// Like gc::MarkColor but allows the possibility of the cell being
+// unmarked. Order is important here, with white being 'least marked'
+// and black being 'most marked'.
+enum class CellColor : uint8_t { White = 0, Gray = 1, Black = 2 };
+
+static constexpr CellColor AllCellColors[] = {
+  CellColor::White, CellColor::Gray, CellColor::Black
+};
+
+static constexpr CellColor MarkedCellColors[] = {
+  CellColor::Gray, CellColor::Black
+};
+
+inline CellColor GetCellColor(Cell* cell) {
+  if (cell->isMarkedBlack()) {
+    return CellColor::Black;
+  }
+
+  if (cell->isMarkedGray()) {
+    return CellColor::Gray;
+  }
+
+  return CellColor::White;
+}
+
+inline CellColor ExpectedWeakMapValueColor(CellColor keyColor,
+                                           CellColor mapColor) {
+  return Min(keyColor, mapColor);
+}
+
+inline CellColor ExpectedWeakMapKeyColor(CellColor mapColor,
+                                         CellColor delegateColor) {
+  return Min(mapColor, delegateColor);
+}
+
+inline CellColor ExpectedKeyAndDelegateColor(CellColor keyColor,
+                                             CellColor delegateColor) {
+  return Max(keyColor, delegateColor);
+}
+
+} // namespace gc
+} // namespace js
+
+#endif /* gc_Verifier_h */
--- a/js/src/gc/WeakMap-inl.h
+++ b/js/src/gc/WeakMap-inl.h
@@ -91,16 +91,25 @@ template <class K, class V>
 void WeakMap<K, V>::trace(JSTracer* trc) {
   MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy(), isInList());
 
   TraceNullableEdge(trc, &memberOf, "WeakMap owner");
 
   if (trc->isMarkingTracer()) {
     MOZ_ASSERT(trc->weakMapAction() == ExpandWeakMaps);
     auto marker = GCMarker::fromTracer(trc);
+
+    // Don't change the map color from black to gray. This can happen when a
+    // barrier pushes the map object onto the black mark stack when it's already
+    // present on the gray mark stack, which is marked later.
+    if (marked && markColor == gc::MarkColor::Black &&
+        marker->markColor() == gc::MarkColor::Gray) {
+      return;
+    }
+
     marked = true;
     markColor = marker->markColor();
     (void)markIteratively(marker);
     return;
   }
 
   if (trc->weakMapAction() == DoNotTraceWeakMaps) {
     return;
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -37,17 +37,16 @@ JS::Zone::Zone(JSRuntime* rt)
       uniqueIds_(this),
       suppressAllocationMetadataBuilder(this, false),
       arenas(this),
       tenuredAllocsSinceMinorGC_(0),
       types(this),
       gcWeakMapList_(this),
       compartments_(),
       gcGrayRoots_(this),
-      gcWeakRefs_(this),
       weakCaches_(this),
       gcWeakKeys_(this, SystemAllocPolicy(), rt->randomHashCodeScrambler()),
       typeDescrObjects_(this, this),
       markedAtoms_(this),
       atomCache_(this),
       externalStringCache_(this),
       functionToStringCache_(this),
       keepAtomsCount(this, 0),
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -384,26 +384,16 @@ class Zone : public JS::shadow::Zone,
                                js::SystemAllocPolicy>;
 
  private:
   js::ZoneOrGCTaskData<GrayRootVector> gcGrayRoots_;
 
  public:
   GrayRootVector& gcGrayRoots() { return gcGrayRoots_.ref(); }
 
-  // This zone's weak edges found via graph traversal during marking,
-  // preserved for re-scanning during sweeping.
-  using WeakEdges = js::Vector<js::gc::TenuredCell**, 0, js::SystemAllocPolicy>;
-
- private:
-  js::ZoneOrGCTaskData<WeakEdges> gcWeakRefs_;
-
- public:
-  WeakEdges& gcWeakRefs() { return gcWeakRefs_.ref(); }
-
  private:
   // List of non-ephemeron weak containers to sweep during
   // beginSweepingSweepGroup.
   js::ZoneOrGCTaskData<mozilla::LinkedList<detail::WeakCacheBase>> weakCaches_;
 
  public:
   mozilla::LinkedList<detail::WeakCacheBase>& weakCaches() {
     return weakCaches_.ref();
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -53,17 +53,16 @@ UNIFIED_SOURCES += [
     'testGCGrayMarking.cpp',
     'testGCHeapPostBarriers.cpp',
     'testGCHooks.cpp',
     'testGCMarking.cpp',
     'testGCOutOfMemory.cpp',
     'testGCStoreBufferRemoval.cpp',
     'testGCUniqueId.cpp',
     'testGCWeakCache.cpp',
-    'testGCWeakRef.cpp',
     'testGetPropertyDescriptor.cpp',
     'testHashTable.cpp',
     'testIndexToString.cpp',
     'testInformalValueTypeName.cpp',
     'testIntern.cpp',
     'testIntlAvailableLocales.cpp',
     'testIntString.cpp',
     'testIsInsideNursery.cpp',
--- a/js/src/jsapi-tests/testGCGrayMarking.cpp
+++ b/js/src/jsapi-tests/testGCGrayMarking.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sts=2 et sw=2 tw=80:
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gc/Heap.h"
+#include "gc/Verifier.h"
 #include "gc/WeakMap.h"
 #include "gc/Zone.h"
 #include "js/Proxy.h"
 #include "jsapi-tests/tests.h"
 
 using namespace js;
 using namespace js::gc;
 
@@ -51,19 +52,24 @@ BEGIN_TEST(testGCGrayMarking) {
   AutoLeaveZeal nozeal(cx);
 #endif /* JS_GC_ZEAL */
 
   CHECK(InitGlobals());
   JSAutoRealm ar(cx, global1);
 
   InitGrayRootTracer();
 
-  bool ok = TestMarking() && TestWeakMaps() && TestUnassociatedWeakMaps() &&
+  // Enable incremental GC.
+  JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_ZONE_INCREMENTAL);
+
+  bool ok = TestMarking() && TestJSWeakMaps() && TestInternalWeakMaps() &&
             TestCCWs() && TestGrayUnmarking();
 
+  JS_SetGCParameter(cx, JSGC_MODE, JSGC_MODE_GLOBAL);
+
   global1 = nullptr;
   global2 = nullptr;
   RemoveGrayRootTracer();
 
   return ok;
 }
 
 bool TestMarking() {
@@ -144,288 +150,324 @@ bool TestMarking() {
   blackRoot1 = nullptr;
   blackRoot2 = nullptr;
   grayRoots.grayRoot1 = nullptr;
   grayRoots.grayRoot2 = nullptr;
 
   return true;
 }
 
-bool TestWeakMaps() {
-  JSObject* weakMap = JS::NewWeakMapObject(cx);
-  CHECK(weakMap);
+static const CellColor DontMark = CellColor::White;
+
+enum MarkKeyOrDelegate : bool { MarkKey = true, MarkDelegate = false };
 
-  JSObject* key;
-  JSObject* value;
-  {
-    JS::RootedObject rootedMap(cx, weakMap);
-
-    key = AllocWeakmapKeyObject();
-    CHECK(key);
-
-    value = AllocPlainObject();
-    CHECK(value);
-
-    weakMap = rootedMap;
+bool TestJSWeakMaps() {
+  for (auto keyOrDelegateColor : MarkedCellColors) {
+    for (auto mapColor : MarkedCellColors) {
+      for (auto markKeyOrDelegate : { MarkKey, MarkDelegate }) {
+        CellColor expected = ExpectedWeakMapValueColor(keyOrDelegateColor,
+                                                       mapColor);
+        CHECK(TestJSWeakMap(markKeyOrDelegate, keyOrDelegateColor, mapColor,
+                            expected));
+#ifdef JS_GC_ZEAL
+        CHECK(TestJSWeakMapWithGrayUnmarking(markKeyOrDelegate,
+                                             keyOrDelegateColor, mapColor,
+                                             expected));
+#endif
+      }
+    }
   }
 
-  {
-    JS::RootedObject rootedMap(cx, weakMap);
-    JS::RootedObject rootedKey(cx, key);
-    JS::RootedValue rootedValue(cx, ObjectValue(*value));
-    CHECK(SetWeakMapEntry(cx, rootedMap, rootedKey, rootedValue));
+  return true;
+}
+
+bool TestInternalWeakMaps() {
+  for (auto keyMarkColor : AllCellColors) {
+    for (auto delegateMarkColor : AllCellColors) {
+      if (keyMarkColor == CellColor::White &&
+          delegateMarkColor == CellColor::White) {
+        continue;
+      }
+
+      CellColor keyOrDelegateColor =
+          ExpectedKeyAndDelegateColor(keyMarkColor, delegateMarkColor);
+      CellColor expected = ExpectedWeakMapValueColor(keyOrDelegateColor,
+                                                     CellColor::Black);
+      CHECK(TestInternalWeakMap(keyMarkColor, delegateMarkColor, expected));
+
+#ifdef JS_GC_ZEAL
+      CHECK(TestInternalWeakMapWithGrayUnmarking(keyMarkColor,
+                                                 delegateMarkColor,
+                                                 expected));
+#endif
+    }
   }
 
-  // Test the value of a weakmap entry is marked gray by GC if both the
-  // weakmap and key are marked gray.
+  return true;
+}
 
-  grayRoots.grayRoot1 = weakMap;
-  grayRoots.grayRoot2 = key;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(weakMap));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(value));
-
-  // Test the value of a weakmap entry is marked gray by GC if one of the
-  // weakmap and the key is marked gray and the other black.
+bool TestJSWeakMap(MarkKeyOrDelegate markKey, CellColor weakMapMarkColor,
+                   CellColor keyOrDelegateMarkColor,
+                   CellColor expectedValueColor) {
+  // Test marking a JS WeakMap object.
+  //
+  // This marks the map and one of the key or delegate. The key/delegate and the
+  // value can end up different colors depending on the color of the map.
 
-  JS::RootedObject blackRoot1(cx);
-  blackRoot1 = weakMap;
-  grayRoots.grayRoot1 = key;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(value));
+  JSObject* weakMap;
+  JSObject* key;
+  JSObject* value;
 
-  blackRoot1 = key;
-  grayRoots.grayRoot1 = weakMap;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(weakMap));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedGray(value));
+  // If both map and key are marked the same color, test both possible
+  // orderings.
+  unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1;
+
+  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+    CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value));
+
+    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+    JSObject* keyOrDelegate = markKey ? key : delegate;
 
-  // Test the value of a weakmap entry is marked black by GC if both the
-  // weakmap and the key are marked black.
-
-  JS::RootedObject blackRoot2(cx);
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
+    RootedObject blackRoot1(cx);
+    RootedObject blackRoot2(cx);
 
-  blackRoot1 = weakMap;
-  blackRoot2 = key;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(value));
+    RootObject(weakMap, weakMapMarkColor, blackRoot1, grayRoots.grayRoot1);
+    RootObject(keyOrDelegate, keyOrDelegateMarkColor, blackRoot2,
+               grayRoots.grayRoot2);
+
+    if (markOrder != 0) {
+      mozilla::Swap(blackRoot1.get(), blackRoot2.get());
+      mozilla::Swap(grayRoots.grayRoot1, grayRoots.grayRoot2);
+    }
 
-  blackRoot1 = key;
-  blackRoot2 = weakMap;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(value));
+    JS_GC(cx);
 
-  // Test that a weakmap key is marked gray if it has a gray delegate and the
-  // map is either gray or black.
+    ClearGrayRoots();
 
-  JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
-  blackRoot1 = weakMap;
-  blackRoot2 = nullptr;
-  grayRoots.grayRoot1 = delegate;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(delegate));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedGray(value));
+    CHECK(GetCellColor(weakMap) == weakMapMarkColor);
+    CHECK(GetCellColor(keyOrDelegate) == keyOrDelegateMarkColor);
+    CHECK(GetCellColor(value) == expectedValueColor);
+  }
+
+  return true;
+}
+
+#ifdef JS_GC_ZEAL
 
-  blackRoot1 = nullptr;
-  blackRoot2 = nullptr;
-  grayRoots.grayRoot1 = weakMap;
-  grayRoots.grayRoot2 = delegate;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(delegate));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(weakMap));
-  CHECK(IsMarkedGray(value));
+bool TestJSWeakMapWithGrayUnmarking(MarkKeyOrDelegate markKey,
+                                    CellColor weakMapMarkColor,
+                                    CellColor keyOrDelegateMarkColor,
+                                    CellColor expectedValueColor) {
+  // This is like the previous test, but things are marked black by gray
+  // unmarking during incremental GC.
 
-  // Test that a weakmap key is marked gray if it has a black delegate but
-  // the map is gray.
+  JSObject* weakMap;
+  JSObject* key;
+  JSObject* value;
 
-  blackRoot1 = delegate;
-  blackRoot2 = nullptr;
-  grayRoots.grayRoot1 = weakMap;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(delegate));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(weakMap));
-  CHECK(IsMarkedGray(value));
+  // If both map and key are marked the same color, test both possible
+  // orderings.
+  unsigned markOrderings = weakMapMarkColor == keyOrDelegateMarkColor ? 2 : 1;
+
+  JS_SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0);
 
-  blackRoot1 = delegate;
-  blackRoot2 = nullptr;
-  grayRoots.grayRoot1 = weakMap;
-  grayRoots.grayRoot2 = key;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(delegate));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(weakMap));
-  CHECK(IsMarkedGray(value));
+  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+    CHECK(CreateJSWeakMapObjects(&weakMap, &key, &value));
 
-  // Test that a weakmap key is marked black if it has a black delegate and
-  // the map is black.
+    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+    JSObject* keyOrDelegate = markKey ? key : delegate;
+
+    grayRoots.grayRoot1 = keyOrDelegate;
+    grayRoots.grayRoot2 = weakMap;
 
-  blackRoot1 = delegate;
-  blackRoot2 = weakMap;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(delegate));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedBlack(value));
-
-  blackRoot1 = delegate;
-  blackRoot2 = weakMap;
-  grayRoots.grayRoot1 = key;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(delegate));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedBlack(value));
-
-  // Test what happens if there is a delegate but it is not marked for both
-  // black and gray cases.
+    // Start an incremental GC and run until gray roots have been pushed onto
+    // the mark stack.
+    JS::PrepareForFullGC(cx);
+    JS::StartIncrementalGC(cx, GC_NORMAL, JS::GCReason::DEBUG_GC, 1000000);
+    MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep);
+    MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray);
 
-  delegate = nullptr;
-  blackRoot1 = key;
-  blackRoot2 = weakMap;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(weakMap));
-  CHECK(IsMarkedBlack(value));
+    // Unmark gray things as specified.
+    if (markOrder != 0) {
+      MaybeExposeObject(weakMap, weakMapMarkColor);
+      MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor);
+    } else {
+      MaybeExposeObject(keyOrDelegate, keyOrDelegateMarkColor);
+      MaybeExposeObject(weakMap, weakMapMarkColor);
+    }
 
-  blackRoot1 = nullptr;
-  blackRoot2 = nullptr;
-  grayRoots.grayRoot1 = weakMap;
-  grayRoots.grayRoot2 = key;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(weakMap));
-  CHECK(IsMarkedGray(value));
+    JS::FinishIncrementalGC(cx, JS::GCReason::API);
+
+    ClearGrayRoots();
 
-  blackRoot1 = nullptr;
-  blackRoot2 = nullptr;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
+    CHECK(GetCellColor(weakMap) == weakMapMarkColor);
+    CHECK(GetCellColor(keyOrDelegate) == keyOrDelegateMarkColor);
+    CHECK(GetCellColor(value) == expectedValueColor);
+  }
+
+  JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));
 
   return true;
 }
 
-bool TestUnassociatedWeakMaps() {
-  // Make a weakmap that's not associated with a JSObject.
-  auto weakMap = cx->make_unique<GCManagedObjectWeakMap>(cx);
+static void MaybeExposeObject(JSObject* object, CellColor color) {
+  if (color == CellColor::Black) {
+      JS::ExposeObjectToActiveJS(object);
+  }
+}
+
+#endif // JS_GC_ZEAL
+
+bool CreateJSWeakMapObjects(JSObject** weakMapOut, JSObject** keyOut,
+                            JSObject** valueOut) {
+  RootedObject key(cx, AllocWeakmapKeyObject());
+  CHECK(key);
+
+  RootedObject value(cx, AllocPlainObject());
+  CHECK(value);
+
+  RootedObject weakMap(cx, JS::NewWeakMapObject(cx));
   CHECK(weakMap);
 
-  // Make sure this gets traced during GC.
-  Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
+  JS::RootedValue valueValue(cx, ObjectValue(*value));
+  CHECK(SetWeakMapEntry(cx, weakMap, key, valueValue));
+
+  *weakMapOut = weakMap;
+  *keyOut = key;
+  *valueOut = value;
+  return true;
+}
+
+bool TestInternalWeakMap(CellColor keyMarkColor, CellColor delegateMarkColor,
+                         CellColor expectedColor) {
+  // Test marking for internal weakmaps (without an owning JSObject).
+  //
+  // All of the key, delegate and value are expected to end up the same color.
+
+  UniquePtr<GCManagedObjectWeakMap> weakMap;
+  JSObject* key;
+  JSObject* value;
+
+  // If both key and delegate are marked the same color, test both possible
+  // orderings.
+  unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1;
+
+  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+    CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value));
+
+    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+
+    RootedObject blackRoot1(cx);
+    RootedObject blackRoot2(cx);
+
+    Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
+    RootObject(key, keyMarkColor, blackRoot1, grayRoots.grayRoot1);
+    RootObject(delegate, delegateMarkColor, blackRoot2, grayRoots.grayRoot2);
+
+    if (markOrder != 0) {
+      mozilla::Swap(blackRoot1.get(), blackRoot2.get());
+      mozilla::Swap(grayRoots.grayRoot1, grayRoots.grayRoot2);
+    }
+
+    JS_GC(cx);
+
+    ClearGrayRoots();
+
+    CHECK(GetCellColor(key) == expectedColor);
+    CHECK(GetCellColor(delegate) == expectedColor);
+    CHECK(GetCellColor(value) == expectedColor);
+  }
+
+  return true;
+}
+
+#ifdef JS_GC_ZEAL
 
-  JSObject* key = AllocWeakmapKeyObject();
+bool TestInternalWeakMapWithGrayUnmarking(CellColor keyMarkColor,
+                                          CellColor delegateMarkColor,
+                                          CellColor expectedColor) {
+  UniquePtr<GCManagedObjectWeakMap> weakMap;
+  JSObject* key;
+  JSObject* value;
+
+  // If both key and delegate are marked the same color, test both possible
+  // orderings.
+  unsigned markOrderings = keyMarkColor == delegateMarkColor ? 2 : 1;
+
+  JS_SetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking), 0);
+
+  for (unsigned markOrder = 0; markOrder < markOrderings; markOrder++) {
+    CHECK(CreateInternalWeakMapObjects(&weakMap, &key, &value));
+
+    JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
+
+    Rooted<GCManagedObjectWeakMap*> rootMap(cx, weakMap.get());
+    grayRoots.grayRoot1 = key;
+    grayRoots.grayRoot2 = delegate;
+
+    // Start an incremental GC and run until gray roots have been pushed onto
+    // the mark stack.
+    JS::PrepareForFullGC(cx);
+    JS::StartIncrementalGC(cx, GC_NORMAL, JS::GCReason::DEBUG_GC, 1000000);
+    MOZ_ASSERT(cx->runtime()->gc.state() == gc::State::Sweep);
+    MOZ_ASSERT(cx->zone()->gcState() == Zone::MarkBlackAndGray);
+
+    // Unmark gray things as specified.
+    if (markOrder != 0) {
+      MaybeExposeObject(key, keyMarkColor);
+      MaybeExposeObject(delegate, delegateMarkColor);
+    } else {
+      MaybeExposeObject(key, keyMarkColor);
+      MaybeExposeObject(delegate, delegateMarkColor);
+    }
+
+    JS::FinishIncrementalGC(cx, JS::GCReason::API);
+
+    ClearGrayRoots();
+
+    CHECK(GetCellColor(key) == expectedColor);
+    CHECK(GetCellColor(delegate) == expectedColor);
+    CHECK(GetCellColor(value) == expectedColor);
+  }
+
+  JS_UnsetGCZeal(cx, uint8_t(ZealMode::YieldWhileGrayMarking));
+
+  return true;
+}
+
+#endif // JS_GC_ZEAL
+
+bool CreateInternalWeakMapObjects(UniquePtr<GCManagedObjectWeakMap>* weakMapOut,
+                                  JSObject** keyOut, JSObject** valueOut) {
+  RootedObject key(cx, AllocWeakmapKeyObject());
   CHECK(key);
 
-  JSObject* value = AllocPlainObject();
+  RootedObject value(cx, AllocPlainObject());
   CHECK(value);
 
+  auto weakMap = cx->make_unique<GCManagedObjectWeakMap>(cx);
+  CHECK(weakMap);
+
   CHECK(weakMap->add(cx, key, value));
 
-  // Test the value of a weakmap entry is marked gray by GC if the
-  // key is marked gray.
-
-  grayRoots.grayRoot1 = key;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(value));
-
-  // Test the value of a weakmap entry is marked gray by GC if the key is marked
-  // gray.
-
-  grayRoots.grayRoot1 = key;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(value));
-
-  // Test the value of a weakmap entry is marked black by GC if the key is
-  // marked black.
-
-  JS::RootedObject blackRoot(cx);
-  blackRoot = key;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(value));
-
-  // Test that a weakmap key is marked gray if it has a gray delegate.
-
-  JSObject* delegate = UncheckedUnwrapWithoutExpose(key);
-  blackRoot = nullptr;
-  grayRoots.grayRoot1 = delegate;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(delegate));
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(value));
+  *weakMapOut = std::move(weakMap);
+  *keyOut = key;
+  *valueOut = value;
+  return true;
+}
 
-  // Test that a weakmap key is marked black if it has a black delegate.
-
-  blackRoot = delegate;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(delegate));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(value));
-
-  blackRoot = delegate;
-  grayRoots.grayRoot1 = key;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(delegate));
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(value));
-
-  // Test what happens if there is a delegate but it is not marked for both
-  // black and gray cases.
-
-  delegate = nullptr;
-  blackRoot = key;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedBlack(key));
-  CHECK(IsMarkedBlack(value));
-
-  blackRoot = nullptr;
-  grayRoots.grayRoot1 = key;
-  grayRoots.grayRoot2 = nullptr;
-  JS_GC(cx);
-  CHECK(IsMarkedGray(key));
-  CHECK(IsMarkedGray(value));
-
-  blackRoot = nullptr;
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
-
-  return true;
+void RootObject(JSObject* object, CellColor color, RootedObject& blackRoot,
+                JS::Heap<JSObject*>& grayRoot) {
+  if (color == CellColor::Black) {
+    blackRoot = object;
+  } else if (color == CellColor::Gray) {
+    grayRoot = object;
+  } else {
+    MOZ_RELEASE_ASSERT(color == CellColor::White);
+  }
 }
 
 bool TestCCWs() {
   JSObject* target = AllocPlainObject();
   CHECK(target);
 
   // Test getting a new wrapper doesn't return a gray wrapper.
 
@@ -589,25 +631,28 @@ bool InitGlobals() {
   global1.init(cx, global);
   if (!createGlobal()) {
     return false;
   }
   global2.init(cx, global);
   return global2 != nullptr;
 }
 
-void InitGrayRootTracer() {
+void ClearGrayRoots() {
   grayRoots.grayRoot1 = nullptr;
   grayRoots.grayRoot2 = nullptr;
+}
+
+void InitGrayRootTracer() {
+  ClearGrayRoots();
   JS_SetGrayGCRootsTracer(cx, TraceGrayRoots, &grayRoots);
 }
 
 void RemoveGrayRootTracer() {
-  grayRoots.grayRoot1 = nullptr;
-  grayRoots.grayRoot2 = nullptr;
+  ClearGrayRoots();
   JS_SetGrayGCRootsTracer(cx, nullptr, nullptr);
 }
 
 static void TraceGrayRoots(JSTracer* trc, void* data) {
   auto grayRoots = static_cast<GrayRoots*>(data);
   TraceEdge(trc, &grayRoots->grayRoot1, "gray root 1");
   TraceEdge(trc, &grayRoots->grayRoot2, "gray root 2");
 }
deleted file mode 100644
--- a/js/src/jsapi-tests/testGCWeakRef.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- * vim: set ts=8 sts=2 et sw=2 tw=80:
- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "gc/Barrier.h"
-#include "js/RootingAPI.h"
-
-#include "jsapi-tests/tests.h"
-
-struct MyHeap {
-  explicit MyHeap(JSObject* obj) : weak(obj) {}
-  js::WeakHeapPtrObject weak;
-
-  void trace(JSTracer* trc) { js::TraceWeakEdge(trc, &weak, "weak"); }
-};
-
-BEGIN_TEST(testGCWeakRef) {
-  // Create an object and add a property to it so that we can read the
-  // property back later to verify that object internals are not garbage.
-  JS::RootedObject obj(cx, JS_NewPlainObject(cx));
-  CHECK(obj);
-  CHECK(JS_DefineProperty(cx, obj, "x", 42, 0));
-
-  // Store the object behind a weak pointer and remove other references.
-  JS::Rooted<MyHeap> heap(cx, MyHeap(obj));
-  obj = nullptr;
-
-  cx->runtime()->gc.minorGC(JS::GCReason::API);
-
-  // The minor collection should have treated the weak ref as a strong ref,
-  // so the object should still be live, despite not having any other live
-  // references.
-  CHECK(heap.get().weak.unbarrieredGet() != nullptr);
-  obj = heap.get().weak;
-  JS::RootedValue v(cx);
-  CHECK(JS_GetProperty(cx, obj, "x", &v));
-  CHECK(v.isInt32());
-  CHECK(v.toInt32() == 42);
-
-  // A full collection with a second ref should keep the object as well.
-  CHECK(obj == heap.get().weak);
-  JS_GC(cx);
-  CHECK(obj == heap.get().weak);
-  v = JS::UndefinedValue();
-  CHECK(JS_GetProperty(cx, obj, "x", &v));
-  CHECK(v.isInt32());
-  CHECK(v.toInt32() == 42);
-
-  // A full collection after nulling the root should collect the object, or
-  // at least null out the weak reference before returning to the mutator.
-  obj = nullptr;
-  JS_GC(cx);
-  CHECK(heap.get().weak == nullptr);
-
-  return true;
-}
-END_TEST(testGCWeakRef)
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -247,16 +247,17 @@ const uint8_t JS_FRESH_NURSERY_PATTERN =
 const uint8_t JS_SWEPT_NURSERY_PATTERN = 0x2B;
 const uint8_t JS_ALLOCATED_NURSERY_PATTERN = 0x2D;
 const uint8_t JS_FRESH_TENURED_PATTERN = 0x4F;
 const uint8_t JS_MOVED_TENURED_PATTERN = 0x49;
 const uint8_t JS_SWEPT_TENURED_PATTERN = 0x4B;
 const uint8_t JS_ALLOCATED_TENURED_PATTERN = 0x4D;
 const uint8_t JS_FREED_HEAP_PTR_PATTERN = 0x6B;
 const uint8_t JS_FREED_CHUNK_PATTERN = 0x8B;
+const uint8_t JS_FREED_ARENA_PATTERN = 0x9B;
 const uint8_t JS_SWEPT_TI_PATTERN = 0x6F;
 const uint8_t JS_FRESH_MARK_STACK_PATTERN = 0x9F;
 
 /*
  * Ensure JS_SWEPT_CODE_PATTERN is a byte pattern that will crash immediately
  * when executed, so either an undefined instruction or an instruction that's
  * illegal in user mode.
  */
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -64,29 +64,34 @@ struct IonScriptCounts;
 #define ION_PENDING_SCRIPT ((js::jit::IonScript*)0x3)
 
 #define BASELINE_DISABLED_SCRIPT ((js::jit::BaselineScript*)0x1)
 
 class AutoKeepTypeScripts;
 class AutoSweepTypeScript;
 class BreakpointSite;
 class Debugger;
+class GCParallelTask;
 class LazyScript;
 class ModuleObject;
 class RegExpObject;
 class SourceCompressionTask;
 class Shape;
 class TypeScript;
 
 namespace frontend {
 struct BytecodeEmitter;
 class FunctionBox;
 class ModuleSharedContext;
 }  // namespace frontend
 
+namespace gc {
+void SweepLazyScripts(GCParallelTask* task);
+} // namespace gc
+
 namespace detail {
 
 // Do not call this directly! It is exposed for the friend declarations in
 // this file.
 JSScript* CopyScript(JSContext* cx, HandleScript src,
                      HandleScriptSourceObject sourceObject,
                      MutableHandle<GCVector<Scope*>> scopes);
 
@@ -3020,16 +3025,17 @@ class alignas(uintptr_t) LazyScriptData 
 
 // Information about a script which may be (or has been) lazily compiled to
 // bytecode from its source.
 class LazyScript : public gc::TenuredCell {
   // If non-nullptr, the script has been compiled and this is a forwarding
   // pointer to the result. This is a weak pointer: after relazification, we
   // can collect the script if there are no other pointers to it.
   WeakHeapPtrScript script_;
+  friend void js::gc::SweepLazyScripts(GCParallelTask* task);
 
   // Original function with which the lazy script is associated.
   GCPtrFunction function_;
 
   // This field holds one of:
   //   * LazyScript in which the script is nested.  This case happens if the
   //     enclosing script is lazily parsed and have never been compiled.
   //
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -1407,18 +1407,17 @@ bool Instance::memoryAccessInBounds(uint
   }
 
   return true;
 }
 
 void Instance::tracePrivate(JSTracer* trc) {
   // This method is only called from WasmInstanceObject so the only reason why
   // TraceEdge is called is so that the pointer can be updated during a moving
-  // GC. TraceWeakEdge may sound better, but it is less efficient given that
-  // we know object_ is already marked.
+  // GC.
   MOZ_ASSERT(!gc::IsAboutToBeFinalized(&object_));
   TraceEdge(trc, &object_, "wasm instance object");
 
   // OK to just do one tier here; though the tiers have different funcImports
   // tables, they share the tls object.
   for (const FuncImport& fi : metadata(code().stableTier()).funcImports) {
     TraceNullableEdge(trc, &funcImportTls(fi).fun, "wasm import");
   }
--- a/js/src/wasm/WasmTable.cpp
+++ b/js/src/wasm/WasmTable.cpp
@@ -77,18 +77,17 @@ SharedTable Table::create(JSContext* cx,
       MOZ_CRASH();
   }
 }
 
 void Table::tracePrivate(JSTracer* trc) {
   // If this table has a WasmTableObject, then this method is only called by
   // WasmTableObject's trace hook so maybeObject_ must already be marked.
   // TraceEdge is called so that the pointer can be updated during a moving
-  // GC. TraceWeakEdge may sound better, but it is less efficient given that
-  // we know object_ is already marked.
+  // GC.
   if (maybeObject_) {
     MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
     TraceEdge(trc, &maybeObject_, "wasm table object");
   }
 
   switch (kind_) {
     case TableKind::FuncRef: {
       for (uint32_t i = 0; i < length_; i++) {
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -3619,18 +3619,17 @@ nsDocumentViewer::PrintPreview(nsIPrintS
     mAutoBeforeAndAfterPrint = std::move(autoBeforeAndAfterPrint);
   }
   dom::Element* root = doc->GetRootElement();
   if (root &&
       root->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdisallowselectionprint)) {
     PR_PL(("PrintPreview: found mozdisallowselectionprint"));
     printJob->SetDisallowSelectionPrint(true);
   }
-  rv = printJob->PrintPreview(aPrintSettings, aChildDOMWin,
-                              aWebProgressListener);
+  rv = printJob->PrintPreview(doc, aPrintSettings, aWebProgressListener);
   mPrintPreviewZoomed = false;
   if (NS_FAILED(rv)) {
     OnDonePrinting();
   }
   return rv;
 #  else
   return NS_ERROR_FAILURE;
 #  endif
--- a/layout/printing/nsPrintJob.cpp
+++ b/layout/printing/nsPrintJob.cpp
@@ -529,27 +529,32 @@ void nsPrintJob::Destroy() {
 void nsPrintJob::DestroyPrintingData() { mPrt = nullptr; }
 
 //---------------------------------------------------------------------------------
 //-- Section: Methods needed by the DocViewer
 //---------------------------------------------------------------------------------
 
 //--------------------------------------------------------
 nsresult nsPrintJob::Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
-                                nsIDocShell* aContainer, Document* aDocument,
+                                nsIDocShell* aDocShell, Document* aOriginalDoc,
                                 float aScreenDPI) {
   NS_ENSURE_ARG_POINTER(aDocViewerPrint);
-  NS_ENSURE_ARG_POINTER(aContainer);
-  NS_ENSURE_ARG_POINTER(aDocument);
+  NS_ENSURE_ARG_POINTER(aDocShell);
+  NS_ENSURE_ARG_POINTER(aOriginalDoc);
 
   mDocViewerPrint = aDocViewerPrint;
-  mContainer = do_GetWeakReference(aContainer);
-  mDocument = aDocument;
+  mDocShell = do_GetWeakReference(aDocShell);
   mScreenDPI = aScreenDPI;
 
+  // XXX We should not be storing this.  The original document that the user
+  // selected to print can be mutated while print preview is open.  Anything
+  // we need to know about the original document should be checked and stored
+  // here instead.
+  mOriginalDoc = aOriginalDoc;
+
   return NS_OK;
 }
 
 //-------------------------------------------------------
 bool nsPrintJob::CheckBeforeDestroy() {
   if (mPrt && mPrt->mPreparingForPrint) {
     mPrt->mDocWasToBeDestroyed = true;
     return true;
@@ -567,17 +572,17 @@ nsresult nsPrintJob::Cancelled() {
 
 //-------------------------------------------------------
 // Install our event listeners on the document to prevent
 // some events from being processed while in PrintPreview
 //
 // No return code - if this fails, there isn't much we can do
 void nsPrintJob::SuppressPrintPreviewUserEvents() {
   if (!mPrt->mPPEventSuppressor) {
-    nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mContainer);
+    nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
     if (!docShell) {
       return;
     }
 
     if (nsPIDOMWindowOuter* win = docShell->GetWindow()) {
       nsCOMPtr<EventTarget> target = win->GetFrameElementInternal();
       mPrt->mPPEventSuppressor = new PrintPreviewUserEventSuppressor(target);
     }
@@ -620,24 +625,24 @@ static void DumpLayoutData(const char* a
                            FILE* aFD);
 #endif
 
 //--------------------------------------------------------------------------------
 
 nsresult nsPrintJob::CommonPrint(bool aIsPrintPreview,
                                  nsIPrintSettings* aPrintSettings,
                                  nsIWebProgressListener* aWebProgressListener,
-                                 Document* aDoc) {
+                                 Document* aSourceDoc) {
   // Callers must hold a strong reference to |this| to ensure that we stay
   // alive for the duration of this method, because our main owning reference
   // (on nsDocumentViewer) might be cleared during this function (if we cause
   // script to run and it cancels the print operation).
 
   nsresult rv = DoCommonPrint(aIsPrintPreview, aPrintSettings,
-                              aWebProgressListener, aDoc);
+                              aWebProgressListener, aSourceDoc);
   if (NS_FAILED(rv)) {
     if (aIsPrintPreview) {
       mIsCreatingPrintPreview = false;
       SetIsPrintPreview(false);
     } else {
       SetIsPrinting(false);
     }
     if (mProgressDialogIsShown) CloseProgressDialog(aWebProgressListener);
@@ -648,17 +653,17 @@ nsresult nsPrintJob::CommonPrint(bool aI
   }
 
   return rv;
 }
 
 nsresult nsPrintJob::DoCommonPrint(bool aIsPrintPreview,
                                    nsIPrintSettings* aPrintSettings,
                                    nsIWebProgressListener* aWebProgressListener,
-                                   Document* aDoc) {
+                                   Document* aSourceDoc) {
   nsresult rv;
 
   if (aIsPrintPreview) {
     // The WebProgressListener can be QI'ed to nsIPrintingPromptService
     // then that means the progress dialog is already being shown.
     nsCOMPtr<nsIPrintingPromptService> pps(
         do_QueryInterface(aWebProgressListener));
     mProgressDialogIsShown = pps != nullptr;
@@ -733,39 +738,39 @@ nsresult nsPrintJob::DoCommonPrint(bool 
   // because the Print Dialog will "steal" focus and later when you try
   // to get the currently focused windows it will be nullptr
   printData->mCurrentFocusWin = FindFocusedDOMWindow();
 
   // Check to see if there is a "regular" selection
   bool isSelection = IsThereARangeSelection(printData->mCurrentFocusWin);
 
   // Get the docshell for this documentviewer
-  nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer, &rv));
+  nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
 
   {
     if (aIsPrintPreview) {
       nsCOMPtr<nsIContentViewer> viewer;
-      webContainer->GetContentViewer(getter_AddRefs(viewer));
+      docShell->GetContentViewer(getter_AddRefs(viewer));
       if (viewer && viewer->GetDocument() &&
           viewer->GetDocument()->IsShowing()) {
         viewer->GetDocument()->OnPageHide(false, nullptr);
       }
     }
 
     nsAutoScriptBlocker scriptBlocker;
     printData->mPrintObject = MakeUnique<nsPrintObject>();
-    rv = printData->mPrintObject->Init(webContainer, aDoc, aIsPrintPreview);
+    rv = printData->mPrintObject->Init(docShell, aSourceDoc, aIsPrintPreview);
     NS_ENSURE_SUCCESS(rv, rv);
 
     NS_ENSURE_TRUE(
         printData->mPrintDocList.AppendElement(printData->mPrintObject.get()),
         NS_ERROR_OUT_OF_MEMORY);
 
-    printData->mIsParentAFrameSet = IsParentAFrameSet(webContainer);
+    printData->mIsParentAFrameSet = IsParentAFrameSet(docShell);
     printData->mPrintObject->mFrameType =
         printData->mIsParentAFrameSet ? eFrameSet : eDoc;
 
     // Build the "tree" of PrintObjects
     BuildDocTree(printData->mPrintObject->mDocShell, &printData->mPrintDocList,
                  printData->mPrintObject);
   }
 
@@ -786,17 +791,17 @@ nsresult nsPrintJob::DoCommonPrint(bool 
       !printData->mPrintObject->mDocument->GetRootElement())
     return NS_ERROR_GFX_PRINTER_STARTDOC;
 
   // Create the linkage from the sub-docs back to the content element
   // in the parent document
   MapContentToWebShells(printData->mPrintObject, printData->mPrintObject);
 
   printData->mIsIFrameSelected = IsThereAnIFrameSelected(
-      webContainer, printData->mCurrentFocusWin, printData->mIsParentAFrameSet);
+      docShell, printData->mCurrentFocusWin, printData->mIsParentAFrameSet);
 
   // Setup print options for UI
   if (printData->mIsParentAFrameSet) {
     if (printData->mCurrentFocusWin) {
       printData->mPrintSettings->SetHowToEnableFrameUI(
           nsIPrintSettings::kFrameEnableAll);
     } else {
       printData->mPrintSettings->SetHowToEnableFrameUI(
@@ -842,17 +847,17 @@ nsresult nsPrintJob::DoCommonPrint(bool 
     // and get a remote print job, but the parent won't display a prompt.
     if (!printSilently || printingViaParent) {
       nsCOMPtr<nsIPrintingPromptService> printPromptService(
           do_GetService(kPrintingPromptService));
       if (printPromptService) {
         nsPIDOMWindowOuter* domWin = nullptr;
         // We leave domWin as nullptr to indicate a call for print preview.
         if (!aIsPrintPreview) {
-          domWin = mDocument->GetWindow();
+          domWin = mOriginalDoc->GetWindow();
           NS_ENSURE_TRUE(domWin, NS_ERROR_FAILURE);
         }
 
         // Platforms not implementing a given dialog for the service may
         // return NS_ERROR_NOT_IMPLEMENTED or an error code.
         //
         // NS_ERROR_NOT_IMPLEMENTED indicates they want default behavior
         // Any other error code means we must bail out
@@ -1030,64 +1035,58 @@ nsresult nsPrintJob::DoCommonPrint(bool 
 //---------------------------------------------------------------------------------
 nsresult nsPrintJob::Print(nsIPrintSettings* aPrintSettings,
                            nsIWebProgressListener* aWebProgressListener) {
   // If we have a print preview document, use that instead of the original
   // mDocument. That way animated images etc. get printed using the same state
   // as in print preview.
   Document* doc = mPrtPreview && mPrtPreview->mPrintObject
                       ? mPrtPreview->mPrintObject->mDocument
-                      : mDocument;
+                      : mOriginalDoc;
 
   return CommonPrint(false, aPrintSettings, aWebProgressListener, doc);
 }
 
 nsresult nsPrintJob::PrintPreview(
-    nsIPrintSettings* aPrintSettings, mozIDOMWindowProxy* aChildDOMWin,
+    Document* aSourceDoc, nsIPrintSettings* aPrintSettings,
     nsIWebProgressListener* aWebProgressListener) {
   // Get the DocShell and see if it is busy
   // (We can't Print Preview this document if it is still busy)
-  nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mContainer));
+  nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
   NS_ENSURE_STATE(docShell);
 
   auto busyFlags = docShell->GetBusyFlags();
   if (busyFlags != nsIDocShell::BUSY_FLAGS_NONE) {
     CloseProgressDialog(aWebProgressListener);
     FirePrintingErrorEvent(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY);
     return NS_ERROR_FAILURE;
   }
 
-  auto* window = nsPIDOMWindowOuter::From(aChildDOMWin);
-  NS_ENSURE_STATE(window);
-  nsCOMPtr<Document> doc = window->GetDoc();
-  NS_ENSURE_STATE(doc);
-
   // Document is not busy -- go ahead with the Print Preview
-  return CommonPrint(true, aPrintSettings, aWebProgressListener, doc);
+  return CommonPrint(true, aPrintSettings, aWebProgressListener, aSourceDoc);
 }
 
 //----------------------------------------------------------------------------------
 bool nsPrintJob::IsFramesetDocument() const {
-  nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer));
-  return IsParentAFrameSet(webContainer);
+  nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
+  return IsParentAFrameSet(docShell);
 }
 
 //----------------------------------------------------------------------------------
 bool nsPrintJob::IsIFrameSelected() {
   // Get the docshell for this documentviewer
-  nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer));
+  nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mDocShell));
   // Get the currently focused window
   nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
-  if (currentFocusWin && webContainer) {
+  if (currentFocusWin && docShell) {
     // Get whether the doc contains a frameset
     // Also, check to see if the currently focus docshell
     // is a child of this docshell
     bool isParentFrameSet;
-    return IsThereAnIFrameSelected(webContainer, currentFocusWin,
-                                   isParentFrameSet);
+    return IsThereAnIFrameSelected(docShell, currentFocusWin, isParentFrameSet);
   }
   return false;
 }
 
 //----------------------------------------------------------------------------------
 bool nsPrintJob::IsRangeSelection() {
   // Get the currently focused window
   nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
@@ -1210,17 +1209,17 @@ void nsPrintJob::ShowPrintProgress(bool 
   }
 
   // Now open the service to get the progress dialog
   // If we don't get a service, that's ok, then just don't show progress
   if (showProgresssDialog) {
     nsCOMPtr<nsIPrintingPromptService> printPromptService(
         do_GetService(kPrintingPromptService));
     if (printPromptService) {
-      nsPIDOMWindowOuter* domWin = mDocument->GetWindow();
+      nsPIDOMWindowOuter* domWin = mOriginalDoc->GetWindow();
       if (!domWin) return;
 
       nsCOMPtr<nsIDocShell> docShell = domWin->GetDocShell();
       if (!docShell) return;
       nsCOMPtr<nsIDocShellTreeOwner> owner;
       docShell->GetTreeOwner(getter_AddRefs(owner));
       nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(owner);
       if (!browserChrome) return;
@@ -2612,24 +2611,24 @@ static bool DocHasPrintCallbackCanvas(Do
 }
 
 /**
  * Checks to see if the document this print engine is associated with has any
  * canvases that have a mozPrintCallback.
  * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement#Properties
  */
 bool nsPrintJob::HasPrintCallbackCanvas() {
-  if (!mDocument) {
+  if (!mOriginalDoc) {
     return false;
   }
-  // First check this mDocument.
+  // First check this mOriginalDoc.
   bool result = false;
-  DocHasPrintCallbackCanvas(mDocument, static_cast<void*>(&result));
+  DocHasPrintCallbackCanvas(mOriginalDoc, static_cast<void*>(&result));
   // Also check the sub documents.
-  return result || DocHasPrintCallbackCanvas(mDocument);
+  return result || DocHasPrintCallbackCanvas(mOriginalDoc);
 }
 
 //-------------------------------------------------------
 bool nsPrintJob::PrePrintPage() {
   NS_ASSERTION(mPageSeqFrame.IsAlive(), "mPageSeqFrame is not alive!");
   NS_ASSERTION(mPrt, "mPrt is null!");
 
   // Although these should NEVER be nullptr
@@ -2818,17 +2817,17 @@ void nsPrintJob::CleanupDocTitleArray(ch
 
 /** ---------------------------------------------------
  *  Get the Focused Frame for a documentviewer
  */
 already_AddRefed<nsPIDOMWindowOuter> nsPrintJob::FindFocusedDOMWindow() const {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   NS_ENSURE_TRUE(fm, nullptr);
 
-  nsPIDOMWindowOuter* window = mDocument->GetWindow();
+  nsPIDOMWindowOuter* window = mOriginalDoc->GetWindow();
   NS_ENSURE_TRUE(window, nullptr);
 
   nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
   NS_ENSURE_TRUE(rootWindow, nullptr);
 
   nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
   nsFocusManager::GetFocusedDescendant(rootWindow,
                                        nsFocusManager::eIncludeAllDescendants,
@@ -2847,17 +2846,17 @@ bool nsPrintJob::IsWindowsInOurSubTree(n
   bool found = false;
 
   // now check to make sure it is in "our" tree of docshells
   if (window) {
     nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
 
     if (docShell) {
       // get this DocViewer docshell
-      nsCOMPtr<nsIDocShell> thisDVDocShell(do_QueryReferent(mContainer));
+      nsCOMPtr<nsIDocShell> thisDVDocShell(do_QueryReferent(mDocShell));
       while (!found) {
         if (docShell) {
           if (docShell == thisDVDocShell) {
             found = true;
             break;
           }
         } else {
           break;  // at top of tree
@@ -3165,17 +3164,16 @@ void nsPrintJob::TurnScriptingOn(bool aD
   // The following for loop uses nsPrintObject instances that are owned by
   // mPrt or mPrtPreview.  Therefore, this method needs to guarantee that
   // they won't be deleted in this method.
   RefPtr<nsPrintData> printData = mPrt ? mPrt : mPrtPreview;
   if (!printData) {
     return;
   }
 
-  NS_ASSERTION(mDocument, "We MUST have a document.");
   // First, get the script global object from the document...
 
   for (uint32_t i = 0; i < printData->mPrintDocList.Length(); i++) {
     nsPrintObject* po = printData->mPrintDocList.ElementAt(i);
     MOZ_ASSERT(po);
 
     Document* doc = po->mDocument;
     if (!doc) {
--- a/layout/printing/nsPrintJob.h
+++ b/layout/printing/nsPrintJob.h
@@ -54,22 +54,67 @@ class nsPrintJob final : public nsIObser
   // nsISupports interface...
   NS_DECL_ISUPPORTS
 
   // nsIObserver
   NS_DECL_NSIOBSERVER
 
   NS_DECL_NSIWEBPROGRESSLISTENER
 
-  // Our nsIWebBrowserPrint implementation defers to these methods.
+  /**
+   * Initialize for printing, or for creating a print preview document.
+   *
+   * aDocViewerPrint owns us.
+   *
+   * When called in preparation for printing, aOriginalDoc is aDocViewerPrint's
+   * document.  The document/viewer may be for a sub-document (an iframe).
+   *
+   * When called in preparation for print preview, aOriginalDoc belongs to a
+   * different docViewer, in a different docShell, in a different TabGroup.
+   * In this case our aDocViewerPrint is the docViewer for the about:blank
+   * document in a new tab that the Firefox frontend code has created in
+   * preparation for PrintPreview to generate a print preview document in it.
+   *
+   * NOTE: In the case we're called for print preview, aOriginalDoc actually
+   * may not be the original document that the user selected to print.  It
+   * is not the actual original document in the case when the user chooses to
+   * display a simplified version of a print preview document.  In that
+   * instance the Firefox frontend code creates a second print preview tab,
+   * with a new docViewer and nsPrintJob, and passes the previous print preview
+   * document as aOriginalDoc (it doesn't want to pass the actual original
+   * document since it may have mutated)!
+   */
+  nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
+                      nsIDocShell* aDocShell,
+                      mozilla::dom::Document* aOriginalDoc, float aScreenDPI);
+
+  // Our nsIWebBrowserPrint implementation (nsDocumentViewer) defers to the
+  // following methods.
+
+  /**
+   * May be called immediately after initialization, or after one or more
+   * PrintPreview calls.
+   */
   nsresult Print(nsIPrintSettings* aPrintSettings,
                  nsIWebProgressListener* aWebProgressListener);
-  nsresult PrintPreview(nsIPrintSettings* aPrintSettings,
-                        mozIDOMWindowProxy* aChildDOMWin,
+
+  /**
+   * Generates a new print preview document and replaces our docViewer's
+   * document with it.  (Note that this breaks the normal invariant that a
+   * Document and its nsDocumentViewer have an unchanging 1:1 relationship.)
+   *
+   * This may be called multiple times on the same instance in order to
+   * recreate the print preview document to take account of settings that the
+   * user has changed in the print preview interface.  In this case aSourceDoc
+   * is actually our docViewer's current document!
+   */
+  nsresult PrintPreview(mozilla::dom::Document* aSourceDoc,
+                        nsIPrintSettings* aPrintSettings,
                         nsIWebProgressListener* aWebProgressListener);
+
   bool IsDoingPrint() const { return mIsDoingPrinting; }
   bool IsDoingPrintPreview() const { return mIsDoingPrintPreview; }
   bool IsFramesetDocument() const;
   bool IsIFrameSelected();
   bool IsRangeSelection();
   bool IsFramesetFrameSelected() const;
   /// If the returned value is not greater than zero, an error occurred.
   int32_t GetPrintPreviewNumPages();
@@ -79,20 +124,16 @@ class nsPrintJob final : public nsIObser
 
   // This enum tells indicates what the default should be for the title
   // if the title from the document is null
   enum eDocTitleDefault { eDocTitleDefBlank, eDocTitleDefURLDoc };
 
   void Destroy();
   void DestroyPrintingData();
 
-  nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
-                      nsIDocShell* aContainer,
-                      mozilla::dom::Document* aDocument, float aScreenDPI);
-
   nsresult GetSeqFrameAndCountPages(nsIFrame*& aSeqFrame, int32_t& aCount);
 
   //
   // The following three methods are used for printing...
   //
   nsresult DocumentReadyForPrinting();
   nsresult GetSelectionDocument(nsIDeviceContextSpec* aDevSpec,
                                 mozilla::dom::Document** aNewDoc);
@@ -191,21 +232,21 @@ class nsPrintJob final : public nsIObser
 
  private:
   nsPrintJob& operator=(const nsPrintJob& aOther) = delete;
 
   ~nsPrintJob();
 
   nsresult CommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
                        nsIWebProgressListener* aWebProgressListener,
-                       mozilla::dom::Document* aDoc);
+                       mozilla::dom::Document* aSourceDoc);
 
   nsresult DoCommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
                          nsIWebProgressListener* aWebProgressListener,
-                         mozilla::dom::Document* aDoc);
+                         mozilla::dom::Document* aSourceDoc);
 
   void FirePrintCompletionEvent();
 
   void DisconnectPagePrintTimer();
 
   /**
    * This method is called to resume printing after all outstanding resources
    * referenced by the static clone have finished loading.  (It is possibly
@@ -226,20 +267,27 @@ class nsPrintJob final : public nsIObser
   nsresult ReconstructAndReflow(bool aDoSetPixelScale);
   nsresult UpdateSelectionAndShrinkPrintObject(nsPrintObject* aPO,
                                                bool aDocumentIsTopLevel);
   nsresult InitPrintDocConstruction(bool aHandleError);
   void FirePrintPreviewUpdateEvent();
 
   void PageDone(nsresult aResult);
 
-  RefPtr<mozilla::dom::Document> mDocument;
+  // The document that we were originally created for in order to print it or
+  // create a print preview of it.  This may belong to mDocViewerPrint or may
+  // belong to a different docViewer in a different docShell.  In reality, this
+  // also may not be the original document that the user selected to print (see
+  // the comment documenting Initialize() above).
+  RefPtr<mozilla::dom::Document> mOriginalDoc;
+
+  // The docViewer that owns us, and its docShell.
   nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
+  nsWeakPtr mDocShell;
 
-  nsWeakPtr mContainer;
   WeakFrame mPageSeqFrame;
 
   // We are the primary owner of our nsPrintData member vars.  These vars
   // are refcounted so that functions (e.g. nsPrintData methods) can create
   // temporary owning references when they need to fire a callback that
   // could conceivably destroy this nsPrintJob owner object and all its
   // member-data.
   RefPtr<nsPrintData> mPrt;