summaryrefslogtreecommitdiffstats
path: root/js/src/proxy/CrossCompartmentWrapper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/proxy/CrossCompartmentWrapper.cpp')
-rw-r--r--js/src/proxy/CrossCompartmentWrapper.cpp685
1 files changed, 685 insertions, 0 deletions
diff --git a/js/src/proxy/CrossCompartmentWrapper.cpp b/js/src/proxy/CrossCompartmentWrapper.cpp
new file mode 100644
index 000000000..2a6cb5400
--- /dev/null
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -0,0 +1,685 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "jsiter.h"
+#include "jswrapper.h"
+
+#include "proxy/DeadObjectProxy.h"
+#include "vm/WrapperObject.h"
+
+#include "jscompartmentinlines.h"
+#include "jsobjinlines.h"
+
+using namespace js;
+
+#define PIERCE(cx, wrapper, pre, op, post) \
+ JS_BEGIN_MACRO \
+ bool ok; \
+ { \
+ AutoCompartment call(cx, wrappedObject(wrapper)); \
+ ok = (pre) && (op); \
+ } \
+ return ok && (post); \
+ JS_END_MACRO
+
+#define NOTHING (true)
+
+bool
+CrossCompartmentWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::getPropertyDescriptor(cx, wrapper, id, desc),
+ cx->compartment()->wrap(cx, desc));
+}
+
+bool
+CrossCompartmentWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc),
+ cx->compartment()->wrap(cx, desc));
+}
+
+bool
+CrossCompartmentWrapper::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ Rooted<PropertyDescriptor> desc2(cx, desc);
+ PIERCE(cx, wrapper,
+ cx->compartment()->wrap(cx, &desc2),
+ Wrapper::defineProperty(cx, wrapper, id, desc2, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::ownPropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::ownPropertyKeys(cx, wrapper, props),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::delete_(JSContext* cx, HandleObject wrapper, HandleId id,
+ ObjectOpResult& result) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::delete_(cx, wrapper, id, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::getPrototype(JSContext* cx, HandleObject wrapper,
+ MutableHandleObject protop) const
+{
+ {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ AutoCompartment call(cx, wrapped);
+ if (!GetPrototype(cx, wrapped, protop))
+ return false;
+ if (protop) {
+ if (!protop->setDelegate(cx))
+ return false;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, protop);
+}
+
+bool
+CrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject wrapper,
+ HandleObject proto, ObjectOpResult& result) const
+{
+ RootedObject protoCopy(cx, proto);
+ PIERCE(cx, wrapper,
+ cx->compartment()->wrap(cx, &protoCopy),
+ Wrapper::setPrototype(cx, wrapper, protoCopy, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper,
+ bool* isOrdinary, MutableHandleObject protop) const
+{
+ {
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ AutoCompartment call(cx, wrapped);
+ if (!GetPrototypeIfOrdinary(cx, wrapped, isOrdinary, protop))
+ return false;
+
+ if (!*isOrdinary)
+ return true;
+
+ if (protop) {
+ if (!protop->setDelegate(cx))
+ return false;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, protop);
+}
+
+bool
+CrossCompartmentWrapper::setImmutablePrototype(JSContext* cx, HandleObject wrapper, bool* succeeded) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::setImmutablePrototype(cx, wrapper, succeeded),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::preventExtensions(JSContext* cx, HandleObject wrapper,
+ ObjectOpResult& result) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::preventExtensions(cx, wrapper, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::isExtensible(JSContext* cx, HandleObject wrapper, bool* extensible) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::isExtensible(cx, wrapper, extensible),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::has(JSContext* cx, HandleObject wrapper, HandleId id, bool* bp) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::has(cx, wrapper, id, bp),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::hasOwn(JSContext* cx, HandleObject wrapper, HandleId id, bool* bp) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::hasOwn(cx, wrapper, id, bp),
+ NOTHING);
+}
+
+static bool
+WrapReceiver(JSContext* cx, HandleObject wrapper, MutableHandleValue receiver)
+{
+ // Usually the receiver is the wrapper and we can just unwrap it. If the
+ // wrapped object is also a wrapper, things are more complicated and we
+ // fall back to the slow path (it calls UncheckedUnwrap to unwrap all
+ // wrappers).
+ if (ObjectValue(*wrapper) == receiver) {
+ JSObject* wrapped = Wrapper::wrappedObject(wrapper);
+ if (!IsWrapper(wrapped)) {
+ MOZ_ASSERT(wrapped->compartment() == cx->compartment());
+ MOZ_ASSERT(!IsWindow(wrapped));
+ receiver.setObject(*wrapped);
+ return true;
+ }
+ }
+
+ return cx->compartment()->wrap(cx, receiver);
+}
+
+bool
+CrossCompartmentWrapper::get(JSContext* cx, HandleObject wrapper, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const
+{
+ RootedValue receiverCopy(cx, receiver);
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!WrapReceiver(cx, wrapper, &receiverCopy))
+ return false;
+
+ if (!Wrapper::get(cx, wrapper, receiverCopy, id, vp))
+ return false;
+ }
+ return cx->compartment()->wrap(cx, vp);
+}
+
+bool
+CrossCompartmentWrapper::set(JSContext* cx, HandleObject wrapper, HandleId id, HandleValue v,
+ HandleValue receiver, ObjectOpResult& result) const
+{
+ RootedValue valCopy(cx, v);
+ RootedValue receiverCopy(cx, receiver);
+ PIERCE(cx, wrapper,
+ cx->compartment()->wrap(cx, &valCopy) &&
+ WrapReceiver(cx, wrapper, &receiverCopy),
+ Wrapper::set(cx, wrapper, id, valCopy, receiverCopy, result),
+ NOTHING);
+}
+
+bool
+CrossCompartmentWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject wrapper,
+ AutoIdVector& props) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::getOwnEnumerablePropertyKeys(cx, wrapper, props),
+ NOTHING);
+}
+
+/*
+ * We can reify non-escaping iterator objects instead of having to wrap them. This
+ * allows fast iteration over objects across a compartment boundary.
+ */
+static bool
+CanReify(HandleObject obj)
+{
+ return obj->is<PropertyIteratorObject>() &&
+ (obj->as<PropertyIteratorObject>().getNativeIterator()->flags & JSITER_ENUMERATE);
+}
+
+struct AutoCloseIterator
+{
+ AutoCloseIterator(JSContext* cx, JSObject* obj) : cx(cx), obj(cx, obj) {}
+
+ ~AutoCloseIterator() { if (obj) CloseIterator(cx, obj); }
+
+ void clear() { obj = nullptr; }
+
+ private:
+ JSContext* cx;
+ RootedObject obj;
+};
+
+static bool
+Reify(JSContext* cx, JSCompartment* origin, MutableHandleObject objp)
+{
+ Rooted<PropertyIteratorObject*> iterObj(cx, &objp->as<PropertyIteratorObject>());
+ NativeIterator* ni = iterObj->getNativeIterator();
+
+ AutoCloseIterator close(cx, iterObj);
+
+ /* Wrap the iteratee. */
+ RootedObject obj(cx, ni->obj);
+ if (!origin->wrap(cx, &obj))
+ return false;
+
+ /*
+ * Wrap the elements in the iterator's snapshot.
+ * N.B. the order of closing/creating iterators is important due to the
+ * implicit cx->enumerators state.
+ */
+ size_t length = ni->numKeys();
+ AutoIdVector keys(cx);
+ if (length > 0) {
+ if (!keys.reserve(length))
+ return false;
+ for (size_t i = 0; i < length; ++i) {
+ RootedId id(cx);
+ RootedValue v(cx, StringValue(ni->begin()[i]));
+ if (!ValueToId<CanGC>(cx, v, &id))
+ return false;
+ keys.infallibleAppend(id);
+ }
+ }
+
+ close.clear();
+ if (!CloseIterator(cx, iterObj))
+ return false;
+
+ return EnumeratedIdVectorToIterator(cx, obj, ni->flags, keys, objp);
+}
+
+bool
+CrossCompartmentWrapper::enumerate(JSContext* cx, HandleObject wrapper,
+ MutableHandleObject objp) const
+{
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!Wrapper::enumerate(cx, wrapper, objp))
+ return false;
+ }
+
+ if (CanReify(objp))
+ return Reify(cx, cx->compartment(), objp);
+ return cx->compartment()->wrap(cx, objp);
+}
+
+bool
+CrossCompartmentWrapper::call(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+{
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+
+ {
+ AutoCompartment call(cx, wrapped);
+
+ args.setCallee(ObjectValue(*wrapped));
+ if (!cx->compartment()->wrap(cx, args.mutableThisv()))
+ return false;
+
+ for (size_t n = 0; n < args.length(); ++n) {
+ if (!cx->compartment()->wrap(cx, args[n]))
+ return false;
+ }
+
+ if (!Wrapper::call(cx, wrapper, args))
+ return false;
+ }
+
+ return cx->compartment()->wrap(cx, args.rval());
+}
+
+bool
+CrossCompartmentWrapper::construct(JSContext* cx, HandleObject wrapper, const CallArgs& args) const
+{
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ {
+ AutoCompartment call(cx, wrapped);
+
+ for (size_t n = 0; n < args.length(); ++n) {
+ if (!cx->compartment()->wrap(cx, args[n]))
+ return false;
+ }
+ if (!cx->compartment()->wrap(cx, args.newTarget()))
+ return false;
+ if (!Wrapper::construct(cx, wrapper, args))
+ return false;
+ }
+ return cx->compartment()->wrap(cx, args.rval());
+}
+
+bool
+CrossCompartmentWrapper::nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
+ const CallArgs& srcArgs) const
+{
+ RootedObject wrapper(cx, &srcArgs.thisv().toObject());
+ MOZ_ASSERT(srcArgs.thisv().isMagic(JS_IS_CONSTRUCTING) ||
+ !UncheckedUnwrap(wrapper)->is<CrossCompartmentWrapperObject>());
+
+ RootedObject wrapped(cx, wrappedObject(wrapper));
+ {
+ AutoCompartment call(cx, wrapped);
+ InvokeArgs dstArgs(cx);
+ if (!dstArgs.init(cx, srcArgs.length()))
+ return false;
+
+ Value* src = srcArgs.base();
+ Value* srcend = srcArgs.array() + srcArgs.length();
+ Value* dst = dstArgs.base();
+
+ RootedValue source(cx);
+ for (; src < srcend; ++src, ++dst) {
+ source = *src;
+ if (!cx->compartment()->wrap(cx, &source))
+ return false;
+ *dst = source.get();
+
+ // Handle |this| specially. When we rewrap on the other side of the
+ // membrane, we might apply a same-compartment security wrapper that
+ // will stymie this whole process. If that happens, unwrap the wrapper.
+ // This logic can go away when same-compartment security wrappers go away.
+ if ((src == srcArgs.base() + 1) && dst->isObject()) {
+ RootedObject thisObj(cx, &dst->toObject());
+ if (thisObj->is<WrapperObject>() &&
+ Wrapper::wrapperHandler(thisObj)->hasSecurityPolicy())
+ {
+ MOZ_ASSERT(!thisObj->is<CrossCompartmentWrapperObject>());
+ *dst = ObjectValue(*Wrapper::wrappedObject(thisObj));
+ }
+ }
+ }
+
+ if (!CallNonGenericMethod(cx, test, impl, dstArgs))
+ return false;
+
+ srcArgs.rval().set(dstArgs.rval());
+ }
+ return cx->compartment()->wrap(cx, srcArgs.rval());
+}
+
+bool
+CrossCompartmentWrapper::hasInstance(JSContext* cx, HandleObject wrapper, MutableHandleValue v,
+ bool* bp) const
+{
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!cx->compartment()->wrap(cx, v))
+ return false;
+ return Wrapper::hasInstance(cx, wrapper, v, bp);
+}
+
+const char*
+CrossCompartmentWrapper::className(JSContext* cx, HandleObject wrapper) const
+{
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ return Wrapper::className(cx, wrapper);
+}
+
+JSString*
+CrossCompartmentWrapper::fun_toString(JSContext* cx, HandleObject wrapper, unsigned indent) const
+{
+ RootedString str(cx);
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ str = Wrapper::fun_toString(cx, wrapper, indent);
+ if (!str)
+ return nullptr;
+ }
+ if (!cx->compartment()->wrap(cx, &str))
+ return nullptr;
+ return str;
+}
+
+bool
+CrossCompartmentWrapper::regexp_toShared(JSContext* cx, HandleObject wrapper, RegExpGuard* g) const
+{
+ RegExpGuard wrapperGuard(cx);
+ {
+ AutoCompartment call(cx, wrappedObject(wrapper));
+ if (!Wrapper::regexp_toShared(cx, wrapper, &wrapperGuard))
+ return false;
+ }
+
+ // Get an equivalent RegExpShared associated with the current compartment.
+ RegExpShared* re = wrapperGuard.re();
+ return cx->compartment()->regExps.get(cx, re->getSource(), re->getFlags(), g);
+}
+
+bool
+CrossCompartmentWrapper::boxedValue_unbox(JSContext* cx, HandleObject wrapper, MutableHandleValue vp) const
+{
+ PIERCE(cx, wrapper,
+ NOTHING,
+ Wrapper::boxedValue_unbox(cx, wrapper, vp),
+ cx->compartment()->wrap(cx, vp));
+}
+
+const CrossCompartmentWrapper CrossCompartmentWrapper::singleton(0u);
+
+bool
+js::IsCrossCompartmentWrapper(JSObject* obj)
+{
+ return IsWrapper(obj) &&
+ !!(Wrapper::wrapperHandler(obj)->flags() & Wrapper::CROSS_COMPARTMENT);
+}
+
+static void
+NukeRemovedCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper)
+{
+ MOZ_ASSERT(wrapper->is<CrossCompartmentWrapperObject>());
+
+ NotifyGCNukeWrapper(wrapper);
+
+ wrapper->as<ProxyObject>().nuke();
+
+ MOZ_ASSERT(IsDeadProxyObject(wrapper));
+}
+
+JS_FRIEND_API(void)
+js::NukeCrossCompartmentWrapper(JSContext* cx, JSObject* wrapper)
+{
+ JSCompartment* comp = wrapper->compartment();
+ auto ptr = comp->lookupWrapper(ObjectValue(*Wrapper::wrappedObject(wrapper)));
+ if (ptr)
+ comp->removeWrapper(ptr);
+ NukeRemovedCrossCompartmentWrapper(cx, wrapper);
+}
+
+/*
+ * NukeChromeCrossCompartmentWrappersForGlobal reaches into chrome and cuts
+ * 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,
+ const CompartmentFilter& targetFilter,
+ js::NukeReferencesToWindow nukeReferencesToWindow)
+{
+ CHECK_REQUEST(cx);
+ JSRuntime* rt = cx->runtime();
+
+ rt->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
+ // Iterate through scopes looking for system cross compartment wrappers
+ // that point to an object that shares a global with obj.
+
+ for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+ if (!sourceFilter.match(c))
+ continue;
+
+ // Iterate the wrappers looking for anything interesting.
+ for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
+ // Some cross-compartment wrappers are for strings. We're not
+ // interested in those.
+ const CrossCompartmentKey& k = e.front().key();
+ if (!k.is<JSObject*>())
+ continue;
+
+ AutoWrapperRooter wobj(cx, WrapperValue(e));
+ JSObject* wrapped = UncheckedUnwrap(wobj);
+
+ if (nukeReferencesToWindow == DontNukeWindowReferences &&
+ IsWindowProxy(wrapped))
+ {
+ continue;
+ }
+
+ if (targetFilter.match(wrapped->compartment())) {
+ // We found a wrapper to nuke.
+ e.removeFront();
+ NukeRemovedCrossCompartmentWrapper(cx, wobj);
+ }
+ }
+ }
+
+ return true;
+}
+
+// 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));
+ MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
+ RootedObject wobj(cx, wobjArg);
+ RootedObject newTarget(cx, newTargetArg);
+ MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>());
+ MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
+ JSObject* origTarget = Wrapper::wrappedObject(wobj);
+ MOZ_ASSERT(origTarget);
+ Value origv = ObjectValue(*origTarget);
+ JSCompartment* wcompartment = wobj->compartment();
+
+ AutoDisableProxyCheck adpc(cx->runtime());
+
+ // If we're mapping to a different target (as opposed to just recomputing
+ // for the same target), we must not have an existing wrapper for the new
+ // target, otherwise this will break.
+ MOZ_ASSERT_IF(origTarget != newTarget,
+ !wcompartment->lookupWrapper(ObjectValue(*newTarget)));
+
+ // The old value should still be in the cross-compartment wrapper map, and
+ // the lookup should return wobj.
+ WrapperMap::Ptr p = wcompartment->lookupWrapper(origv);
+ MOZ_ASSERT(&p->value().unsafeGet()->toObject() == wobj);
+ wcompartment->removeWrapper(p);
+
+ // When we remove origv from the wrapper map, its wrapper, wobj, must
+ // immediately cease to be a cross-compartment wrapper. Nuke it.
+ NukeCrossCompartmentWrapper(cx, wobj);
+
+ // First, we wrap it in the new compartment. We try to use the existing
+ // wrapper, |wobj|, since it's been nuked anyway. The wrap() function has
+ // the choice to reuse |wobj| or not.
+ RootedObject tobj(cx, newTarget);
+ AutoCompartment ac(cx, wobj);
+ if (!wcompartment->rewrap(cx, &tobj, wobj))
+ MOZ_CRASH();
+
+ // If wrap() reused |wobj|, it will have overwritten it and returned with
+ // |tobj == wobj|. Otherwise, |tobj| will point to a new wrapper and |wobj|
+ // will still be nuked. In the latter case, we replace |wobj| with the
+ // contents of the new wrapper in |tobj|.
+ if (tobj != wobj) {
+ // Now, because we need to maintain object identity, we do a brain
+ // transplant on the old object so that it contains the contents of the
+ // new one.
+ if (!JSObject::swap(cx, wobj, tobj))
+ MOZ_CRASH();
+ }
+
+ // Before swapping, this wrapper came out of wrap(), which enforces the
+ // invariant that the wrapper in the map points directly to the key.
+ MOZ_ASSERT(Wrapper::wrappedObject(wobj) == newTarget);
+
+ // Update the entry in the compartment's wrapper map to point to the old
+ // wrapper, which has now been updated (via reuse or swap).
+ MOZ_ASSERT(wobj->is<WrapperObject>());
+ if (!wcompartment->putWrapper(cx, CrossCompartmentKey(newTarget), ObjectValue(*wobj)))
+ MOZ_CRASH();
+}
+
+// Remap all cross-compartment wrappers pointing to |oldTarget| to point to
+// |newTarget|. All wrappers are recomputed.
+JS_FRIEND_API(bool)
+js::RemapAllWrappersForObject(JSContext* cx, JSObject* oldTargetArg,
+ JSObject* newTargetArg)
+{
+ MOZ_ASSERT(!IsInsideNursery(oldTargetArg));
+ MOZ_ASSERT(!IsInsideNursery(newTargetArg));
+
+ RootedValue origv(cx, ObjectValue(*oldTargetArg));
+ RootedObject newTarget(cx, newTargetArg);
+
+ AutoWrapperVector toTransplant(cx);
+ if (!toTransplant.reserve(cx->runtime()->numCompartments))
+ return false;
+
+ for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+ if (WrapperMap::Ptr wp = c->lookupWrapper(origv)) {
+ // We found a wrapper. Remember and root it.
+ toTransplant.infallibleAppend(WrapperValue(wp));
+ }
+ }
+
+ for (const WrapperValue& v : toTransplant)
+ RemapWrapper(cx, &v.toObject(), newTarget);
+
+ return true;
+}
+
+JS_FRIEND_API(bool)
+js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
+ const CompartmentFilter& targetFilter)
+{
+ // Drop any nursery-allocated wrappers.
+ cx->runtime()->gc.evictNursery(JS::gcreason::EVICT_NURSERY);
+
+ AutoWrapperVector toRecompute(cx);
+ for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
+ // Filter by source compartment.
+ if (!sourceFilter.match(c))
+ continue;
+
+ // Iterate over the wrappers, filtering appropriately.
+ for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
+ // Filter out non-objects.
+ CrossCompartmentKey& k = e.front().mutableKey();
+ if (!k.is<JSObject*>())
+ continue;
+
+ // Filter by target compartment.
+ if (!targetFilter.match(k.compartment()))
+ continue;
+
+ // Add it to the list.
+ if (!toRecompute.append(WrapperValue(e)))
+ return false;
+ }
+ }
+
+ // Recompute all the wrappers in the list.
+ for (const WrapperValue& v : toRecompute) {
+ JSObject* wrapper = &v.toObject();
+ JSObject* wrapped = Wrapper::wrappedObject(wrapper);
+ RemapWrapper(cx, wrapper, wrapped);
+ }
+
+ return true;
+}