Bug 803376 - Allow wrappers to be reused (r=bholley)

This commit is contained in:
Bill McCloskey 2012-09-11 17:14:24 -07:00
parent e3f39b6c2e
commit 89122b3c84
10 changed files with 102 additions and 22 deletions

View File

@ -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);

View File

@ -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);
/*

View File

@ -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;

View File

@ -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);

View File

@ -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)
{

View File

@ -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 *)

View File

@ -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;
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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,