Silenty return undefined instead of throwing when content tries to access non-exposed chrome properties (bug 594999, r=mrbkap).
authorAndreas Gal <gal@mozilla.com>
Sat, 29 Jan 2011 18:48:30 -0800
changeset 61693 2fb3475b30365f1fbceee6298206828a3c6cc0ea
parent 61692 6d5c859c452db930a3e1d620c8b2592a124ff79c
child 61694 59d84fe951ba848ad0ebb05cb17d1ee3fcc97883
push idunknown
push userunknown
push dateunknown
reviewersmrbkap
bugs594999
milestone2.0b11pre
Silenty return undefined instead of throwing when content tries to access non-exposed chrome properties (bug 594999, r=mrbkap).
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/xpconnect/wrappers/AccessCheck.cpp
js/src/xpconnect/wrappers/AccessCheck.h
js/src/xpconnect/wrappers/CrossOriginWrapper.cpp
js/src/xpconnect/wrappers/CrossOriginWrapper.h
js/src/xpconnect/wrappers/FilteringWrapper.cpp
js/src/xpconnect/wrappers/FilteringWrapper.h
js/src/xpconnect/wrappers/XrayWrapper.cpp
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -92,30 +92,32 @@ JSWrapper::JSWrapper(uintN flags) : JSPr
 }
 
 JSWrapper::~JSWrapper()
 {
 }
 
 #define CHECKED(op, act)                                                     \
     JS_BEGIN_MACRO                                                           \
