Bug 836301 - Hoist enter() calls from {Xray,}Wrapper::foo into Proxy::foo. r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Fri, 22 Feb 2013 08:14:34 -0800
changeset 122668 ab07392f24241194e485b42e9c5b598bba5aaf87
parent 122667 f2fa2d0bb5e6bb1db92006a8f51eec1f54e927ec
child 122669 4d301b2bcad047442fa34470893aff52b4df73ee
push id24356
push usergszorc@mozilla.com
push dateSun, 24 Feb 2013 01:00:12 +0000
treeherdermozilla-central@195e706140d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs836301
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 836301 - Hoist enter() calls from {Xray,}Wrapper::foo into Proxy::foo. r=mrbkap
js/src/jsproxy.cpp
js/src/jswrapper.cpp
js/xpconnect/tests/chrome/test_bug760109.xul
js/xpconnect/tests/unit/test_bug780370.js
js/xpconnect/wrappers/ChromeObjectWrapper.cpp
js/xpconnect/wrappers/ChromeObjectWrapper.h
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -2195,25 +2195,28 @@ ScriptedDirectProxyHandler ScriptedDirec
         if (!handler->getPrototypeOf(cx, proxy, proto.address()))            \
             return false;                                                    \
         if (!proto)                                                          \
             return true;                                                     \
         assertSameCompartment(cx, proxy, proto);                             \
         return protoCall;                                                    \
     JS_END_MACRO                                                             \
 
-
 bool
 Proxy::getPropertyDescriptor(JSContext *cx, JSObject *proxy_, jsid id_, PropertyDescriptor *desc,
                              unsigned flags)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
     RootedId id(cx, id_);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
+    desc->obj = NULL; // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     if (!handler->hasPrototype())
         return handler->getPropertyDescriptor(cx, proxy, id, desc, flags);
     if (!handler->getOwnPropertyDescriptor(cx, proxy, id, desc, flags))
         return false;
     if (desc->obj)
         return true;
     INVOKE_ON_PROTOTYPE(cx, handler, proxy,
                         JS_GetPropertyDescriptorById(cx, proto, id, 0, desc));
@@ -2236,17 +2239,22 @@ Proxy::getPropertyDescriptor(JSContext *
 }
 
 bool
 Proxy::getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy_, jsid id, PropertyDescriptor *desc,
                                 unsigned flags)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->getOwnPropertyDescriptor(cx, proxy, id, desc, flags);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    desc->obj = NULL; // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
