Bug 597118 - Give proxy handler the ability to have an innerObject hook. r=gal
authorBlake Kaplan <mrbkap@gmail.com>
Mon, 23 Aug 2010 15:34:11 -0700
changeset 54404 8de49a455d776e56ec7dbf4226c9fc35d2573cd2
parent 54403 f1865de38a60dbb7dc6ab068b7dce2bccb51387f
child 54405 e578b218b6717fb2e6af6944fcb91e74f01c68f5
push idunknown
push userunknown
push dateunknown
reviewersgal
bugs597118
milestone2.0b6pre
Bug 597118 - Give proxy handler the ability to have an innerObject hook. r=gal
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jswrapper.cpp
js/src/xpconnect/wrappers/WrapperFactory.cpp
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -803,16 +803,22 @@ JSProxy::obj_toString(JSContext *cx, JSO
 
 JSString *
 JSProxy::fun_toString(JSContext *cx, JSObject *proxy, uintN indent)
 {
     AutoPendingProxyOperation pending(cx, proxy);
     return proxy->getProxyHandler()->fun_toString(cx, proxy, indent);
 }
 
+static JSObject *
+proxy_innerObject(JSContext *cx, JSObject *obj)
+{
+    return obj->getProxyPrivate().toObjectOrNull();
+}
+
 static JSBool
 proxy_LookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp,
                      JSProperty **propp)
 {
     bool found;
     if (!JSProxy::has(cx, obj, id, &found))
         return false;
 
@@ -939,16 +945,56 @@ JS_FRIEND_API(Class) ObjectProxyClass = 
         NULL,       /* enumerate       */
         NULL,       /* typeof          */
         proxy_TraceObject,
         NULL,       /* thisObject      */
         proxy_Finalize, /* clear */
     }
 };
 
+JS_FRIEND_API(Class) OuterWindowProxyClass = {
+    "Proxy",
+    Class::NON_NATIVE | JSCLASS_HAS_RESERVED_SLOTS(2),
+    PropertyStub,   /* addProperty */
+    PropertyStub,   /* delProperty */
+    PropertyStub,   /* getProperty */
+    PropertyStub,   /* setProperty */
+    EnumerateStub,
+    ResolveStub,
+    ConvertStub,
+    NULL,           /* finalize */
+    NULL,           /* reserved0   */
+    NULL,           /* checkAccess */
+    NULL,           /* call        */
+    NULL,           /* construct   */
+    NULL,           /* xdrObject   */
+    NULL,           /* hasInstance */
+    NULL,           /* mark        */
+    {
+        NULL,       /* equality    */
+        NULL,       /* outerObject */
+        proxy_innerObject,
+        NULL,       /* wrappedObject */
+    },
+    {
+        proxy_LookupProperty,
+        proxy_DefineProperty,
+        proxy_GetProperty,
+        proxy_SetProperty,
+        proxy_GetAttributes,
+        proxy_SetAttributes,
+        proxy_DeleteProperty,
+        NULL,       /* enumerate       */
+        NULL,       /* typeof          */
+        proxy_TraceObject,
+        NULL,       /* thisObject      */
+        proxy_Finalize, /* clear */
+    }
+};
+
 JSBool
 proxy_Call(JSContext *cx, uintN argc, Value *vp)
 {
     JSObject *proxy = &JS_CALLEE(cx, vp).toObject();
     JS_ASSERT(proxy->isProxy());
     return JSProxy::call(cx, proxy, argc, vp);
 }
 