-        if (!enter(cx, wrapper, id, act))                                    \
-            return false;                                                    \
+        bool status;                                                         \
+        if (!enter(cx, wrapper, id, act, &status))                           \
+            return status;                                                   \
         bool ok = (op);                                                      \
         leave(cx, wrapper);                                                  \
         return ok;                                                           \
     JS_END_MACRO
 
 #define SET(action) CHECKED(action, SET)
 #define GET(action) CHECKED(action, GET)
 
 bool
 JSWrapper::getPropertyDescriptor(JSContext *cx, JSObject *wrapper, jsid id,
                                  bool set, PropertyDescriptor *desc)
 {
+    desc->obj= NULL; // default result if we refuse to perform this action
     CHECKED(JS_GetPropertyDescriptorById(cx, wrappedObject(wrapper), id, JSRESOLVE_QUALIFIED,
                                          Jsvalify(desc)), set ? SET : GET);
 }
 
 static bool
 GetOwnPropertyDescriptor(JSContext *cx, JSObject *obj, jsid id, uintN flags, JSPropertyDescriptor *desc)
 {
     if (!JS_GetPropertyDescriptorById(cx, obj, id, flags, desc))
@@ -124,53 +126,57 @@ GetOwnPropertyDescriptor(JSContext *cx, 
         desc->obj = NULL;
     return true;
 }
 
 bool
 JSWrapper::getOwnPropertyDescriptor(JSContext *cx, JSObject *wrapper, jsid id, bool set,
                                     PropertyDescriptor *desc)
 {
+    desc->obj= NULL; // default result if we refuse to perform this action
     CHECKED(GetOwnPropertyDescriptor(cx, wrappedObject(wrapper), id, JSRESOLVE_QUALIFIED,
                                      Jsvalify(desc)), set ? SET : GET);
 }
 
 bool
 JSWrapper::defineProperty(JSContext *cx, JSObject *wrapper, jsid id,
                           PropertyDescriptor *desc)
 {
     SET(JS_DefinePropertyById(cx, wrappedObject(wrapper), id, Jsvalify(desc->value),
                               Jsvalify(desc->getter), Jsvalify(desc->setter), desc->attrs));
 }
 
 bool
 JSWrapper::getOwnPropertyNames(JSContext *cx, JSObject *wrapper, AutoIdVector &props)
 {
+    // if we refuse to perform this action, props remains empty
     jsid id = JSID_VOID;
     GET(GetPropertyNames(cx, wrappedObject(wrapper), JSITER_OWNONLY | JSITER_HIDDEN, &props));
 }
 
 static bool
 ValueToBoolean(Value *vp, bool *bp)
 {
     *bp = js_ValueToBoolean(*vp);
     return true;
 }
 
 bool
 JSWrapper::delete_(JSContext *cx, JSObject *wrapper, jsid id, bool *bp)
 {
+    *bp = true; // default result if we refuse to perform this action
     Value v;
     SET(JS_DeletePropertyById2(cx, wrappedObject(wrapper), id, Jsvalify(&v)) &&
         ValueToBoolean(&v, bp));
 }
 
 bool
 JSWrapper::enumerate(JSContext *cx, JSObject *wrapper, AutoIdVector &props)
 {
+    // if we refuse to perform this action, props remains empty
     static jsid id = JSID_VOID;
     GET(GetPropertyNames(cx, wrappedObject(wrapper), 0, &props));
 }
 
 bool
 JSWrapper::fix(JSContext *cx, JSObject *wrapper, Value *vp)
 {
     vp->setUndefined();
@@ -182,116 +188,139 @@ Cond(JSBool b, bool *bp)
 {
     *bp = !!b;
     return true;
 }
 
 bool
 JSWrapper::has(JSContext *cx, JSObject *wrapper, jsid id, bool *bp)
 {
+    *bp = false; // default result if we refuse to perform this action
     JSBool found;
     GET(JS_HasPropertyById(cx, wrappedObject(wrapper), id, &found) &&
         Cond(found, bp));
 }
 
 bool
 JSWrapper::hasOwn(JSContext *cx, JSObject *wrapper, jsid id, bool *bp)
 {
+    *bp = false; // default result if we refuse to perform this action
     PropertyDescriptor desc;
     JSObject *wobj = wrappedObject(wrapper);
     GET(JS_GetPropertyDescriptorById(cx, wobj, id, JSRESOLVE_QUALIFIED, Jsvalify(&desc)) &&
         Cond(desc.obj == wobj, bp));
 }
 
 bool
 JSWrapper::get(JSContext *cx, JSObject *wrapper, JSObject *receiver, jsid id, Value *vp)
 {
+    vp->setUndefined(); // default result if we refuse to perform this action
     GET(wrappedObject(wrapper)->getProperty(cx, receiver, id, vp));
 }
 
 bool
 JSWrapper::set(JSContext *cx, JSObject *wrapper, JSObject *receiver, jsid id, Value *vp)
 {
     // FIXME (bug 596351): Need deal with strict mode.
     SET(wrappedObject(wrapper)->setProperty(cx, id, vp, false));
 }
 
 bool
 JSWrapper::keys(JSContext *cx, JSObject *wrapper, AutoIdVector &props)
 {
+    // if we refuse to perform this action, props remains empty
     const jsid id = JSID_VOID;
     GET(GetPropertyNames(cx, wrappedObject(wrapper), JSITER_OWNONLY, &props));
 }
 
 bool
 JSWrapper::iterate(JSContext *cx, JSObject *wrapper, uintN flags, Value *vp)
 {
+    vp->setUndefined(); // default result if we refuse to perform this action
     const jsid id = JSID_VOID;
     GET(GetIterator(cx, wrappedObject(wrapper), flags, vp));
 }
 
 bool
 JSWrapper::call(JSContext *cx, JSObject *wrapper, uintN argc, Value *vp)
 {
+    vp->setUndefined(); // default result if we refuse to perform this action
     const jsid id = JSID_VOID;
     CHECKED(JSProxyHandler::call(cx, wrapper, argc, vp), CALL);
 }
 
 bool
-JSWrapper::construct(JSContext *cx, JSObject *wrapper, uintN argc, Value *argv, Value *rval)
+JSWrapper::construct(JSContext *cx, JSObject *wrapper, uintN argc, Value *argv, Value *vp)
 {
+    vp->setUndefined(); // default result if we refuse to perform this action
     const jsid id = JSID_VOID;
-    GET(JSProxyHandler::construct(cx, wrapper, argc, argv, rval));
+    GET(JSProxyHandler::construct(cx, wrapper, argc, argv, vp));
 }
 
 bool
 JSWrapper::hasInstance(JSContext *cx, JSObject *wrapper, const Value *vp, bool *bp)
 {
+    *bp = true; // default result if we refuse to perform this action
     const jsid id = JSID_VOID;
     JSBool b;
     GET(JS_HasInstance(cx, wrappedObject(wrapper), Jsvalify(*vp), &b) && Cond(b, bp));
 }
 
 JSType
 JSWrapper::typeOf(JSContext *cx, JSObject *wrapper)
 {
     return TypeOfValue(cx, ObjectValue(*wrappedObject(wrapper)));
 }
 
 JSString *
 JSWrapper::obj_toString(JSContext *cx, JSObject *wrapper)
 {
-    JSString *str;
-    if (!enter(cx, wrapper, JSID_VOID, GET))
+    bool status;
+    if (!enter(cx, wrapper, JSID_VOID, GET, &status)) {
+        if (status) {
+            // Perform some default behavior that doesn't leak any information.
+            return JS_NewStringCopyZ(cx, "[object Object]");
+        }
         return NULL;
-    str = obj_toStringHelper(cx, wrappedObject(wrapper));
+    }
+    JSString *str = obj_toStringHelper(cx, wrappedObject(wrapper));
     leave(cx, wrapper);
     return str;
 }
 
 JSString *
 JSWrapper::fun_toString(JSContext *cx, JSObject *wrapper, uintN indent)
 {
-    JSString *str;
-    if (!enter(cx, wrapper, JSID_VOID, GET))
+    bool status;
+    if (!enter(cx, wrapper, JSID_VOID, GET, &status)) {
+        if (status) {
+            // Perform some default behavior that doesn't leak any information.
+            if (wrapper->isCallable())
+                return JS_NewStringCopyZ(cx, "function () {\n    [native code]\n}");
+            js::Value v = ObjectValue(*wrapper);
+            js_ReportIsNotFunction(cx, &v, 0);
+            return NULL;
+        }
         return NULL;
-    str = JSProxyHandler::fun_toString(cx, wrapper, indent);
+    }
+    JSString *str = JSProxyHandler::fun_toString(cx, wrapper, indent);
     leave(cx, wrapper);
     return str;
 }
 
 void
 JSWrapper::trace(JSTracer *trc, JSObject *wrapper)
 {
     MarkObject(trc, *wrappedObject(wrapper), "wrappedObject");
 }
 
 bool
-JSWrapper::enter(JSContext *cx, JSObject *wrapper, jsid id, Action act)
+JSWrapper::enter(JSContext *cx, JSObject *wrapper, jsid id, Action act, bool *bp)
 {
+    *bp = true;
     return true;
 }
 
 void
 JSWrapper::leave(JSContext *cx, JSObject *wrapper)
 {
 }
 
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -87,17 +87,17 @@ class JS_FRIEND_API(JSWrapper) : public 
     virtual JSType typeOf(JSContext *cx, JSObject *proxy);
     virtual JSString *obj_toString(JSContext *cx, JSObject *wrapper);
     virtual JSString *fun_toString(JSContext *cx, JSObject *wrapper, uintN indent);
 
     virtual void trace(JSTracer *trc, JSObject *wrapper);
 
     /* Policy enforcement traps. */
     enum Action { GET, SET, CALL };
-    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, Action act);
+    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, Action act, bool *bp);
     virtual void leave(JSContext *cx, JSObject *wrapper);
 
     static JSWrapper singleton;
 
     static JSObject *New(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent,
                          JSWrapper *handler);
 
     static inline JSObject *wrappedObject(const JSObject *wrapper) {
--- a/js/src/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/src/xpconnect/wrappers/AccessCheck.cpp
@@ -420,34 +420,46 @@ AccessCheck::deny(JSContext *cx, jsid id
         const jschar *chars = JS_GetStringCharsZ(cx, str);
         if (chars)
             JS_ReportError(cx, "Permission denied to access property '%hs'", chars);
     }
 }
 
 enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 };
 