+    return handler->getOwnPropertyDescriptor(cx, proxy, id, desc, flags);
 }
 
 bool
 Proxy::getOwnPropertyDescriptor(JSContext *cx, JSObject *proxy_, unsigned flags, jsid id,
                                 Value *vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
@@ -2260,17 +2268,21 @@ Proxy::getOwnPropertyDescriptor(JSContex
     *vp = value;
     return true;
 }
 
 bool
 Proxy::defineProperty(JSContext *cx, JSObject *proxy_, jsid id, PropertyDescriptor *desc)
 {
     JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = GetProxyHandler(proxy_);
     RootedObject proxy(cx, proxy_);
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     return GetProxyHandler(proxy)->defineProperty(cx, proxy, id, desc);
 }
 
 bool
 Proxy::defineProperty(JSContext *cx, JSObject *proxy_, jsid id_, const Value &v)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
@@ -2279,25 +2291,34 @@ Proxy::defineProperty(JSContext *cx, JSO
     return ParsePropertyDescriptorObject(cx, proxy, v, &desc) &&
            Proxy::defineProperty(cx, proxy, id, &desc);
 }
 
 bool
 Proxy::getOwnPropertyNames(JSContext *cx, JSObject *proxy_, AutoIdVector &props)
 {
     JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = GetProxyHandler(proxy_);
     RootedObject proxy(cx, proxy_);
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     return GetProxyHandler(proxy)->getOwnPropertyNames(cx, proxy, props);
 }
 
 bool
 Proxy::delete_(JSContext *cx, JSObject *proxy_, jsid id, bool *bp)
 {
     JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = GetProxyHandler(proxy_);
     RootedObject proxy(cx, proxy_);
+    *bp = true; // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     return GetProxyHandler(proxy)->delete_(cx, proxy, id, bp);
 }
 
 JS_FRIEND_API(bool)
 js::AppendUnique(JSContext *cx, AutoIdVector &base, AutoIdVector &others)
 {
     AutoIdVector uniqueOthers(cx);
     if (!uniqueOthers.reserve(others.length()))
@@ -2317,16 +2338,19 @@ js::AppendUnique(JSContext *cx, AutoIdVe
 }
 
 bool
 Proxy::enumerate(JSContext *cx, JSObject *proxy_, AutoIdVector &props)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     if (!handler->hasPrototype())
         return GetProxyHandler(proxy)->enumerate(cx, proxy, props);
     if (!handler->keys(cx, proxy, props))
         return false;
     AutoIdVector protoProps(cx);
     INVOKE_ON_PROTOTYPE(cx, handler, proxy,
                         GetPropertyNames(cx, proto, 0, &protoProps) &&
                         AppendUnique(cx, props, protoProps));
@@ -2334,16 +2358,20 @@ Proxy::enumerate(JSContext *cx, JSObject
 
 bool
 Proxy::has(JSContext *cx, JSObject *proxy_, jsid id_, bool *bp)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
     RootedId id(cx, id_);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
+    *bp = false; // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     if (!handler->hasPrototype())
         return handler->has(cx, proxy, id, bp);
     if (!handler->hasOwn(cx, proxy, id, bp))
         return false;
     if (*bp)
         return true;
     JSBool Bp;
     INVOKE_ON_PROTOTYPE(cx, handler, proxy,
@@ -2351,25 +2379,34 @@ Proxy::has(JSContext *cx, JSObject *prox
                         ((*bp = Bp) || true));
 }
 
 bool
 Proxy::hasOwn(JSContext *cx, JSObject *proxy_, jsid id, bool *bp)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->hasOwn(cx, proxy, id, bp);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    *bp = false; // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
+    return handler->hasOwn(cx, proxy, id, bp);
 }
 
 bool
 Proxy::get(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id,
            MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
+    vp.setUndefined(); // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     bool own;
     if (!handler->hasPrototype()) {
         own = true;
     } else {
         if (!handler->hasOwn(cx, proxy, id, &own))
             return false;
     }
     if (own)
@@ -2378,27 +2415,30 @@ Proxy::get(JSContext *cx, HandleObject p
 }
 
 bool
 Proxy::getElementIfPresent(JSContext *cx, HandleObject proxy, HandleObject receiver, uint32_t index,
                            MutableHandleValue vp, bool *present)
 {
     JS_CHECK_RECURSION(cx, return false);
 
-    BaseProxyHandler *handler = GetProxyHandler(proxy);
-
-    if (!handler->hasPrototype()) {
-        return GetProxyHandler(proxy)->getElementIfPresent(cx, proxy, receiver, index,
-                                                           vp.address(), present);
-    }
-
     RootedId id(cx);
     if (!IndexToId(cx, index, &id))
         return false;
 
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
+
+    if (!handler->hasPrototype()) {
+        return handler->getElementIfPresent(cx, proxy, receiver, index,
+                                            vp.address(), present);
+    }
+
     bool hasOwn;
     if (!handler->hasOwn(cx, proxy, id, &hasOwn))
         return false;
 
     if (hasOwn) {
         *present = true;
         return GetProxyHandler(proxy)->get(cx, proxy, receiver, id, vp.address());
     }
@@ -2408,16 +2448,19 @@ Proxy::getElementIfPresent(JSContext *cx
 }
 
 bool
 Proxy::set(JSContext *cx, HandleObject proxy, HandleObject receiver, HandleId id, bool strict,
            MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
+    AutoEnterPolicy policy(cx, handler, proxy, id, BaseProxyHandler::SET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     if (handler->hasPrototype()) {
         // If we're using a prototype, we still want to use the proxy trap unless
         // we have a non-own property with a setter.
         bool hasOwn;
         if (!handler->hasOwn(cx, proxy, id, &hasOwn))
             return false;
         if (!hasOwn) {
             RootedObject proto(cx);
@@ -2435,88 +2478,152 @@ Proxy::set(JSContext *cx, HandleObject p
     return handler->set(cx, proxy, receiver, id, strict, vp.address());
 }
 
 bool
 Proxy::keys(JSContext *cx, JSObject *proxy_, AutoIdVector &props)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->keys(cx, proxy, props);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
+    return handler->keys(cx, proxy, props);
 }
 
 bool
 Proxy::iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
-    if (!handler->hasPrototype())
-        return GetProxyHandler(proxy)->iterate(cx, proxy, flags, vp.address());
+    vp.setUndefined(); // default result if we refuse to perform this action
+    if (!handler->hasPrototype()) {
+        AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID,
+                               BaseProxyHandler::GET, /* mayThrow = */ false);
+        // If the policy denies access but wants us to return true, we need
+        // to hand a valid (empty) iterator object to the caller.
+        if (!policy.allowed()) {
+            AutoIdVector props(cx);
+            return policy.returnValue() &&
+                   EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
+        }
+        return handler->iterate(cx, proxy, flags, vp.address());
+    }
     AutoIdVector props(cx);
     // The other Proxy::foo methods do the prototype-aware work for us here.
     if ((flags & JSITER_OWNONLY)
         ? !Proxy::keys(cx, proxy, props)
         : !Proxy::enumerate(cx, proxy, props)) {
         return false;
     }
     return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
 }
 
 bool
 Proxy::call(JSContext *cx, JSObject *proxy_, unsigned argc, Value *vp)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->call(cx, proxy, argc, vp);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+
+    // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
+    // can only set our default value once we're sure that we're not calling the
+    // trap.
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID,
+                           BaseProxyHandler::CALL, true);
+    if (!policy.allowed()) {
+        vp->setUndefined();
+        return policy.returnValue();
+    }
+
+    return handler->call(cx, proxy, argc, vp);
 }
 
 bool
 Proxy::construct(JSContext *cx, JSObject *proxy_, unsigned argc, Value *argv, Value *rval)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->construct(cx, proxy, argc, argv, rval);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+
+    // Because vp[0] is JS_CALLEE on the way in and JS_RVAL on the way out, we
+    // can only set our default value once we're sure that we're not calling the
+    // trap.
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID,
+                           BaseProxyHandler::CALL, true);
+    if (!policy.allowed()) {
+        rval->setUndefined();
+        return policy.returnValue();
+    }
+
+    return handler->construct(cx, proxy, argc, argv, rval);
 }
 
 bool
 Proxy::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args)
 {
     JS_CHECK_RECURSION(cx, return false);
     Rooted<JSObject*> proxy(cx, &args.thisv().toObject());
+    // Note - we don't enter a policy here because our security architecture
+    // guards against nativeCall by overriding the trap itself in the right
+    // circumstances.
     return GetProxyHandler(proxy)->nativeCall(cx, test, impl, args);
 }
 
 bool
 Proxy::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp)
 {
     JS_CHECK_RECURSION(cx, return false);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    *bp = false; // default result if we refuse to perform this action
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID, BaseProxyHandler::GET, true);
+    if (!policy.allowed())
+        return policy.returnValue();
     return GetProxyHandler(proxy)->hasInstance(cx, proxy, v, bp);
 }
 
 bool
 Proxy::objectClassIs(JSObject *proxy_, ESClassValue classValue, JSContext *cx)
 {
     RootedObject proxy(cx, proxy_);
     return GetProxyHandler(proxy)->objectClassIs(proxy, classValue, cx);
 }
 
 JSString *
 Proxy::obj_toString(JSContext *cx, JSObject *proxy_)
 {
     JS_CHECK_RECURSION(cx, return NULL);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->obj_toString(cx, proxy);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID,
+                           BaseProxyHandler::GET, /* mayThrow = */ false);
+    // Do the safe thing if the policy rejects.
+    if (!policy.allowed()) {
+        return handler->BaseProxyHandler::obj_toString(cx, proxy);
+    }
+    return handler->obj_toString(cx, proxy);
 }
 
 JSString *
 Proxy::fun_toString(JSContext *cx, JSObject *proxy_, unsigned indent)
 {
     JS_CHECK_RECURSION(cx, return NULL);
     RootedObject proxy(cx, proxy_);
-    return GetProxyHandler(proxy)->fun_toString(cx, proxy, indent);
+    BaseProxyHandler *handler = GetProxyHandler(proxy);
+    AutoEnterPolicy policy(cx, handler, proxy, JSID_VOID,
+                           BaseProxyHandler::GET, /* mayThrow = */ false);
+    // Do the safe thing if the policy rejects.
+    if (!policy.allowed()) {
+        if (proxy->isCallable())
+            return JS_NewStringCopyZ(cx, "function () {\n    [native code]\n}");
+        ReportIsNotFunction(cx, ObjectValue(*proxy));
+        return NULL;
+    }
+    return handler->fun_toString(cx, proxy, indent);
 }
 
 bool
 Proxy::regexp_toShared(JSContext *cx, JSObject *proxy_, RegExpGuard *g)
 {
     JS_CHECK_RECURSION(cx, return false);
     RootedObject proxy(cx, proxy_);
     return GetProxyHandler(proxy)->regexp_toShared(cx, proxy, g);
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -110,27 +110,16 @@ js::UnwrapOneChecked(RawObject obj, bool
 
 bool
 js::IsCrossCompartmentWrapper(RawObject wrapper)
 {
     return wrapper->isWrapper() &&
            !!(Wrapper::wrapperHandler(wrapper)->flags() & Wrapper::CROSS_COMPARTMENT);
 }
 
-#define CHECKED(op, act)                                                     \
-    JS_BEGIN_MACRO                                                           \
-        AutoEnterPolicy policy(cx, this, wrapper, id, act, true);            \
-        if (!policy.allowed())                                               \
-            return policy.returnValue();                                     \
-        return (op);                                                         \
-    JS_END_MACRO
-
-#define SET(action) CHECKED(action, SET)
-#define GET(action) CHECKED(action, GET)
-
 Wrapper::Wrapper(unsigned flags, bool hasPrototype) : DirectProxyHandler(&sWrapperFamily)
                                                     , mFlags(flags)
                                                     , mSafeToUnwrap(true)
 {
     setHasPrototype(hasPrototype);
 }
 
 Wrapper::~Wrapper()
@@ -138,63 +127,56 @@ Wrapper::~Wrapper()
 }
 
 bool
 Wrapper::getPropertyDescriptor(JSContext *cx, JSObject *wrapperArg,
                                jsid id, PropertyDescriptor *desc, unsigned flags)
 {
     RootedObject wrapper(cx, wrapperArg);
     JS_ASSERT(!hasPrototype()); // Should never be called when there's a prototype.
-    desc->obj = NULL; // default result if we refuse to perform this action
-    CHECKED(DirectProxyHandler::getPropertyDescriptor(cx, wrapper, id, desc, flags), GET);
+    return DirectProxyHandler::getPropertyDescriptor(cx, wrapper, id, desc, flags);
 }
 
 bool
 Wrapper::getOwnPropertyDescriptor(JSContext *cx, JSObject *wrapperArg,
                                   jsid id, PropertyDescriptor *desc, unsigned flags)
 {
     RootedObject wrapper(cx, wrapperArg);
-    desc->obj = NULL; // default result if we refuse to perform this action
-    CHECKED(DirectProxyHandler::getOwnPropertyDescriptor(cx, wrapper, id, desc, flags), GET);
+    return DirectProxyHandler::getOwnPropertyDescriptor(cx, wrapper, id, desc, flags);
 }
 
 bool
 Wrapper::defineProperty(JSContext *cx, JSObject *wrapperArg, jsid id,
                         PropertyDescriptor *desc)
 {
     RootedObject wrapper(cx, wrapperArg);
-    SET(DirectProxyHandler::defineProperty(cx, wrapper, id, desc));
+    return DirectProxyHandler::defineProperty(cx, wrapper, id, desc);
 }
 
 bool
 Wrapper::getOwnPropertyNames(JSContext *cx, JSObject *wrapperArg,
                              AutoIdVector &props)
 {
     RootedObject wrapper(cx, wrapperArg);
-    // if we refuse to perform this action, props remains empty
-    jsid id = JSID_VOID;
-    GET(DirectProxyHandler::getOwnPropertyNames(cx, wrapper, props));
+    return DirectProxyHandler::getOwnPropertyNames(cx, wrapper, props);
 }
 
 bool
 Wrapper::delete_(JSContext *cx, JSObject *wrapperArg, jsid id, bool *bp)
 {
     RootedObject wrapper(cx, wrapperArg);
-    *bp = true; // default result if we refuse to perform this action
-    SET(DirectProxyHandler::delete_(cx, wrapper, id, bp));
+    return DirectProxyHandler::delete_(cx, wrapper, id, bp);
 }
 
 bool
 Wrapper::enumerate(JSContext *cx, JSObject *wrapperArg, AutoIdVector &props)
 {
     RootedObject wrapper(cx, wrapperArg);
     JS_ASSERT(!hasPrototype()); // Should never be called when there's a prototype.
-    // if we refuse to perform this action, props remains empty
-    static jsid id = JSID_VOID;
-    GET(DirectProxyHandler::enumerate(cx, wrapper, props));
+    return DirectProxyHandler::enumerate(cx, wrapper, props);
 }
 
 /*
  * Ordinarily, the convert trap would require unwrapping. However, the default
  * implementation of convert, JS_ConvertStub, obtains a default value by calling
  * the toString/valueOf method on the wrapper, if any. Throwing if we can't unwrap
  * in this case would be overly conservative. To make matters worse, XPConnect sometimes
  * installs a custom convert trap that obtains a default value by calling the
@@ -226,128 +208,96 @@ Wrapper::defaultValue(JSContext *cx, JSO
     return DirectProxyHandler::defaultValue(cx, wrapper, hint, vp);
 }
 
 bool
 Wrapper::has(JSContext *cx, JSObject *wrapperArg, jsid id, bool *bp)
 {
     RootedObject wrapper(cx, wrapperArg);
     JS_ASSERT(!hasPrototype()); // Should never be called when there's a prototype.
-    *bp = false; // default result if we refuse to perform this action
-    GET(DirectProxyHandler::has(cx, wrapper, id, bp));
+    return DirectProxyHandler::has(cx, wrapper, id, bp);
 }
 
 bool
 Wrapper::hasOwn(JSContext *cx, JSObject *wrapperArg, jsid id, bool *bp)
 {
     RootedObject wrapper(cx, wrapperArg);
-    *bp = false; // default result if we refuse to perform this action
-    GET(DirectProxyHandler::hasOwn(cx, wrapper, id, bp));
+    return DirectProxyHandler::hasOwn(cx, wrapper, id, bp);
 }
 
 bool
 Wrapper::get(JSContext *cx, JSObject *wrapperArg, JSObject *receiver, jsid id, Value *vp)
 {
     RootedObject wrapper(cx, wrapperArg);
-    vp->setUndefined(); // default result if we refuse to perform this action
-    GET(DirectProxyHandler::get(cx, wrapper, receiver, id, vp));
+    return DirectProxyHandler::get(cx, wrapper, receiver, id, vp);
 }
 
 bool
 Wrapper::set(JSContext *cx, JSObject *wrapperArg, JSObject *receiver, jsid id, bool strict,
              Value *vp)
 {
     RootedObject wrapper(cx, wrapperArg);
-    SET(DirectProxyHandler::set(cx, wrapper, receiver, id, strict, vp));
+    return DirectProxyHandler::set(cx, wrapper, receiver, id, strict, vp);
 }
 
 bool
 Wrapper::keys(JSContext *cx, JSObject *wrapperArg, AutoIdVector &props)
 {
     RootedObject wrapper(cx, wrapperArg);
-    // if we refuse to perform this action, props remains empty
-    const jsid id = JSID_VOID;
-    GET(DirectProxyHandler::keys(cx, wrapper, props));
+    return DirectProxyHandler::keys(cx, wrapper, props);
 }
 
 bool
 Wrapper::iterate(JSContext *cx, JSObject *wrapperArg, unsigned flags, Value *vp)
 {
     RootedObject wrapper(cx, wrapperArg);
     JS_ASSERT(!hasPrototype()); // Should never be called when there's a prototype.
-    vp->setUndefined(); // default result if we refuse to perform this action
-    const jsid id = JSID_VOID;
-    GET(DirectProxyHandler::iterate(cx, wrapper, flags, vp));
+    return DirectProxyHandler::iterate(cx, wrapper, flags, vp);
 }
 
 bool
 Wrapper::call(JSContext *cx, JSObject *wrapperArg, unsigned argc, Value *vp)
 {
     RootedObject wrapper(cx, wrapperArg);
-    vp->setUndefined(); // default result if we refuse to perform this action
-    const jsid id = JSID_VOID;
-    CHECKED(DirectProxyHandler::call(cx, wrapper, argc, vp), CALL);
+    return DirectProxyHandler::call(cx, wrapper, argc, vp);
 }
 
 bool
 Wrapper::construct(JSContext *cx, JSObject *wrapperArg, unsigned argc, Value *argv, Value *vp)
 {
     RootedObject wrapper(cx, wrapperArg);
-    vp->setUndefined(); // default result if we refuse to perform this action
-    const jsid id = JSID_VOID;
-    CHECKED(DirectProxyHandler::construct(cx, wrapper, argc, argv, vp), CALL);
+    return DirectProxyHandler::construct(cx, wrapper, argc, argv, vp);
 }
 
 bool
 Wrapper::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args)
 {
     RootedObject wrapper(cx, &args.thisv().toObject());
     // Note - we don't enter a policy here because our security architecture guards
     // against nativeCall by overriding the trap itself in the right circumstances.
     return DirectProxyHandler::nativeCall(cx, test, impl, args);
 }
 
 bool
 Wrapper::hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v, bool *bp)
 {
-    *bp = false; // default result if we refuse to perform this action
-    const jsid id = JSID_VOID;
-    GET(DirectProxyHandler::hasInstance(cx, wrapper, v, bp));
+    return DirectProxyHandler::hasInstance(cx, wrapper, v, bp);
 }
 
 JSString *
 Wrapper::obj_toString(JSContext *cx, JSObject *wrapperArg)
 {
     RootedObject wrapper(cx, wrapperArg);
-    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;
-    }
     JSString *str = DirectProxyHandler::obj_toString(cx, wrapper);
     return str;
 }
 
 JSString *
 Wrapper::fun_toString(JSContext *cx, JSObject *wrapper, unsigned indent)
 {
-    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}");
-            ReportIsNotFunction(cx, ObjectValue(*wrapper));
-            return NULL;
-        }
-        return NULL;
-    }
     JSString *str = DirectProxyHandler::fun_toString(cx, wrapper, indent);
     return str;
 }
 
 Wrapper Wrapper::singleton((unsigned)0);
 Wrapper Wrapper::singletonWithPrototype((unsigned)0, true);
 
 /* Compartments. */
--- a/js/xpconnect/tests/chrome/test_bug760109.xul
+++ b/js/xpconnect/tests/chrome/test_bug760109.xul
@@ -26,22 +26,23 @@ https://bugzilla.mozilla.org/show_bug.cg
     // Check that COWs for objects with standard prototypes use the standard
     // prototype in the home compartment.
     ok(Object.getPrototypeOf(chromeArray) === Array.prototype,
        "Array prototype remapped properly");
     var protoProto = Object.getPrototypeOf(Object.getPrototypeOf(chromeObject));
     ok(protoProto === Object.prototype,
        "Object prototype remapped properly");
 
-    // Check |constructor|. The semantics of this weird for the case of an
-    // object with a custom chrome-implemented prototype, because we'll end up
-    // bouncing up the prototype chain to Object, even though that's not fully
-    // accurate. It's unlikely to be a problem though, so we just verify that
-    // it does what we expect.
-    ok(chromeObject.constructor === Object, "Object constructor does what we expect");
+    // Check |constructor|.
+    // Note that the 'constructor' property of the underlying chrome object
+    // will be resolved on SomeConstructor.prototype, which has an empty
+    // __exposedProps__. This means that we shouldn't remap the property, even
+    // though we'd also be able to find it on Object.prototype. Some recent
+    // refactoring has made it possible to do the right thing here.
+    is(typeof chromeObject.constructor, "undefined", "Object constructor does what we expect");
     ok(chromeArray.constructor === Array, "Array constructor remapped properly");
 
     // We should be able to .forEach on the Array.
     var concat = '';
     chromeArray.forEach(function(val) { concat += val; });
     is(concat, 'abz', "Should be able to .forEach COW-ed Array");
 
     // Try other Array operations.
--- a/js/xpconnect/tests/unit/test_bug780370.js
+++ b/js/xpconnect/tests/unit/test_bug780370.js
@@ -11,10 +11,13 @@ const Cu = Components.utils;
 
 function run_test()
 {
   var sb = Cu.Sandbox("http://www.example.com");
   sb.obj = { foo: 42, __exposedProps__: { hasOwnProperty: 'r' } };
   do_check_eq(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected");
   do_check_true(Cu.evalInSandbox('obj.hasOwnProperty === Object.prototype.hasOwnProperty', sb),
                 "Remapping happens even when the property is explicitly exposed");
-  do_check_eq(Cu.evalInSandbox('Object.prototype.bar = 10; obj.bar', sb), 10);
+  // NB: We used to test for the following, but such behavior became very
+  // difficult to implement in a recent refactor. We're moving away from this
+  // API anyway, so we decided to explicitly drop support for this.
+  // do_check_eq(Cu.evalInSandbox('Object.prototype.bar = 10; obj.bar', sb), 10);
 }
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.cpp
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp
@@ -10,34 +10,63 @@ namespace xpc {
 //
 // One of the reasons for doing this is to allow standard operations like
 // chromeArray.forEach(..) to Just Work without explicitly listing them in
 // __exposedProps__. Since proxies don't automatically inherit behavior from
 // their prototype, we have to instrument the traps to do this manually.
 ChromeObjectWrapper ChromeObjectWrapper::singleton;
 
 static bool
+AllowedByBase(JSContext *cx, JSObject *wrapper, jsid id, js::Wrapper::Action act)
+{
+    MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) ==
+               &ChromeObjectWrapper::singleton);
+    bool bp;
+    ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton;
+    return handler->ChromeObjectWrapperBase::enter(cx, wrapper, id, act, &bp);
+}
+
+static bool
 PropIsFromStandardPrototype(JSContext *cx, JSPropertyDescriptor *desc)
 {
     MOZ_ASSERT(desc->obj);
     JSObject *unwrapped = js::UnwrapObject(desc->obj);
     JSAutoCompartment ac(cx, unwrapped);
     return JS_IdentifyClassPrototype(cx, unwrapped) != JSProto_Null;
 }
 
+// Note that we're past the policy enforcement stage, here, so we can query
+// ChromeObjectWrapperBase and get an unfiltered view of the underlying object.
+// This lets us determine whether the property we would have found (given a
+// transparent wrapper) would have come off a standard prototype.
+static bool
+PropIsFromStandardPrototype(JSContext *cx, JSObject *wrapper, jsid id)
+{
+    MOZ_ASSERT(js::Wrapper::wrapperHandler(wrapper) ==
+               &ChromeObjectWrapper::singleton);
+    JSPropertyDescriptor desc;
+    ChromeObjectWrapper *handler = &ChromeObjectWrapper::singleton;
+    if (!handler->ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id,
+                                                                 &desc, 0) ||
+        !desc.obj)
+    {
+        return false;
+    }
+    return PropIsFromStandardPrototype(cx, &desc);
+}
+
 bool
 ChromeObjectWrapper::getPropertyDescriptor(JSContext *cx, JSObject *wrapper,
                                            jsid id, js::PropertyDescriptor *desc,
                                            unsigned flags)
 {
-    // First, try the lookup on the base wrapper. This can throw for various
-    // reasons, including sets (gets fail silently). There's nothing we can really
-    // do for sets, so we can conveniently propagate any exception we hit here.
+    // First, try a lookup on the base wrapper if permitted.
     desc->obj = NULL;
-    if (!ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id,
+    if (AllowedByBase(cx, wrapper, id, Wrapper::GET) &&
+        !ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id,
                                                         desc, flags)) {
         return false;
     }
 
     // If the property is something that can be found on a standard prototype,
     // prefer the one we'll get via the prototype chain in the content
     // compartment.
     if (desc->obj && PropIsFromStandardPrototype(cx, desc))
@@ -53,19 +82,22 @@ ChromeObjectWrapper::getPropertyDescript
     // If not, try doing the lookup on the prototype.
     MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
     return JS_GetPropertyDescriptorById(cx, wrapperProto, id, 0, desc);
 }
 
 bool
 ChromeObjectWrapper::has(JSContext *cx, JSObject *wrapper, jsid id, bool *bp)
 {
-    // Try the lookup on the base wrapper.
-    if (!ChromeObjectWrapperBase::has(cx, wrapper, id, bp))
+    // Try the lookup on the base wrapper if permitted.
+    if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) &&
+        !ChromeObjectWrapperBase::has(cx, wrapper, id, bp))
+    {
         return false;
+    }
 
     // If we found something or have no prototype, we're done.
     JSObject *wrapperProto;
     if (!JS_GetPrototype(cx, wrapper, &wrapperProto))
         return false;
     if (*bp || !wrapperProto)
         return true;
 