@@ -1006,17 +1052,21 @@ JS_FRIEND_API(Class) FunctionProxyClass 
     }
 };
 
 JS_FRIEND_API(JSObject *)
 NewProxyObject(JSContext *cx, JSProxyHandler *handler, const Value &priv, JSObject *proto,
                JSObject *parent, JSObject *call, JSObject *construct)
 {
     bool fun = call || construct;
-    Class *clasp = fun ? &FunctionProxyClass : &ObjectProxyClass;
+    Class *clasp;
+    if (fun)
+        clasp = &FunctionProxyClass;
+    else
+        clasp = handler->isOuterWindow() ? &OuterWindowProxyClass : &ObjectProxyClass;
     JSObject *obj = NewNonFunction<WithProto::Given>(cx, clasp, proto, parent);
     if (!obj || (construct && !obj->ensureInstanceReservedSlots(cx, 0)))
         return NULL;
     obj->setSlot(JSSLOT_PROXY_HANDLER, PrivateValue(handler));
     obj->setSlot(JSSLOT_PROXY_PRIVATE, priv);
     if (fun) {
         obj->setSlot(JSSLOT_PROXY_CALL, call ? ObjectValue(*call) : UndefinedValue());
         if (construct) {
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -79,16 +79,20 @@ class JSProxyHandler {
     virtual JS_FRIEND_API(bool) call(JSContext *cx, JSObject *proxy, uintN argc, js::Value *vp);
     virtual JS_FRIEND_API(bool) construct(JSContext *cx, JSObject *proxy,
                                           uintN argc, js::Value *argv, js::Value *rval);
     virtual JS_FRIEND_API(JSString *) obj_toString(JSContext *cx, JSObject *proxy);
     virtual JS_FRIEND_API(JSString *) fun_toString(JSContext *cx, JSObject *proxy, uintN indent);
     virtual JS_FRIEND_API(void) finalize(JSContext *cx, JSObject *proxy);
     virtual JS_FRIEND_API(void) trace(JSTracer *trc, JSObject *proxy);
 
+    virtual bool isOuterWindow() {
+        return false;
+    }
+
     inline void *family() {
         return mFamily;
     }
 };
 
 /* Dispatch point for handlers that executes the appropriate C++ or scripted traps. */
 class JSProxy {
   public:
@@ -125,24 +129,26 @@ class JSProxy {
 const uint32 JSSLOT_PROXY_HANDLER = JSSLOT_PRIVATE + 0;
 const uint32 JSSLOT_PROXY_PRIVATE = JSSLOT_PRIVATE + 1;
 /* Function proxies only. */
 const uint32 JSSLOT_PROXY_CALL = JSSLOT_PRIVATE + 2;
 const uint32 JSSLOT_PROXY_CONSTRUCT = JSSLOT_PRIVATE + 3;
 
 extern JS_FRIEND_API(js::Class) ObjectProxyClass;
 extern JS_FRIEND_API(js::Class) FunctionProxyClass;
+extern JS_FRIEND_API(js::Class) OuterWindowProxyClass;
 extern js::Class CallableObjectClass;
 
 }
 
 inline bool
 JSObject::isObjectProxy() const
 {
-    return getClass() == &js::ObjectProxyClass;
+    return getClass() == &js::ObjectProxyClass ||
+           getClass() == &js::OuterWindowProxyClass;
 }
 
 inline bool
 JSObject::isFunctionProxy() const
 {
     return getClass() == &js::FunctionProxyClass;
 }
 
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -282,17 +282,18 @@ JSWrapper::New(JSContext *cx, JSObject *
 
 /* Compartments. */
 
 namespace js {
 
 extern JSObject *
 TransparentObjectWrapper(JSContext *cx, JSObject *obj, JSObject *wrappedProto, uintN flags)
 {
-    JS_ASSERT(!obj->isWrapper());
+    // Allow wrapping outer window proxies.
+    JS_ASSERT(!obj->isWrapper() || obj->getClass()->ext.innerObject);
     return JSWrapper::New(cx, obj, wrappedProto, NULL, &JSCrossCompartmentWrapper::singleton);
 }
 
 }
 
 JSCompartment::JSCompartment(JSRuntime *rt)
   : rt(rt), principals(NULL), data(NULL), marked(false), debugMode(false)
 {
@@ -323,18 +324,32 @@ JSCompartment::wrap(JSContext *cx, Value
         return true;
 
     /* Static strings do not have to be wrapped. */
     if (vp->isString() && JSString::isStatic(vp->toString()))
         return true;
 
     /* Unwrap incoming objects. */
     if (vp->isObject()) {
-        JSObject *obj = vp->toObject().unwrap(&flags);
-        vp->setObject(*obj);
+        JSObject *obj = &vp->toObject();
+
+        /* If the object is already in this compartment, we are done. */
+        if (obj->getCompartment(cx) == this)
+            return true;
+
+        /* Don't unwrap an outer window proxy. */
+        if (!obj->getClass()->ext.innerObject) {
+            obj = vp->toObject().unwrap(&flags);
+            OBJ_TO_OUTER_OBJECT(cx, obj);
+            if (!obj)
+                return false;
+
+            vp->setObject(*obj);
+        }
+
         /* If the wrapped object is already in this compartment, we are done. */
         if (obj->getCompartment(cx) == this)
             return true;
     }
 
     /* If we already have a wrapper for this value, use it. */
     if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(*vp)) {
         *vp = p->value;
--- a/js/src/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/src/xpconnect/wrappers/WrapperFactory.cpp
@@ -62,17 +62,18 @@ JSWrapper WaiveXrayWrapperWrapper(Wrappe
 // chrome, we wrap them into a special cross-compartment wrapper
 // that transitively extends the waiver to all properties we get
 // off it.
 JSCrossCompartmentWrapper XrayWrapperWaivedWrapper(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG);
 
 JSObject *
 WrapperFactory::Rewrap(JSContext *cx, JSObject *obj, JSObject *wrappedProto, uintN flags)
 {
-    NS_ASSERTION(!obj->isWrapper(), "wrapped object passed to rewrap");
+    NS_ASSERTION(!obj->isWrapper() || obj->getClass()->ext.innerObject,
+                 "wrapped object passed to rewrap");
 
     JSCompartment *origin = obj->getCompartment(cx);
     JSCompartment *target = cx->compartment;
 
     JSWrapper *wrapper;
     if (AccessCheck::isChrome(target)) {
         NS_ASSERTION(!AccessCheck::isChrome(origin), "we shouldn't rewrap from chrome into chrome");