+static bool
+Deny(JSContext *cx, jsid id, JSWrapper::Action act)
+{
+    // Refuse to perform the action and just return the default value.
+    if (act == JSWrapper::GET)
+        return true;
+    // If its a set, deny it and throw an exception.
+    AccessCheck::deny(cx, id);
+    return false;
+}
+
 bool
-PermitIfUniversalXPConnect(ExposedPropertiesOnly::Permission &perm)
+PermitIfUniversalXPConnect(JSContext *cx, jsid id, JSWrapper::Action act,
+                           ExposedPropertiesOnly::Permission &perm)
 {
     // If UniversalXPConnect is enabled, allow access even if __exposedProps__ doesn't
     // exists.
     nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
     if (!ssm) {
         return false;
     }
     PRBool privileged;
     if (NS_SUCCEEDED(ssm->IsCapabilityEnabled("UniversalXPConnect", &privileged)) &&
         privileged) {
         perm = ExposedPropertiesOnly::PermitPropertyAccess;
         return true; // Allow
     }
 
-    // Use default
-    return true;
+    // Deny
+    return Deny(cx, id, act);
 }
 
 bool
 ExposedPropertiesOnly::check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
                              Permission &perm)
 {
     JSObject *wrappedObject = JSWrapper::wrappedObject(wrapper);
 
@@ -476,48 +488,48 @@ ExposedPropertiesOnly::check(JSContext *
 
     // If no __exposedProps__ existed, deny access.
     if (!found) {
         // For now, only do this on functions.
         if (!JS_ObjectIsFunction(cx, wrappedObject)) {
             perm = PermitPropertyAccess;
             return true;
         }
-        return PermitIfUniversalXPConnect(perm); // Deny
+        return PermitIfUniversalXPConnect(cx, id, act, perm); // Deny
     }
 
     if (id == JSID_VOID) {
         // This will force the caller to call us back for individual property accesses.
         perm = PermitPropertyAccess;
         return true;
     }
 
     jsval exposedProps;
     if (!JS_LookupPropertyById(cx, wrappedObject, exposedPropsId, &exposedProps))
         return false;
 
     if (JSVAL_IS_VOID(exposedProps) || JSVAL_IS_NULL(exposedProps)) {
-        return PermitIfUniversalXPConnect(perm); // Deny
+        return PermitIfUniversalXPConnect(cx, id, act, perm); // Deny
     }
 
     if (!JSVAL_IS_OBJECT(exposedProps)) {
         JS_ReportError(cx, "__exposedProps__ must be undefined, null, or an Object");
         return false;
     }
 
     JSObject *hallpass = JSVAL_TO_OBJECT(exposedProps);
 
     Access access = NO_ACCESS;
 
     JSPropertyDescriptor desc;
     if (!JS_GetPropertyDescriptorById(cx, hallpass, id, JSRESOLVE_QUALIFIED, &desc)) {
         return false; // Error
     }
     if (desc.obj == NULL || !(desc.attrs & JSPROP_ENUMERATE)) {
-        return PermitIfUniversalXPConnect(perm); // Deny
+        return PermitIfUniversalXPConnect(cx, id, act, perm); // Deny
     }
 
     if (!JSVAL_IS_STRING(desc.value)) {
         JS_ReportError(cx, "property must be a string");
         return false;
     }
 
     JSString *str = JSVAL_TO_STRING(desc.value);
@@ -552,16 +564,16 @@ ExposedPropertiesOnly::check(JSContext *
 
     if (access == NO_ACCESS) {
         JS_ReportError(cx, "specified properties must have a permission bit set");
         return false;
     }
 
     if ((act == JSWrapper::SET && !(access & WRITE)) ||
         (act != JSWrapper::SET && !(access & READ))) {
-        return PermitIfUniversalXPConnect(perm); // Deny
+        return PermitIfUniversalXPConnect(cx, id, act, perm); // Deny
     }
 
     perm = PermitPropertyAccess;
     return true; // Allow
 }
 
 }
--- a/js/src/xpconnect/wrappers/AccessCheck.h
+++ b/js/src/xpconnect/wrappers/AccessCheck.h
@@ -79,46 +79,63 @@ struct Permissive : public Policy {
     }
 };
 
 // This policy only permits access to the object if the subject can touch
 // system objects.
 struct OnlyIfSubjectIsSystem : public Policy {
     static bool check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
                       Permission &perm) {
+        if (AccessCheck::isSystemOnlyAccessPermitted(cx)) {
+            perm = PermitObjectAccess;
+            return true;
+        }
         perm = DenyAccess;
-        if (AccessCheck::isSystemOnlyAccessPermitted(cx))
-            perm = PermitObjectAccess;
-        return true;
+        JSAutoEnterCompartment ac;
+        if (!ac.enter(cx, wrapper))
+            return false;
+        AccessCheck::deny(cx, id);
+        return false;
     }
 };
 
 // This policy only permits access to properties that are safe to be used
 // across origins.
 struct CrossOriginAccessiblePropertiesOnly : public Policy {
     static bool check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
                       Permission &perm) {
+        if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act)) {
+            perm = PermitPropertyAccess;
+            return true;
+        }
         perm = DenyAccess;