@@ -77,29 +109,24 @@ ChromeObjectWrapper::has(JSContext *cx, 
     *bp = !!desc.obj;
     return true;
 }
 
 bool
 ChromeObjectWrapper::get(JSContext *cx, JSObject *wrapper, JSObject *receiver,
                          jsid id, js::Value *vp)
 {
-    // Start with a call to getPropertyDescriptor. We unfortunately need to do
-    // this because the call signature of ::get doesn't give us any way to
-    // determine the object upon which the property was found.
+    vp->setUndefined();
     JSPropertyDescriptor desc;
-    if (!ChromeObjectWrapperBase::getPropertyDescriptor(cx, wrapper, id, &desc,
-                                                        0)) {
-        return false;
-    }
-
-    // Only call through to the get trap on the underlying object if we'll find
-    // something, and if what we'll find is not on a standard prototype.
-    vp->setUndefined();
-    if (desc.obj && !PropIsFromStandardPrototype(cx, &desc)) {
+    // Only call through to the get trap on the underlying object if we're
+    // allowed to see the property, and if what we'll find is not on a standard
+    // prototype.
+    if (AllowedByBase(cx, wrapper, id, js::Wrapper::GET) &&
+        !PropIsFromStandardPrototype(cx, wrapper, id))
+    {
         // Call the get trap.
         if (!ChromeObjectWrapperBase::get(cx, wrapper, receiver, id, vp))
             return false;
         // If we found something, we're done.
         if (!vp->isUndefined())
             return true;
     }
 
@@ -110,9 +137,25 @@ ChromeObjectWrapper::get(JSContext *cx, 
     if (!wrapperProto)
         return true;
 
     // Try the prototype.
     MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
     return js::GetGeneric(cx, wrapperProto, receiver, id, vp);
 }
 
+// This mechanism isn't ideal because we end up calling enter() on the base class
+// twice (once during enter() here and once during the trap itself), and policy
+// enforcement or COWs isn't cheap. But it results in the cleanest code, and this
+// whole proto remapping thing for COWs is going to be phased out anyway.
+bool
+ChromeObjectWrapper::enter(JSContext *cx, JSObject *wrapper, jsid id,
+                           js::Wrapper::Action act, bool *bp)
+{
+    if (AllowedByBase(cx, wrapper, id, act))
+        return true;
+    // COWs fail silently for GETs, and that also happens to be the only case
+    // where we might want to redirect the lookup to the home prototype chain.
+    *bp = (act == Wrapper::GET);
+    return *bp && PropIsFromStandardPrototype(cx, wrapper, id);
 }
+
+}
--- a/js/xpconnect/wrappers/ChromeObjectWrapper.h
+++ b/js/xpconnect/wrappers/ChromeObjectWrapper.h
@@ -32,16 +32,19 @@ class ChromeObjectWrapper : public Chrom
     virtual bool getPropertyDescriptor(JSContext *cx, JSObject *wrapper,
                                        jsid id, js::PropertyDescriptor *desc,
                                        unsigned flags) MOZ_OVERRIDE;
     virtual bool has(JSContext *cx, JSObject *wrapper, jsid id,
                      bool *bp) MOZ_OVERRIDE;
     virtual bool get(JSContext *cx, JSObject *wrapper, JSObject *receiver,
                      jsid id, js::Value *vp) MOZ_OVERRIDE;
 
+    virtual bool enter(JSContext *cx, JSObject *wrapper, jsid id,
+                       js::Wrapper::Action act, bool *bp) MOZ_OVERRIDE;
+
     // NB: One might think we'd need to implement enumerate(), keys(), iterate(),
     // and getPropertyNames() here. However, ES5 built-in properties aren't
     // enumerable (and SpiderMonkey's implementation seems to match the spec
     // modulo Error.prototype.fileName and Error.prototype.lineNumber). Since
     // we're only remapping the prototypes of standard objects, there would
     // never be anything more to enumerate up the prototype chain. So we can
     // atually skip these.
 
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -978,22 +978,18 @@ XPCWrappedNativeXrayTraits::resolveOwnPr
     // in the wrapper's compartment here, not the wrappee.
     MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx));
     XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
     if (AccessCheck::isChrome(wrapper) &&
         (((id == rt->GetStringID(XPCJSRuntime::IDX_BASEURIOBJECT) ||
            id == rt->GetStringID(XPCJSRuntime::IDX_NODEPRINCIPAL)) &&
           Is<nsINode>(wrapper)) ||
           (id == rt->GetStringID(XPCJSRuntime::IDX_DOCUMENTURIOBJECT) &&
-          Is<nsIDocument>(wrapper)))) {
-        bool status;
-        desc->obj = NULL; // default value
-        if (!jsWrapper.enter(cx, wrapper, id, Wrapper::GET, &status))
-            return status;
-
+          Is<nsIDocument>(wrapper))))
+    {
         desc->obj = wrapper;
         desc->attrs = JSPROP_ENUMERATE|JSPROP_SHARED;
         if (id == rt->GetStringID(XPCJSRuntime::IDX_BASEURIOBJECT))
             desc->getter = baseURIObject_getter;
         else if (id == rt->GetStringID(XPCJSRuntime::IDX_DOCUMENTURIOBJECT))
             desc->getter = documentURIObject_getter;
         else
             desc->getter = nodePrincipal_getter;
@@ -1415,21 +1411,16 @@ XrayWrapper<Base, Traits>::getPropertyDe
                                                  js::PropertyDescriptor *desc, unsigned flags)
 {
     JSObject *holder = Traits::singleton.ensureHolder(cx, wrapper);
     if (Traits::isResolving(cx, holder, id)) {
         desc->obj = NULL;
         return true;
     }
 
-    bool status;
-    desc->obj = NULL; // default value
-    if (!this->enter(cx, wrapper, id, Wrapper::GET, &status))
-        return status;
-
     typename Traits::ResolvingIdImpl resolving(wrapper, id);
 
     // Redirect access straight to the wrapper if we should be transparent.
     if (XrayUtils::IsTransparent(cx, wrapper, id)) {
         JSObject *obj = Traits::getTargetObject(wrapper);
         {
             JSAutoCompartment ac(cx, obj);
             if (!JS_GetPropertyDescriptorById(cx, obj, id, flags, desc))
@@ -1445,21 +1436,16 @@ XrayWrapper<Base, Traits>::getPropertyDe
         return false;
 
     // Only chrome wrappers and same-origin xrays (used by jetpack sandboxes)
     // get .wrappedJSObject. We can check this by determining if the compartment
     // of the wrapper subsumes that of the wrappee.
     XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
     if (AccessCheck::wrapperSubsumes(wrapper) &&
         id == rt->GetStringID(XPCJSRuntime::IDX_WRAPPED_JSOBJECT)) {
-        bool status;
-        desc->obj = NULL; // default value
-        if (!this->enter(cx, wrapper, id, Wrapper::GET, &status))
-            return status;
-
         desc->obj = wrapper;
         desc->attrs = JSPROP_ENUMERATE|JSPROP_SHARED;
         desc->getter = wrappedJSObject_getter;
         desc->setter = NULL;
         desc->shortid = 0;
         desc->value = JSVAL_VOID;
         return true;
     }
@@ -1543,21 +1529,16 @@ XrayWrapper<Base, Traits>::getOwnPropert
                                                     PropertyDescriptor *desc, unsigned flags)
 {
     JSObject *holder = Traits::singleton.ensureHolder(cx, wrapper);
     if (Traits::isResolving(cx, holder, id)) {
         desc->obj = NULL;
         return true;
     }
 
-    bool status;
-    desc->obj = NULL; // default value
-    if (!this->enter(cx, wrapper, id, Wrapper::GET, &status))
-        return status;
-
     typename Traits::ResolvingIdImpl resolving(wrapper, 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.
     if (XrayUtils::IsTransparent(cx, wrapper, id)) {
         JSObject *obj = Traits::getTargetObject(wrapper);
         {