Backed out changeset a8dd01db9f92 (bug 1512260) requsted by owner (missing test) CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Wed, 12 Dec 2018 09:14:53 +0200
changeset 450216 c322f02577d386a90b72d29bf98d99256de13563
parent 450215 a8dd01db9f92757385f96a322296c4b027b7e72c
child 450217 8bf181f9b1c3daa66390ab03b6bc9f27c049f770
push id35192
push userrmaries@mozilla.com
push dateWed, 12 Dec 2018 16:30:58 +0000
treeherdermozilla-central@418b19d4ba3d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1512260
milestone66.0a1
backs outa8dd01db9f92757385f96a322296c4b027b7e72c
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
Backed out changeset a8dd01db9f92 (bug 1512260) requsted by owner (missing test) CLOSED TREE
dom/base/WindowDestroyedEvent.cpp
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/proxy/CrossCompartmentWrapper.cpp
js/src/shell/js.cpp
js/src/vm/Compartment.cpp
js/src/vm/Compartment.h
js/src/vm/Realm.h
js/xpconnect/src/XPCComponents.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
js/xpconnect/wrappers/WrapperFactory.cpp
--- a/dom/base/WindowDestroyedEvent.cpp
+++ b/dom/base/WindowDestroyedEvent.cpp
@@ -104,29 +104,31 @@ WindowDestroyedEvent::Run() {
           currentInner = outer->GetCurrentInnerWindowInternal();
         }
         NS_ENSURE_TRUE(currentInner, NS_OK);
 
         AutoSafeJSContext cx;
         JS::Rooted<JSObject*> obj(cx, currentInner->FastGetGlobalJSObject());
         if (obj && !js::IsSystemRealm(js::GetNonCCWObjectRealm(obj))) {
           JS::Realm* realm = js::GetNonCCWObjectRealm(obj);
+          JS::Compartment* cpt = JS::GetCompartmentForRealm(realm);
+
           nsCOMPtr<nsIPrincipal> pc =
               nsJSPrincipals::get(JS::GetRealmPrincipals(realm));
 
           if (BasePrincipal::Cast(pc)->AddonPolicy()) {
-            // We want to nuke all references to the add-on realm.
-            xpc::NukeAllWrappersForRealm(
-                cx, realm,
+            // We want to nuke all references to the add-on compartment.
+            xpc::NukeAllWrappersForCompartment(
+                cx, cpt,
                 mIsInnerWindow ? js::DontNukeWindowReferences
                                : js::NukeWindowReferences);
           } else {
             // We only want to nuke wrappers for the chrome->content case
             js::NukeCrossCompartmentWrappers(
-                cx, BrowserCompartmentMatcher(), realm,
+                cx, BrowserCompartmentMatcher(), cpt,
                 mIsInnerWindow ? js::DontNukeWindowReferences
                                : js::NukeWindowReferences,
                 js::NukeIncomingReferences);
           }
         }
       }
     } break;
   }
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -53,18 +53,18 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(
   // compartment is nuked. In the case where it is in the same compartment, we
   // have a reference to the real function. Since that means there are no
   // wrappers to cut, we need to check whether the compartment is still alive,
   // and drop the references if it is not.
 
   if (MOZ_UNLIKELY(!callback)) {
     return true;
   }
-  if (MOZ_LIKELY(tmp->mIncumbentGlobal) &&
-      MOZ_UNLIKELY(js::NukedObjectRealm(tmp->CallbackGlobalPreserveColor()))) {
+  auto pvt = xpc::CompartmentPrivate::Get(callback);
+  if (MOZ_LIKELY(tmp->mIncumbentGlobal && pvt) && MOZ_UNLIKELY(pvt->wasNuked)) {
     // It's not safe to release our global reference or drop our JS objects at
     // this point, so defer their finalization until CC is finished.
     AddForDeferredFinalization(new JSObjectsDropper(tmp));
     DeferredFinalize(tmp->mIncumbentGlobal.forget().take());
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -122,20 +122,16 @@ class CallbackObject : public nsISupport
    * be passed into a JS API function and that it won't be stored without being
    * rooted (or otherwise signaling the stored value to the CC).
    */
   JS::Handle<JSObject*> CallbackPreserveColor() const {
     // Calling fromMarkedLocation() is safe because we trace our mCallback, and
     // because the value of mCallback cannot change after if has been set.
     return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
   }
-  JS::Handle<JSObject*> CallbackGlobalPreserveColor() const {
-    // The comment in CallbackPreserveColor applies here as well.
-    return JS::Handle<JSObject*>::fromMarkedLocation(mCallbackGlobal.address());
-  }
 
   /*
    * If the callback is known to be non-gray, then this method can be
    * used instead of CallbackOrNull() to avoid the overhead of
    * ExposeObjectToActiveJS().
    */
   JS::Handle<JSObject*> CallbackKnownNotGray() const {
     MOZ_ASSERT(JS::ObjectIsNotGray(mCallback));
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -520,16 +520,20 @@ JS_FRIEND_API unsigned JS_PCToLineNumber
 JS_FRIEND_API bool JS_IsDeadWrapper(JSObject* obj) {
   return IsDeadProxyObject(obj);
 }
 
 JS_FRIEND_API JSObject* JS_NewDeadWrapper(JSContext* cx, JSObject* origObj) {
   return NewDeadProxyObject(cx, origObj);
 }
 
+JS_FRIEND_API bool JS_IsScriptSourceObject(JSObject* obj) {
+  return obj->is<ScriptSourceObject>();
+}
+
 void js::TraceWeakMaps(WeakMapTracer* trc) {
   WeakMapBase::traceAllMappings(trc);
 }
 
 extern JS_FRIEND_API bool js::AreGCGrayBitsValid(JSRuntime* rt) {
   return rt->gc.areGrayBitsValid();
 }
 
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -101,16 +101,21 @@ extern JS_FRIEND_API bool JS_IsDeadWrapp
  * attempting to wrap objects from scopes which are already dead.
  *
  * If origObject is passed, it must be an proxy object, and will be
  * used to determine the characteristics of the new dead wrapper.
  */
 extern JS_FRIEND_API JSObject* JS_NewDeadWrapper(
     JSContext* cx, JSObject* origObject = nullptr);
 
+/**
+ * Determine whether the given object is a ScriptSourceObject.
+ */
+extern JS_FRIEND_API bool JS_IsScriptSourceObject(JSObject* obj);
+
 /*
  * Used by the cycle collector to trace through a shape or object group and
  * all cycle-participating data it reaches, using bounded stack space.
  */
 extern JS_FRIEND_API void JS_TraceShapeCycleCollectorChildren(
     JS::CallbackTracer* trc, JS::GCCellPtr shape);
 extern JS_FRIEND_API void JS_TraceObjectGroupCycleCollectorChildren(
     JS::CallbackTracer* trc, JS::GCCellPtr group);
@@ -1175,24 +1180,19 @@ struct CompartmentsWithPrincipals : publ
   explicit CompartmentsWithPrincipals(JSPrincipals* p) : principals(p) {}
   virtual bool match(JS::Compartment* c) const override {
     return JS_GetCompartmentPrincipals(c) == principals;
   }
 };
 
 extern JS_FRIEND_API bool NukeCrossCompartmentWrappers(
     JSContext* cx, const CompartmentFilter& sourceFilter,
-    JS::Realm* target, NukeReferencesToWindow nukeReferencesToWindow,
+    JS::Compartment* target, NukeReferencesToWindow nukeReferencesToWindow,
     NukeReferencesFromTarget nukeReferencesFromTarget);
 
-extern JS_FRIEND_API bool AllowNewWrapper(JS::Compartment* target,
-                                          JSObject* obj);
-
-extern JS_FRIEND_API bool NukedObjectRealm(JSObject* obj);
-
 /* Specify information about DOMProxy proxies in the DOM, for use by ICs. */
 
 /*
  * The DOMProxyShadowsCheck function will be called to check if the property for
  * id should be gotten from the prototype, or if there is an own property that
  * shadows it.
  * * If ShadowsViaDirectExpando is returned, then the slot at
  *   listBaseExpandoSlot contains an expando object which has the property in
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -455,92 +455,67 @@ JS_FRIEND_API void js::NukeCrossCompartm
   JS::Compartment* comp = wrapper->compartment();
   auto ptr = comp->lookupWrapper(Wrapper::wrappedObject(wrapper));
   if (ptr) {
     comp->removeWrapper(ptr);
   }
   NukeRemovedCrossCompartmentWrapper(cx, wrapper);
 }
 
-// Returns true iff all realms in the compartment have been nuked.
-static bool NukedAllRealms(JS::Compartment* comp) {
-  for (RealmsInCompartmentIter realm(comp); !realm.done(); realm.next()) {
-    if (!realm->nukedIncomingWrappers) {
-      return false;
-    }
-  }
-  return true;
-}
-
 /*
  * NukeChromeCrossCompartmentWrappersForGlobal reaches into chrome and cuts
- * all of the cross-compartment wrappers that point to an object in the |target|
- * realm. The snag here is that we need to avoid cutting wrappers that point to
- * the window object on page navigation (inner window destruction) and only do
- * that on tab close (outer window destruction).  Thus the option of how to
- * handle the global object.
+ * all of the cross-compartment wrappers that point to objects parented to
+ * obj's global.  The snag here is that we need to avoid cutting wrappers that
+ * point to the window object on page navigation (inner window destruction)
+ * and only do that on tab close (outer window destruction).  Thus the
+ * option of how to handle the global object.
  */
 JS_FRIEND_API bool js::NukeCrossCompartmentWrappers(
     JSContext* cx, const CompartmentFilter& sourceFilter,
-    JS::Realm* target, js::NukeReferencesToWindow nukeReferencesToWindow,
+    JS::Compartment* target, js::NukeReferencesToWindow nukeReferencesToWindow,
     js::NukeReferencesFromTarget nukeReferencesFromTarget) {
   CHECK_THREAD(cx);
   JSRuntime* rt = cx->runtime();
 
-  // If we're nuking all wrappers into the target realm, prevent us from
-  // creating new wrappers for it in the future.
-  if (nukeReferencesFromTarget == NukeAllReferences) {
-    target->nukedIncomingWrappers = true;
-  }
-
   for (CompartmentsIter c(rt); !c.done(); c.next()) {
     if (!sourceFilter.match(c)) {
       continue;
     }
 
-    // If the realm matches both the source and target filter, we may want to
-    // cut outgoing wrappers too, if we nuked all realms in the compartment.
-    bool nukeAll = (nukeReferencesFromTarget == NukeAllReferences &&
-                    target->compartment() == c.get() &&
-                    NukedAllRealms(c.get()));
+    // If the compartment matches both the source and target filter, we may
+    // want to cut both incoming and outgoing wrappers.
+    bool nukeAll =
+        (nukeReferencesFromTarget == NukeAllReferences && target == c.get());
 
     // Iterate only the wrappers that have target compartment matched unless
     // |nukeAll| is true. The string wrappers that we're not interested in
     // won't be iterated, we can exclude them easily because they have
     // compartment nullptr. Use Maybe to avoid copying from conditionally
     // initializing NonStringWrapperEnum.
     mozilla::Maybe<Compartment::NonStringWrapperEnum> e;
     if (MOZ_LIKELY(!nukeAll)) {
-      e.emplace(c, target->compartment());
+      e.emplace(c, target);
     } else {
       e.emplace(c);
-      c.get()->nukedOutgoingWrappers = true;
     }
     for (; !e->empty(); e->popFront()) {
       // Skip debugger references because NukeCrossCompartmentWrapper()
       // doesn't know how to nuke them yet, see bug 1084626 for more
       // information.
       const CrossCompartmentKey& k = e->front().key();
       if (!k.is<JSObject*>()) {
         continue;
       }
 
       AutoWrapperRooter wobj(cx, WrapperValue(*e));
 
       // Unwrap from the wrapped object in CrossCompartmentKey instead of
       // the wrapper, this could save us a bit of time.
       JSObject* wrapped = UncheckedUnwrap(k.as<JSObject*>());
 
-      // Don't nuke wrappers for objects in other realms in the target
-      // compartment unless nukeAll is set because in that case we want to nuke
-      // all outgoing wrappers for the current compartment.
-      if (!nukeAll && wrapped->nonCCWRealm() != target) {
-        continue;
-      }
-
       // We never nuke script source objects, since only ever used internally by
       // the JS engine, and are expected to remain valid throughout a scripts
       // lifetime.
       if (MOZ_UNLIKELY(wrapped->is<ScriptSourceObject>())) {
         continue;
       }
 
       // We only skip nuking window references that point to a target
@@ -554,42 +529,16 @@ JS_FRIEND_API bool js::NukeCrossCompartm
       e->removeFront();
       NukeRemovedCrossCompartmentWrapper(cx, wobj);
     }
   }
 
   return true;
 }
 
-JS_FRIEND_API bool js::AllowNewWrapper(JS::Compartment* target,
-                                       JSObject* obj) {
-  // Disallow creating new wrappers if we nuked the object realm or target
-  // compartment. However, we always need to provide live wrappers for
-  // ScriptSourceObjects, since they're used for cross-compartment cloned
-  // scripts, and need to remain accessible even after the original realm has
-  // been nuked.
-
-  MOZ_ASSERT(obj->compartment() != target);
-
-  if (obj->is<ScriptSourceObject>()) {
-    return true;
-  }
-
-  if (target->nukedOutgoingWrappers ||
-      obj->nonCCWRealm()->nukedIncomingWrappers) {
-    return false;
-  }
-
-  return true;
-}
-
-JS_FRIEND_API bool js::NukedObjectRealm(JSObject* obj) {
-  return obj->nonCCWRealm()->nukedIncomingWrappers;
-}
-
 // Given a cross-compartment wrapper |wobj|, update it to point to
 // |newTarget|. This recomputes the wrapper with JS_WrapValue, and thus can be
 // useful even if wrapper already points to newTarget.
 // This operation crashes on failure rather than leaving the heap in an
 // inconsistent state.
 void js::RemapWrapper(JSContext* cx, JSObject* wobjArg,
                       JSObject* newTargetArg) {
   MOZ_ASSERT(!IsInsideNursery(wobjArg));
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -6224,17 +6224,17 @@ static bool NukeAllCCWs(JSContext* cx, u
   CallArgs args = CallArgsFromVp(argc, vp);
 
   if (args.length() != 0) {
     JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr,
                               JSSMSG_INVALID_ARGS, "nukeAllCCWs");
     return false;
   }
 
-  NukeCrossCompartmentWrappers(cx, AllCompartments(), cx->realm(),
+  NukeCrossCompartmentWrappers(cx, AllCompartments(), cx->compartment(),
                                NukeWindowReferences, NukeAllReferences);
   args.rval().setUndefined();
   return true;
 }
 
 static bool RecomputeWrappers(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -8485,17 +8485,17 @@ JS_FN_HELP("parseBin", BinParse, 1, 0,
 "         new realm. This creates a realm that's marked as a 'system' realm."),
 
     JS_FN_HELP("nukeCCW", NukeCCW, 1, 0,
 "nukeCCW(wrapper)",
 "  Nuke a CrossCompartmentWrapper, which turns it into a DeadProxyObject."),
 
     JS_FN_HELP("nukeAllCCWs", NukeAllCCWs, 0, 0,
 "nukeAllCCWs()",
-"  Like nukeCCW, but for all CrossCompartmentWrappers targeting the current realm."),
+"  Like nukeCCW, but for all CrossCompartmentWrappers targeting the current compartment."),
 
     JS_FN_HELP("recomputeWrappers", RecomputeWrappers, 2, 0,
 "recomputeWrappers([src, [target]])",
 "  Recompute all cross-compartment wrappers. src and target are both optional\n"
 "  and can be used to filter source or target compartments: the unwrapped\n"
 "  object's compartment is used as CompartmentFilter.\n"),
 
     JS_FN_HELP("wrapWithProto", WrapWithProto, 2, 0,
--- a/js/src/vm/Compartment.cpp
+++ b/js/src/vm/Compartment.cpp
@@ -219,27 +219,16 @@ bool Compartment::getNonWrapperObjectFor
   // particular wrapper.
   RootedObject objectPassedToWrap(cx, obj);
   obj.set(UncheckedUnwrap(obj, /* stopAtWindowProxy = */ true));
   if (obj->compartment() == this) {
     MOZ_ASSERT(!IsWindow(obj));
     return true;
   }
 
-  // Disallow creating new wrappers if we nuked the object's realm or the
-  // current compartment.
-  if (!AllowNewWrapper(this, obj)) {
-    JSObject* res = NewDeadProxyObject(cx);
-    if (!res) {
-      return false;
-    }
-    obj.set(res);
-    return true;
-  }
-
   // Invoke the prewrap callback. The prewrap callback is responsible for
   // doing similar reification as above, but can account for any additional
   // embedder requirements.
   //
   // We're a bit worried about infinite recursion here, so we do a check -
   // see bug 809295.
   auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
   if (!CheckSystemRecursionLimit(cx)) {
--- a/js/src/vm/Compartment.h
+++ b/js/src/vm/Compartment.h
@@ -441,22 +441,16 @@ class JS::Compartment {
     // During GC, we may set this to |true| if we entered a realm in this
     // compartment. Note that (without a stack walk) we don't know exactly
     // *which* realms, because Realm::enterRealmDepthIgnoringJit_ does not
     // account for cross-Realm calls in JIT code updating cx->realm_. See
     // also the enterRealmDepthIgnoringJit_ comment.
     bool hasEnteredRealm = false;
   } gcState;
 
-  // True if all outgoing wrappers have been nuked. This happens when all realms
-  // have been nuked and NukeCrossCompartmentWrappers is called with the
-  // NukeAllReferences option. This prevents us from creating new wrappers for
-  // the compartment.
-  bool nukedOutgoingWrappers = false;
-
   JS::Zone* zone() { return zone_; }
   const JS::Zone* zone() const { return zone_; }
 
   JSRuntime* runtimeFromMainThread() const {
     MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_));
     return runtime_;
   }
 
--- a/js/src/vm/Realm.h
+++ b/js/src/vm/Realm.h
@@ -436,21 +436,16 @@ class JS::Realm : public JS::shadow::Rea
    */
   uint32_t globalWriteBarriered = 0;
 
   uint32_t warnedAboutStringGenericsMethods = 0;
 #ifdef DEBUG
   bool firedOnNewGlobalObject = false;
 #endif
 
-  // True if all incoming wrappers have been nuked. This happens when
-  // NukeCrossCompartmentWrappers is called with the NukeAllReferences option.
-  // This prevents us from creating new wrappers for the compartment.
-  bool nukedIncomingWrappers = false;
-
  private:
   void updateDebuggerObservesFlag(unsigned flag);
 
   Realm(const Realm&) = delete;
   void operator=(const Realm&) = delete;
 
  public:
   Realm(JS::Compartment* comp, const JS::RealmOptions& options);
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2072,17 +2072,17 @@ NS_IMETHODIMP
 nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx) {
   AUTO_PROFILER_LABEL("nsXPCComponents_Utils::NukeSandbox", OTHER);
   NS_ENSURE_TRUE(obj.isObject(), NS_ERROR_INVALID_ARG);
   JSObject* wrapper = &obj.toObject();
   NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG);
   RootedObject sb(cx, UncheckedUnwrap(wrapper));
   NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG);
 
-  xpc::NukeAllWrappersForRealm(cx, GetNonCCWObjectRealm(sb));
+  xpc::NukeAllWrappersForCompartment(cx, GetObjectCompartment(sb));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::BlockScriptForGlobal(HandleValue globalArg,
                                             JSContext* cx) {
   NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -190,16 +190,17 @@ CompartmentPrivate::CompartmentPrivate(J
       allowWaivers(true),
       isWebExtensionContentScript(false),
       allowCPOWs(false),
       isContentXBLCompartment(false),
       isUAWidgetCompartment(false),
       isSandboxCompartment(false),
       universalXPConnectEnabled(false),
       forcePermissiveCOWs(false),
+      wasNuked(false),
       mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)) {
   MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
   mozilla::PodArrayZero(wrapperDenialWarnings);
 }
 
 CompartmentPrivate::~CompartmentPrivate() {
   MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
   delete mWrappedJSMap;
@@ -584,36 +585,46 @@ nsGlobalWindowInner* WindowGlobalOrNull(
   return WindowOrNull(glob);
 }
 
 nsGlobalWindowInner* CurrentWindowOrNull(JSContext* cx) {
   JSObject* glob = JS::CurrentGlobalOrNull(cx);
   return glob ? WindowOrNull(glob) : nullptr;
 }
 
-// Nukes all wrappers into or out of the given realm, and prevents new
-// wrappers from being created. Additionally marks the realm as
+// Nukes all wrappers into or out of the given compartment, and prevents new
+// wrappers from being created. Additionally marks the compartment as
 // unscriptable after wrappers have been nuked.
 //
-// Note: This should *only* be called for browser or extension realms.
+// Note: This should *only* be called for browser or extension compartments.
 // Wrappers between web compartments must never be cut in web-observable
 // ways.
-void NukeAllWrappersForRealm(
-    JSContext* cx, JS::Realm* realm,
+void NukeAllWrappersForCompartment(
+    JSContext* cx, JS::Compartment* compartment,
     js::NukeReferencesToWindow nukeReferencesToWindow) {
-  // We do the following:
-  // * Nuke all wrappers into the realm.
-  // * Nuke all wrappers out of the realm's compartment, once we have nuked all
-  //   realms in it.
-  js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), realm,
+  // First, nuke all wrappers into or out of the target compartment. Once
+  // the compartment is marked as nuked, WrapperFactory will refuse to
+  // create new live wrappers for it, in either direction. This means that
+  // we need to be sure that we don't have any existing cross-compartment
+  // wrappers which may be replaced with dead wrappers during unrelated
+  // wrapper recomputation *before* we set that bit.
+  js::NukeCrossCompartmentWrappers(cx, js::AllCompartments(), compartment,
                                    nukeReferencesToWindow,
                                    js::NukeAllReferences);
 
-  // Mark the realm as unscriptable.
-  xpc::RealmPrivate::Get(realm)->scriptability.Block();
+  // At this point, we should cross-compartment wrappers for the nuked
+  // compartment. Set the wasNuked bit so WrapperFactory will return a
+  // DeadObjectProxy when asked to create a new wrapper for it, and mark as
+  // unscriptable.
+  xpc::CompartmentPrivate::Get(compartment)->wasNuked = true;
+
+  auto blockScriptability = [](JSContext*, void*, Handle<Realm*> realm) {
+    xpc::RealmPrivate::Get(realm)->scriptability.Block();
+  };
+  JS::IterateRealmsInCompartment(cx, compartment, nullptr, blockScriptability);
 }
 
 }  // namespace xpc
 
 static void CompartmentDestroyedCallback(JSFreeOp* fop,
                                          JS::Compartment* compartment) {
   // NB - This callback may be called in JS_DestroyContext, which happens
   // after the XPCJSRuntime has been torn down.
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -2802,16 +2802,20 @@ class CompartmentPrivate {
   // This is only ever set during mochitest runs when enablePrivilege is called.
   // It allows the SpecialPowers scope to waive the normal chrome security
   // wrappers and expose properties directly to content. This lets us avoid a
   // bunch of overhead and complexity in our SpecialPowers automation glue.
   //
   // Using it in production is inherently unsafe.
   bool forcePermissiveCOWs;
 
+  // True if this compartment has been nuked. If true, any wrappers into or
+  // out of it should be considered invalid.
+  bool wasNuked;
+
   // Whether we've emitted a warning about a property that was filtered out
   // by a security wrapper. See XrayWrapper.cpp.
   bool wrapperDenialWarnings[WrapperDenialTypeCount];
 
   JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap; }
   void UpdateWeakPointersAfterGC();
 
   void SystemIsBeingShutDown();
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -404,18 +404,18 @@ bool StringToJsval(JSContext* cx, mozill
     return true;
   }
   return NonVoidStringToJsval(cx, str, rval);
 }
 
 nsIPrincipal* GetCompartmentPrincipal(JS::Compartment* compartment);
 nsIPrincipal* GetRealmPrincipal(JS::Realm* realm);
 
-void NukeAllWrappersForRealm(
-    JSContext* cx, JS::Realm* realm,
+void NukeAllWrappersForCompartment(
+    JSContext* cx, JS::Compartment* compartment,
     js::NukeReferencesToWindow nukeReferencesToWindow =
         js::NukeWindowReferences);
 
 void SetLocationForGlobal(JSObject* global, const nsACString& location);
 void SetLocationForGlobal(JSObject* global, nsIURI* locationURI);
 
 // ReportJSRuntimeExplicitTreeStats will expect this in the |extra| member
 // of JS::ZoneStats.
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -175,16 +175,33 @@ void WrapperFactory::PrepareForWrapping(
 
   // If the object is a dead wrapper, return a new dead wrapper rather than
   // trying to wrap it for a different compartment.
   if (JS_IsDeadWrapper(obj)) {
     retObj.set(JS_NewDeadWrapper(cx, obj));
     return;
   }
 
+  // If we've somehow gotten to this point after either the source or target
+  // compartment has been nuked, return a DeadObjectProxy to prevent further
+  // access.
+  // However, we always need to provide live wrappers for ScriptSourceObjects,
+  // since they're used for cross-compartment cloned scripts, and need to
+  // remain accessible even after the original compartment has been nuked.
+  JS::Compartment* origin = js::GetObjectCompartment(obj);
+  JS::Compartment* target = js::GetObjectCompartment(scope);
+  if (!JS_IsScriptSourceObject(obj) &&
+      (CompartmentPrivate::Get(origin)->wasNuked ||
+       CompartmentPrivate::Get(target)->wasNuked)) {
+    NS_WARNING("Trying to create a wrapper into or out of a nuked compartment");
+
+    retObj.set(JS_NewDeadWrapper(cx));
+    return;
+  }
+
   // If we've got a WindowProxy, there's nothing special that needs to be
   // done here, and we can move on to the next phase of wrapping. We handle
   // this case first to allow us to assert against wrappers below.
   if (js::IsWindowProxy(obj)) {
     retObj.set(waive ? WaiveXray(cx, obj) : obj);
     return;
   }
 
@@ -326,19 +343,22 @@ void WrapperFactory::PrepareForWrapping(
   retObj.set(waive ? WaiveXray(cx, obj) : obj);
 }
 
 #ifdef DEBUG
 static void DEBUG_CheckUnwrapSafety(HandleObject obj,
                                     const js::Wrapper* handler,
                                     JS::Compartment* origin,
                                     JS::Compartment* target) {
-  if (!js::AllowNewWrapper(target, obj)) {
-    // The JS engine should have returned a dead wrapper in this case and we
-    // shouldn't even get here.
+  if (!JS_IsScriptSourceObject(obj) &&
+      (CompartmentPrivate::Get(origin)->wasNuked ||
+       CompartmentPrivate::Get(target)->wasNuked)) {
+    // If either compartment has already been nuked, we should have returned
+    // a dead wrapper from our prewrap callback, and this function should
+    // not be called.
     MOZ_ASSERT_UNREACHABLE("CheckUnwrapSafety called for a dead wrapper");
   } else if (AccessCheck::isChrome(target) ||
              xpc::IsUniversalXPConnectEnabled(target)) {
     // If the caller is chrome (or effectively so), unwrap should always be
     // allowed.
     MOZ_ASSERT(!handler->hasSecurityPolicy());
   } else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {
     // Similarly, if this is a privileged scope that has opted to make itself