-        if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act))
-            perm = PermitPropertyAccess;
-        return true;
+        JSAutoEnterCompartment ac;
+        if (!ac.enter(cx, wrapper))
+            return false;
+        AccessCheck::deny(cx, id);
+        return false;
     }
 };
 
 // This policy only permits access to properties that are safe to be used
 // across origins.
 struct SameOriginOrCrossOriginAccessiblePropertiesOnly : public Policy {
     static bool check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
                       Permission &perm) {
-        perm = DenyAccess;
         if (AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act) ||
             AccessCheck::isLocationObjectSameOrigin(cx, wrapper)) {
             perm = PermitPropertyAccess;
+            return true;
         }
-        return true;
+        perm = DenyAccess;
+        JSAutoEnterCompartment ac;
+        if (!ac.enter(cx, wrapper))
+            return false;
+        AccessCheck::deny(cx, id);
+        return false;
     }
 };
 
 // This policy only permits access to properties if they appear in the
 // objects exposed properties list.
 struct ExposedPropertiesOnly : public Policy {
     static bool check(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act,
                       Permission &perm);
--- a/js/src/xpconnect/wrappers/CrossOriginWrapper.cpp
+++ b/js/src/xpconnect/wrappers/CrossOriginWrapper.cpp
@@ -103,18 +103,19 @@ bool
 CrossOriginWrapper::construct(JSContext *cx, JSObject *wrapper,
                               uintN argc, js::Value *argv, js::Value *rval)
 {
     return JSCrossCompartmentWrapper::construct(cx, wrapper, argc, argv, rval) &&
            WrapperFactory::WaiveXrayAndWrap(cx, js::Jsvalify(rval));
 }
 
 bool
-NoWaiverWrapper::enter(JSContext *cx, JSObject *wrapper, jsid id, Action act)
+NoWaiverWrapper::enter(JSContext *cx, JSObject *wrapper, jsid id, Action act, bool *bp)
 {
+    *bp = true; // always allowed
     nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager();
     if (!ssm) {
         return true;
     }
     JSStackFrame *fp = NULL;
     nsIPrincipal *principal = GetCompartmentPrincipal(wrappedObject(wrapper)->compartment());
     nsresult rv = ssm->PushContextPrincipal(cx, JS_FrameIterator(cx, &fp), principal);
     if (NS_FAILED(rv)) {
--- a/js/src/xpconnect/wrappers/CrossOriginWrapper.h
+++ b/js/src/xpconnect/wrappers/CrossOriginWrapper.h
@@ -45,17 +45,17 @@
 
 namespace xpc {
 
 class NoWaiverWrapper : public JSCrossCompartmentWrapper {
   public:
     NoWaiverWrapper(uintN flags);
     virtual ~NoWaiverWrapper();
 
-    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, Action act);
+    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, Action act, bool *bp);
     virtual void leave(JSContext *cx, JSObject *wrapper);
 
     static NoWaiverWrapper singleton;
 };
 
 class CrossOriginWrapper : public NoWaiverWrapper {
   public:
     CrossOriginWrapper(uintN flags);
--- a/js/src/xpconnect/wrappers/FilteringWrapper.cpp
+++ b/js/src/xpconnect/wrappers/FilteringWrapper.cpp
@@ -77,38 +77,16 @@ Filter(JSContext *cx, JSObject *wrapper,
             return false; // Error
         if (perm != DenyAccess)
             props[w++] = id;
     }
     props.resize(w);
     return true;
 }
 
-template <typename Policy>
-static bool
-CheckAndReport(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act, Permission &perm)
-{
-    if (!Policy::check(cx, wrapper, id, act, perm)) {
-        return false;
-    }
-    if (perm == DenyAccess) {
-        // Reporting an error here indicates a problem entering the
-        // compartment. Therefore, any errors that we throw should be
-        // thrown in our *caller's* compartment, so they can inspect
-        // the error object.
-        JSAutoEnterCompartment ac;
-        if (!ac.enter(cx, wrapper))
-            return false;
-
-        AccessCheck::deny(cx, id);
-        return false;
-    }
-    return true;
-}
-
 template <typename Base, typename Policy>
 bool
 FilteringWrapper<Base, Policy>::getOwnPropertyNames(JSContext *cx, JSObject *wrapper, AutoIdVector &props)
 {
     return Base::getOwnPropertyNames(cx, wrapper, props) &&
            Filter<Policy>(cx, wrapper, props);
 }
 
@@ -137,21 +115,27 @@ FilteringWrapper<Base, Policy>::iterate(
     // the default proxy iterate trap, which will ask enumerate() for the list
     // of (consored) ids.
     return JSProxyHandler::iterate(cx, wrapper, flags, vp);
 }
 
 template <typename Base, typename Policy>
 bool
 FilteringWrapper<Base, Policy>::enter(JSContext *cx, JSObject *wrapper, jsid id,
-                                      JSWrapper::Action act)
+                                      JSWrapper::Action act, bool *bp)
 {
     Permission perm;
-    return CheckAndReport<Policy>(cx, wrapper, id, act, perm) &&
-           Base::enter(cx, wrapper, id, act);
+    if (!Policy::check(cx, wrapper, id, act, perm)) {
+        *bp = false;
+        return false;
+    }
+    *bp = true;
+    if (perm == DenyAccess)
+        return false;
+    return Base::enter(cx, wrapper, id, act, bp);
 }
 
 #define SOW FilteringWrapper<JSCrossCompartmentWrapper, OnlyIfSubjectIsSystem>
 #define SCSOW FilteringWrapper<JSWrapper, OnlyIfSubjectIsSystem>
 #define COW FilteringWrapper<JSCrossCompartmentWrapper, ExposedPropertiesOnly>
 #define XOW FilteringWrapper<XrayWrapper<JSCrossCompartmentWrapper>, \
                              CrossOriginAccessiblePropertiesOnly>
 #define NNXOW FilteringWrapper<JSCrossCompartmentWrapper, CrossOriginAccessiblePropertiesOnly>
--- a/js/src/xpconnect/wrappers/FilteringWrapper.h
+++ b/js/src/xpconnect/wrappers/FilteringWrapper.h
@@ -48,14 +48,14 @@ class FilteringWrapper : public Base {
     FilteringWrapper(uintN flags);
     virtual ~FilteringWrapper();
 
     virtual bool getOwnPropertyNames(JSContext *cx, JSObject *wrapper, js::AutoIdVector &props);
     virtual bool enumerate(JSContext *cx, JSObject *wrapper, js::AutoIdVector &props);
     virtual bool keys(JSContext *cx, JSObject *wrapper, js::AutoIdVector &props);
     virtual bool iterate(JSContext *cx, JSObject *proxy, uintN flags, js::Value *vp);
 
-    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act);
+    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id, JSWrapper::Action act, bool *bp);
 
     static FilteringWrapper singleton;
 };
 
 }
