diff --git a/js/src/jsapi-tests/testBug604087.cpp b/js/src/jsapi-tests/testBug604087.cpp index 96802a4e2322..b2409c228651 100644 --- a/js/src/jsapi-tests/testBug604087.cpp +++ b/js/src/jsapi-tests/testBug604087.cpp @@ -56,7 +56,8 @@ PreWrap(JSContext *cx, JSObject *scopeArg, JSObject *objArg, unsigned flags) } static JSObject * -Wrap(JSContext *cx, JSObject *objArg, JSObject *protoArg, JSObject *parentArg, unsigned flags) +Wrap(JSContext *cx, JSObject *existing, JSObject *objArg, + JSObject *protoArg, JSObject *parentArg, unsigned flags) { js::RootedObject obj(cx, objArg); js::RootedObject proto(cx, protoArg); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index e1e1ee6a23e9..d423a929f46e 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1950,10 +1950,15 @@ typedef JSBool /* * Callback used to ask the embedding for the cross compartment wrapper handler * that implements the desired prolicy for this kind of object in the - * destination compartment. + * destination compartment. |obj| is the object to be wrapped. If |existing| is + * non-NULL, it will point to an existing wrapper object that should be re-used + * if possible. |existing| is guaranteed to be a cross-compartment wrapper with + * a lazily-defined prototype and the correct global. It is guaranteed not to + * wrap a function. */ typedef JSObject * -(* JSWrapObjectCallback)(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent, +(* JSWrapObjectCallback)(JSContext *cx, JSObject *existing, JSObject *obj, + JSObject *proto, JSObject *parent, unsigned flags); /* diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index a0701d55107b..c6381d36c4f5 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -193,9 +193,12 @@ WrapForSameCompartment(JSContext *cx, HandleObject obj, Value *vp) } bool -JSCompartment::wrap(JSContext *cx, Value *vp) +JSCompartment::wrap(JSContext *cx, Value *vp, JSObject *existing) { JS_ASSERT(cx->compartment == this); + JS_ASSERT_IF(existing, existing->compartment() == cx->compartment); + JS_ASSERT_IF(existing, vp->isObject()); + JS_ASSERT_IF(existing, IsDeadProxyObject(existing)); unsigned flags = 0; @@ -304,14 +307,25 @@ JSCompartment::wrap(JSContext *cx, Value *vp) RootedObject obj(cx, &vp->toObject()); + /* See if we can reuse |existing| as the wrapper for |obj|. */ JSObject *proto = Proxy::LazyProto; + if (existing) { + if (!existing->getTaggedProto().isLazy() || + existing->getClass() != &ObjectProxyClass || + existing->getParent() != global || + obj->isCallable()) + { + existing = NULL; + } + } /* * We hand in the original wrapped object into the wrap hook to allow * the wrap hook to reason over what wrappers are currently applied * to the object. */ - RootedObject wrapper(cx, cx->runtime->wrapObjectCallback(cx, obj, proto, global, flags)); + RootedObject wrapper(cx); + wrapper = cx->runtime->wrapObjectCallback(cx, existing, obj, proto, global, flags); if (!wrapper) return false; @@ -348,12 +362,12 @@ JSCompartment::wrap(JSContext *cx, HeapPtrString *strp) } bool -JSCompartment::wrap(JSContext *cx, JSObject **objp) +JSCompartment::wrap(JSContext *cx, JSObject **objp, JSObject *existing) { if (!*objp) return true; RootedValue value(cx, ObjectValue(**objp)); - if (!wrap(cx, value.address())) + if (!wrap(cx, value.address(), existing)) return false; *objp = &value.get().toObject(); return true; diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index 2c1d3035ce97..239c21c1e508 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -349,10 +349,10 @@ struct JSCompartment /* Mark cross-compartment wrappers. */ void markCrossCompartmentWrappers(JSTracer *trc); - bool wrap(JSContext *cx, js::Value *vp); + bool wrap(JSContext *cx, js::Value *vp, JSObject *existing = NULL); bool wrap(JSContext *cx, JSString **strp); bool wrap(JSContext *cx, js::HeapPtrString *strp); - bool wrap(JSContext *cx, JSObject **objp); + bool wrap(JSContext *cx, JSObject **objp, JSObject *existing = NULL); bool wrapId(JSContext *cx, jsid *idp); bool wrap(JSContext *cx, js::PropertyOp *op); bool wrap(JSContext *cx, js::StrictPropertyOp *op); diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 084582881a2d..8721cd683292 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -3143,6 +3143,23 @@ js::NewProxyObject(JSContext *cx, BaseProxyHandler *handler, const Value &priv_, return NewProxyObject(cx, handler, priv_, TaggedProto(proto_), parent_, call_, construct_); } +JSObject * +js::RenewProxyObject(JSContext *cx, JSObject *obj, + BaseProxyHandler *handler, Value priv) +{ + JS_ASSERT(obj->getParent() == cx->global()); + JS_ASSERT(obj->getClass() == &ObjectProxyClass); + JS_ASSERT(obj->getTaggedProto().isLazy()); + JS_ASSERT(!handler->isOuterWindow()); + + obj->setSlot(JSSLOT_PROXY_HANDLER, PrivateValue(handler)); + obj->setCrossCompartmentSlot(JSSLOT_PROXY_PRIVATE, priv); + obj->setSlot(JSSLOT_PROXY_EXTRA + 0, UndefinedValue()); + obj->setSlot(JSSLOT_PROXY_EXTRA + 1, UndefinedValue()); + + return obj; +} + static JSBool proxy(JSContext *cx, unsigned argc, jsval *vp) { diff --git a/js/src/jsproxy.h b/js/src/jsproxy.h index 6c65a671c193..73ea5c8c70d8 100644 --- a/js/src/jsproxy.h +++ b/js/src/jsproxy.h @@ -318,6 +318,9 @@ NewProxyObject(JSContext *cx, BaseProxyHandler *handler, const Value &priv, JSObject *proto, JSObject *parent, JSObject *call = NULL, JSObject *construct = NULL); +JSObject * +RenewProxyObject(JSContext *cx, JSObject *obj, BaseProxyHandler *handler, Value priv); + } /* namespace js */ extern JS_FRIEND_API(JSObject *) diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index 125e988f49a4..d3288e1b1253 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -56,6 +56,21 @@ Wrapper::New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent, obj->isCallable() ? obj : NULL, NULL); } +JSObject * +Wrapper::Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler) +{ +#if JS_HAS_XML_SUPPORT + if (obj->isXML()) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, + JSMSG_CANT_WRAP_XML_OBJECT); + return NULL; + } +#endif + + JS_ASSERT(!obj->isCallable()); + return RenewProxyObject(cx, existing, handler, ObjectValue(*obj)); +} + Wrapper * Wrapper::wrapperHandler(RawObject wrapper) { @@ -357,7 +372,8 @@ Wrapper Wrapper::singletonWithPrototype((unsigned)0, true); /* Compartments. */ extern JSObject * -js::TransparentObjectWrapper(JSContext *cx, JSObject *objArg, JSObject *wrappedProtoArg, JSObject *parentArg, +js::TransparentObjectWrapper(JSContext *cx, JSObject *existing, JSObject *objArg, + JSObject *wrappedProtoArg, JSObject *parentArg, unsigned flags) { RootedObject obj(cx, objArg); @@ -967,6 +983,12 @@ js::NewDeadProxyObject(JSContext *cx, JSObject *parent) NULL, parent, NULL, NULL); } +bool +js::IsDeadProxyObject(RawObject obj) +{ + return IsProxy(obj) && GetProxyHandler(obj) == &DeadObjectProxy::singleton; +} + void js::NukeCrossCompartmentWrapper(JSContext *cx, JSObject *wrapper) { @@ -1063,25 +1085,32 @@ js::RemapWrapper(JSContext *cx, JSObject *wobj, JSObject *newTarget) // immediately cease to be a cross-compartment wrapper. Neuter it. NukeCrossCompartmentWrapper(cx, wobj); - // First, we wrap it in the new compartment. This will return - // a new wrapper. + // 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. JSObject *tobj = newTarget; AutoCompartment ac(cx, wobj); - if (!wcompartment->wrap(cx, &tobj)) + if (!wcompartment->wrap(cx, &tobj, wobj)) return false; - // Now, because we need to maintain object identity, we do a - // brain transplant on the old object. At the same time, we - // update the entry in the compartment's wrapper map to point - // to the old wrapper. - JS_ASSERT(tobj != wobj); - if (!wobj->swap(cx, tobj)) - return false; + // 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 (!wobj->swap(cx, tobj)) + return false; + } // Before swapping, this wrapper came out of wrap(), which enforces the // invariant that the wrapper in the map points directly to the key. JS_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). pmap.put(ObjectValue(*newTarget), ObjectValue(*wobj)); return true; } diff --git a/js/src/jswrapper.h b/js/src/jswrapper.h index e8f834ab0753..84cf80215a5f 100644 --- a/js/src/jswrapper.h +++ b/js/src/jswrapper.h @@ -46,6 +46,8 @@ class JS_FRIEND_API(Wrapper) : public DirectProxyHandler static JSObject *New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent, Wrapper *handler); + static JSObject *Renew(JSContext *cx, JSObject *existing, JSObject *obj, Wrapper *handler); + static Wrapper *wrapperHandler(RawObject wrapper); static JSObject *wrappedObject(RawObject wrapper); @@ -235,7 +237,8 @@ class JS_FRIEND_API(DeadObjectProxy) : public BaseProxyHandler }; extern JSObject * -TransparentObjectWrapper(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSObject *parent, +TransparentObjectWrapper(JSContext *cx, JSObject *existing, JSObject *obj, + JSObject *wrappedProto, JSObject *parent, unsigned flags); // Proxy family for wrappers. Public so that IsWrapper() can be fully inlined by @@ -270,6 +273,9 @@ UnwrapOneChecked(JSContext *cx, HandleObject obj); JS_FRIEND_API(bool) IsCrossCompartmentWrapper(RawObject obj); +bool +IsDeadProxyObject(RawObject obj); + JSObject * NewDeadProxyObject(JSContext *cx, JSObject *parent); diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index b1f8e18a935a..092bea3071e3 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -292,7 +292,8 @@ GetWrappedNative(JSContext *cx, JSObject *obj) } JSObject * -WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSObject *parent, +WrapperFactory::Rewrap(JSContext *cx, JSObject *existing, JSObject *obj, + JSObject *wrappedProto, JSObject *parent, unsigned flags) { NS_ASSERTION(!IsWrapper(obj) || @@ -448,6 +449,9 @@ WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, JSO } } + if (existing && proxyProto == wrappedProto) + return Wrapper::Renew(cx, existing, obj, wrapper); + return Wrapper::New(cx, obj, proxyProto, parent, wrapper); } diff --git a/js/xpconnect/wrappers/WrapperFactory.h b/js/xpconnect/wrappers/WrapperFactory.h index 5c0661aaf87c..cc6e2838fe33 100644 --- a/js/xpconnect/wrappers/WrapperFactory.h +++ b/js/xpconnect/wrappers/WrapperFactory.h @@ -59,6 +59,7 @@ class WrapperFactory { // Rewrap an object that is about to cross compartment boundaries. static JSObject *Rewrap(JSContext *cx, + JSObject *existing, JSObject *obj, JSObject *wrappedProto, JSObject *parent,