--- a/js/src/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/src/xpconnect/wrappers/XrayWrapper.cpp
@@ -416,18 +416,21 @@ IsTransparent(JSContext *cx, JSObject *w
 template <typename Base>
 bool
 XrayWrapper<Base>::resolveOwnProperty(JSContext *cx, JSObject *wrapper, jsid id, bool set,
                                       PropertyDescriptor *desc_in)
 {
     JSPropertyDescriptor *desc = Jsvalify(desc_in);
 
     if (id == nsXPConnect::GetRuntimeInstance()->GetStringID(XPCJSRuntime::IDX_WRAPPED_JSOBJECT)) {
-        if (!this->enter(cx, wrapper, id, set ? JSWrapper::SET : JSWrapper::GET))
-            return false;
+        bool status;
+        JSWrapper::Action action = set ? JSWrapper::SET : JSWrapper::GET;
+        desc->obj = NULL; // default value
+        if (!this->enter(cx, wrapper, id, action, &status))
+            return status;
 
         AutoLeaveHelper<Base> helper(*this, cx, wrapper);
 
         desc->obj = wrapper;
         desc->attrs = JSPROP_ENUMERATE|JSPROP_SHARED;
         desc->getter = wrappedJSObject_getter;
         desc->setter = NULL;
         desc->shortid = 0;
@@ -488,18 +491,21 @@ XrayWrapper<Base>::getPropertyDescriptor
 {
     JSPropertyDescriptor *desc = Jsvalify(desc_in);
     JSObject *holder = GetHolder(wrapper);
     if (IsResolving(holder, id)) {
         desc->obj = NULL;
         return true;
     }
 
-    if (!this->enter(cx, wrapper, id, set ? JSWrapper::SET : JSWrapper::GET))
-        return false;
+    bool status;
+    JSWrapper::Action action = set ? JSWrapper::SET : JSWrapper::GET;
+    desc->obj = NULL; // default value
+    if (!this->enter(cx, wrapper, id, action, &status))
+        return status;
 
     AutoLeaveHelper<Base> helper(*this, cx, wrapper);
 
     ResolvingId resolving(holder, id);
 
     // Redirect access straight to the wrapper if we should be transparent.
     if (Transparent(cx, wrapper)) {
         JSObject *wnObject = GetWrappedNativeObjectFromHolder(cx, holder);
@@ -553,18 +559,21 @@ XrayWrapper<Base>::getOwnPropertyDescrip
 {
     JSPropertyDescriptor *desc = Jsvalify(desc_in);
     JSObject *holder = GetHolder(wrapper);
     if (IsResolving(holder, id)) {
         desc->obj = NULL;
         return true;
     }
 
-    if (!this->enter(cx, wrapper, id, set ? JSWrapper::SET : JSWrapper::GET))
-        return false;
+    bool status;
+    JSWrapper::Action action = set ? JSWrapper::SET : JSWrapper::GET;
+    desc->obj = NULL; // default value
+    if (!this->enter(cx, wrapper, id, action, &status))
+        return status;
 
     AutoLeaveHelper<Base> helper(*this, cx, wrapper);
 
     ResolvingId resolving(holder, id);
 
     // NB: Nothing we do here acts on the wrapped native itself, so we don't
     // enter our policy.
     // Redirect access straight to the wrapper if we should be transparent.