Bug 888969 - Make the getPrototypeOf/setPrototypeOf traps scriptable. r=efaust, r=bholley

This commit is contained in:
Jeff Walden
2016-02-23 13:42:30 -08:00
parent c3fa6738d4
commit 51488fefe9
37 changed files with 1006 additions and 39 deletions

View File

@@ -36,6 +36,11 @@ public:
virtual bool
delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
JS::ObjectOpResult &aResult) const override;
// No need for getPrototypeIfOrdinary here: this object shouldn't have a
// lazy prototype, so this trap would never be called (and the inherited
// version, from BaseProxyHandler, just crashes).
virtual bool
preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
JS::ObjectOpResult& aResult) const override

View File

@@ -682,6 +682,12 @@ public:
virtual bool delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id,
JS::ObjectOpResult &result) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx,
JS::Handle<JSObject*> proxy,
bool* isOrdinary,
JS::MutableHandle<JSObject*> protop) const override;
virtual bool enumerate(JSContext *cx, JS::Handle<JSObject*> proxy,
JS::MutableHandle<JSObject*> vp) const override;
virtual bool preventExtensions(JSContext* cx,
@@ -908,6 +914,27 @@ nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle<JSObject*> proxy,
return js::Wrapper::delete_(cx, proxy, id, result);
}
bool
nsOuterWindowProxy::getPrototypeIfOrdinary(JSContext* cx,
JS::Handle<JSObject*> proxy,
bool* isOrdinary,
JS::MutableHandle<JSObject*> protop) const
{
// Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
//
// https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
//
// We nonetheless can implement it here using a non-"lazy" [[Prototype]],
// because wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp)
// supply all the non-ordinary behavior.
//
// But from a spec point of view, it's the exact same object in both cases --
// only the observer's changed. So both cases *must* report non-ordinary,
// even if non-"lazy" [[Prototype]] usually means ordinary.
*isOrdinary = false;
return true;
}
bool
nsOuterWindowProxy::preventExtensions(JSContext* cx,
JS::Handle<JSObject*> proxy,

View File

@@ -99,6 +99,11 @@ class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base
bool RecvGetPrototype(const uint64_t& objId, ReturnStatus* rs, ObjectOrNullVariant* result) {
return Answer::RecvGetPrototype(ObjectId::deserialize(objId), rs, result);
}
bool RecvGetPrototypeIfOrdinary(const uint64_t& objId, ReturnStatus* rs, bool* isOrdinary,
ObjectOrNullVariant* result)
{
return Answer::RecvGetPrototypeIfOrdinary(ObjectId::deserialize(objId), rs, isOrdinary, result);
}
bool RecvRegExpToShared(const uint64_t& objId, ReturnStatus* rs, nsString* source, uint32_t* flags) {
return Answer::RecvRegExpToShared(ObjectId::deserialize(objId), rs, source, flags);
}
@@ -190,6 +195,11 @@ class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base
bool SendGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result) {
return Base::SendGetPrototype(objId.serialize(), rs, result);
}
bool SendGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
ObjectOrNullVariant* result)
{
return Base::SendGetPrototypeIfOrdinary(objId.serialize(), rs, isOrdinary, result);
}
bool SendRegExpToShared(const ObjectId& objId, ReturnStatus* rs,
nsString* source, uint32_t* flags) {

View File

@@ -42,6 +42,7 @@ both:
prio(high) sync IsArray(uint64_t objId) returns (ReturnStatus rs, uint32_t ans);
prio(high) sync ClassName(uint64_t objId) returns (nsCString name);
prio(high) sync GetPrototype(uint64_t objId) returns (ReturnStatus rs, ObjectOrNullVariant result);
prio(high) sync GetPrototypeIfOrdinary(uint64_t objId) returns (ReturnStatus rs, bool isOrdinary, ObjectOrNullVariant result);
prio(high) sync RegExpToShared(uint64_t objId) returns (ReturnStatus rs, nsString source, uint32_t flags);
prio(high) sync GetPropertyKeys(uint64_t objId, uint32_t flags) returns (ReturnStatus rs, JSIDVariant[] ids);

View File

@@ -585,6 +585,34 @@ WrapperAnswer::RecvGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectO
return ok(rs);
}
bool
WrapperAnswer::RecvGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
ObjectOrNullVariant* result)
{
*result = NullVariant();
*isOrdinary = false;
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
return false;
JSContext* cx = jsapi.cx();
RootedObject obj(cx, findObjectById(cx, objId));
if (!obj)
return fail(jsapi, rs);
JS::RootedObject proto(cx);
if (!JS_GetPrototypeIfOrdinary(cx, obj, isOrdinary, &proto))
return fail(jsapi, rs);
if (!toObjectOrNullVariant(cx, proto, result))
return fail(jsapi, rs);
LOG("getPrototypeIfOrdinary(%s)", ReceiverObj(objId));
return ok(rs);
}
bool
WrapperAnswer::RecvRegExpToShared(const ObjectId& objId, ReturnStatus* rs,
nsString* source, uint32_t* flags)

View File

@@ -56,6 +56,8 @@ class WrapperAnswer : public virtual JavaScriptShared
bool RecvIsArray(const ObjectId& objId, ReturnStatus* rs, uint32_t* ans);
bool RecvClassName(const ObjectId& objId, nsCString* result);
bool RecvGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result);
bool RecvGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
ObjectOrNullVariant* result);
bool RecvRegExpToShared(const ObjectId& objId, ReturnStatus* rs, nsString* source, uint32_t* flags);
bool RecvGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,

View File

@@ -136,6 +136,8 @@ class CPOWProxyHandler : public BaseProxyHandler
virtual bool isCallable(JSObject* obj) const override;
virtual bool isConstructor(JSObject* obj) const override;
virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
static const char family;
static const CPOWProxyHandler singleton;
@@ -827,6 +829,34 @@ WrapperOwner::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObjec
return true;
}
bool
CPOWProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject objp) const
{
FORWARD(getPrototypeIfOrdinary, (cx, proxy, isOrdinary, objp));
}
bool
WrapperOwner::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject objp)
{
ObjectId objId = idOf(proxy);
ObjectOrNullVariant val;
ReturnStatus status;
if (!SendGetPrototypeIfOrdinary(objId, &status, isOrdinary, &val))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
objp.set(fromObjectOrNullVariant(cx, val));
return true;
}
bool
CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const
{
@@ -1146,7 +1176,8 @@ WrapperOwner::fromRemoteObjectVariant(JSContext* cx, RemoteObject objVar)
RootedObject junkScope(cx, xpc::PrivilegedJunkScope());
JSAutoCompartment ac(cx, junkScope);
RootedValue v(cx, UndefinedValue());
// We need to setLazyProto for the getPrototype hook.
// We need to setLazyProto for the getPrototype/getPrototypeIfOrdinary
// hooks.
ProxyOptions options;
options.setLazyProto(true);
obj = NewProxyObject(cx,

View File

@@ -58,6 +58,8 @@ class WrapperOwner : public virtual JavaScriptShared
bool isArray(JSContext* cx, JS::HandleObject proxy, JS::IsArrayAnswer* answer);
const char* className(JSContext* cx, JS::HandleObject proxy);
bool getPrototype(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleObject protop);
bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy, bool* isOrdinary,
JS::MutableHandleObject protop);
bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, js::RegExpGuard* g);
@@ -147,6 +149,8 @@ class WrapperOwner : public virtual JavaScriptShared
uint32_t* answer) = 0;
virtual bool SendClassName(const ObjectId& objId, nsCString* result) = 0;
virtual bool SendGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result) = 0;
virtual bool SendGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
ObjectOrNullVariant* result) = 0;
virtual bool SendRegExpToShared(const ObjectId& objId, ReturnStatus* rs, nsString* source,
uint32_t* flags) = 0;

View File

@@ -270,7 +270,9 @@ class JS_FRIEND_API(BaseProxyHandler)
virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const;
/* Non-standard but conceptual kin to {g,s}etPrototype, so lives here. */
/* Non-standard but conceptual kin to {g,s}etPrototype, so these live here. */
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const;
virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const;
virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
@@ -381,6 +383,8 @@ class JS_FRIEND_API(DirectProxyHandler) : public BaseProxyHandler
MutableHandleObject protop) const override;
virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
bool* succeeded) const override;
virtual bool preventExtensions(JSContext* cx, HandleObject proxy,

View File

@@ -356,6 +356,15 @@ ModuleNamespaceObject::ProxyHandler::setPrototype(JSContext* cx, HandleObject pr
return result.failCantSetProto();
}
bool
ModuleNamespaceObject::ProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
bool* isOrdinary,
MutableHandleObject protop) const
{
*isOrdinary = false;
return true;
}
bool
ModuleNamespaceObject::ProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy,
bool* succeeded) const

View File

@@ -165,6 +165,8 @@ class ModuleNamespaceObject : public ProxyObject
MutableHandleObject protop) const override;
bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const override;
bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
bool* succeeded) const override;

View File

@@ -353,6 +353,11 @@ MSG_DEF(JSMSG_WASM_UNREACHABLE, 0, JSEXN_ERR, "reached unreachable tr
// Proxy
MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
MSG_DEF(JSMSG_CANT_CHANGE_EXTENSIBILITY, 0, JSEXN_TYPEERR, "can't change object's extensibility")
MSG_DEF(JSMSG_CANT_DEFINE_INVALID, 0, JSEXN_TYPEERR, "proxy can't define an incompatible property descriptor")
MSG_DEF(JSMSG_CANT_DEFINE_NEW, 0, JSEXN_TYPEERR, "proxy can't define a new property on a non-extensible object")

View File

@@ -2014,6 +2014,13 @@ JS_SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto)
return SetPrototype(cx, obj, proto);
}
JS_PUBLIC_API(bool)
JS_GetPrototypeIfOrdinary(JSContext* cx, HandleObject obj, bool* isOrdinary,
MutableHandleObject result)
{
return GetPrototypeIfOrdinary(cx, obj, isOrdinary, result);
}
JS_PUBLIC_API(bool)
JS_IsExtensible(JSContext* cx, HandleObject obj, bool* extensible)
{

View File

@@ -2874,6 +2874,17 @@ FromPropertyDescriptor(JSContext* cx,
extern JS_PUBLIC_API(bool)
JS_GetPrototype(JSContext* cx, JS::HandleObject obj, JS::MutableHandleObject result);
/**
* If |obj| (underneath any functionally-transparent wrapper proxies) has as
* its [[GetPrototypeOf]] trap the ordinary [[GetPrototypeOf]] behavior defined
* for ordinary objects, set |*isOrdinary = true| and store |obj|'s prototype
* in |result|. Otherwise set |*isOrdinary = false|. In case of error, both
* outparams have unspecified value.
*/
extern JS_PUBLIC_API(bool)
JS_GetPrototypeIfOrdinary(JSContext* cx, JS::HandleObject obj, bool* isOrdinary,
JS::MutableHandleObject result);
/**
* Change the prototype of obj.
*

View File

@@ -2494,6 +2494,20 @@ JSObject::reportNotExtensible(JSContext* cx, unsigned report)
nullptr, nullptr);
}
bool
js::GetPrototypeIfOrdinary(JSContext* cx, HandleObject obj, bool* isOrdinary,
MutableHandleObject protop)
{
if (obj->getTaggedProto().isLazy()) {
MOZ_ASSERT(obj->is<js::ProxyObject>());
return js::Proxy::getPrototypeIfOrdinary(cx, obj, isOrdinary, protop);
}
*isOrdinary = true;
protop.set(obj->getTaggedProto().toObjectOrNull());
return true;
}
// Our immutable-prototype behavior is non-standard, and it's unclear whether
// it's shippable. (Or at least it's unclear whether it's shippable with any
// provided-by-default uses exposed to script.) If this bool is true,
@@ -2587,14 +2601,17 @@ js::SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto, JS::Object
* possibly-Window object we're setting the proto on.
*/
RootedObject objMaybeWindowProxy(cx, ToWindowProxyIfWindow(obj));
RootedObject obj2(cx);
for (obj2 = proto; obj2; ) {
RootedObject obj2(cx, proto);
while (obj2) {
MOZ_ASSERT(!IsWindow(obj2));
if (obj2 == objMaybeWindowProxy)
return result.fail(JSMSG_CANT_SET_PROTO_CYCLE);
if (!GetPrototype(cx, obj2, &obj2))
bool isOrdinary;
if (!GetPrototypeIfOrdinary(cx, obj2, &isOrdinary, &obj2))
return false;
if (!isOrdinary)
break;
}
// Convert unboxed objects to their native representations before changing

View File

@@ -949,6 +949,17 @@ DeleteElement(JSContext* cx, HandleObject obj, uint32_t index, ObjectOpResult& r
/*** SpiderMonkey nonstandard internal methods ***************************************************/
/**
* If |obj| (underneath any functionally-transparent wrapper proxies) has as
* its [[GetPrototypeOf]] trap the ordinary [[GetPrototypeOf]] behavior defined
* for ordinary objects, set |*isOrdinary = true| and store |obj|'s prototype
* in |result|. Otherwise set |*isOrdinary = false|. In case of error, both
* outparams have unspecified value.
*/
extern bool
GetPrototypeIfOrdinary(JSContext* cx, HandleObject obj, bool* isOrdinary,
MutableHandleObject protop);
/*
* Attempt to make |obj|'s [[Prototype]] immutable, such that subsequently
* trying to change it will not work. If an internal error occurred,

View File

@@ -124,6 +124,8 @@ class JS_FRIEND_API(CrossCompartmentWrapper) : public Wrapper
virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
bool* succeeded) const override;
virtual bool preventExtensions(JSContext* cx, HandleObject wrapper,
@@ -179,6 +181,8 @@ class JS_FRIEND_API(OpaqueCrossCompartmentWrapper) : public CrossCompartmentWrap
MutableHandleObject protop) const override;
virtual bool setPrototype(JSContext* cx, HandleObject wrapper, HandleObject proto,
ObjectOpResult& result) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper, bool* isOrdinary,
MutableHandleObject protop) const override;
virtual bool setImmutablePrototype(JSContext* cx, HandleObject wrapper,
bool* succeeded) const override;
virtual bool preventExtensions(JSContext* cx, HandleObject wrapper,

View File

@@ -396,7 +396,7 @@ BaseProxyHandler::weakmapKeyDelegate(JSObject* proxy) const
bool
BaseProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const
{
MOZ_CRASH("Must override getPrototype with lazy prototype.");
MOZ_CRASH("must override getPrototype with lazy prototype");
}
bool
@@ -411,6 +411,13 @@ BaseProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject p
return false;
}
bool
BaseProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const
{
MOZ_CRASH("must override getPrototypeIfOrdinary with lazy prototype");
}
bool
BaseProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const
{

View File

@@ -108,6 +108,24 @@ CrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject wrapper,
NOTHING);
}
bool
CrossCompartmentWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper,
bool* isOrdinary, MutableHandleObject protop) const
{
{
RootedObject wrapped(cx, wrappedObject(wrapper));
AutoCompartment call(cx, wrapped);
if (!GetPrototypeIfOrdinary(cx, wrapped, isOrdinary, protop))
return false;
if (protop) {
if (!protop->setDelegate(cx))
return false;
}
}
return cx->compartment()->wrap(cx, protop);
}
bool
CrossCompartmentWrapper::setImmutablePrototype(JSContext* cx, HandleObject wrapper, bool* succeeded) const
{

View File

@@ -60,6 +60,14 @@ DeadObjectProxy::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleOb
return true;
}
bool
DeadObjectProxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const
{
*isOrdinary = false;
return true;
}
bool
DeadObjectProxy::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
{

View File

@@ -30,6 +30,8 @@ class DeadObjectProxy : public BaseProxyHandler
ObjectOpResult& result) const override;
virtual bool getPrototype(JSContext* cx, HandleObject proxy,
MutableHandleObject protop) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
ObjectOpResult& result) const override;
virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;

View File

@@ -142,6 +142,14 @@ DirectProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject
return SetPrototype(cx, target, proto, result);
}
bool
DirectProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
bool* isOrdinary, MutableHandleObject protop) const
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
return GetPrototypeIfOrdinary(cx, target, isOrdinary, protop);
}
bool
DirectProxyHandler::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const
{

View File

@@ -64,6 +64,15 @@ OpaqueCrossCompartmentWrapper::setPrototype(JSContext* cx, HandleObject proxy, H
return result.succeed();
}
bool
OpaqueCrossCompartmentWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
bool* isOrdinary,
MutableHandleObject protop) const
{
*isOrdinary = false;
return true;
}
bool
OpaqueCrossCompartmentWrapper::setImmutablePrototype(JSContext* cx, HandleObject proxy,
bool* succeeded) const

View File

@@ -196,6 +196,16 @@ Proxy::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto, Objec
return proxy->as<ProxyObject>().handler()->setPrototype(cx, proxy, proto, result);
}
/* static */ bool
Proxy::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject proto)
{
MOZ_ASSERT(proxy->hasLazyPrototype());
JS_CHECK_RECURSION(cx, return false);
return proxy->as<ProxyObject>().handler()->getPrototypeIfOrdinary(cx, proxy, isOrdinary,
proto);
}
/* static */ bool
Proxy::setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded)
{

View File

@@ -38,6 +38,8 @@ class Proxy
static bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop);
static bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result);
static bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop);
static bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded);
static bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp);
static bool get(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id,

View File

@@ -165,33 +165,152 @@ GetProxyTrap(JSContext* cx, HandleObject handler, HandlePropertyName name, Mutab
return true;
}
// ES6 implements both getPrototype and setPrototype traps. We don't have them yet (see bug
// 888969). For now, use these, to account for proxy revocation.
// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.1 Proxy.[[GetPrototypeOf]].
bool
ScriptedDirectProxyHandler::getPrototype(JSContext* cx, HandleObject proxy,
MutableHandleObject protop) const
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
// Though handler is used elsewhere, spec mandates that both get set to null.
if (!target) {
// Steps 1-3.
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
if (!handler) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
return false;
}
return GetPrototype(cx, target, protop);
// Step 4.
RootedObject target(cx, proxy->as<ProxyObject>().target());
MOZ_ASSERT(target);
// Step 5.
RootedValue trap(cx);
if (!GetProxyTrap(cx, handler, cx->names().getPrototypeOf, &trap))
return false;
// Step 6.
if (trap.isUndefined())
return GetPrototype(cx, target, protop);
// Step 7.
RootedValue handlerProto(cx);
{
FixedInvokeArgs<1> args(cx);
args[0].setObject(*target);
handlerProto.setObject(*handler);
if (!js::Call(cx, trap, handlerProto, args, &handlerProto))
return false;
}
// Step 8.
if (!handlerProto.isObjectOrNull()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN);
return false;
}
// Step 9.
bool extensibleTarget;
if (!IsExtensible(cx, target, &extensibleTarget))
return false;
// Step 10.
if (extensibleTarget) {
protop.set(handlerProto.toObjectOrNull());
return true;
}
// Step 11.
RootedObject targetProto(cx);
if (!GetPrototype(cx, target, &targetProto))
return false;
// Step 12.
if (handlerProto.toObjectOrNull() != targetProto) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP);
return false;
}
// Step 13.
protop.set(handlerProto.toObjectOrNull());
return true;
}
// ES8 rev 0c1bd3004329336774cbc90de727cd0cf5f11e93 9.5.2 Proxy.[[SetPrototypeOf]].
bool
ScriptedDirectProxyHandler::setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const
{
RootedObject target(cx, proxy->as<ProxyObject>().target());
if (!target) {
// Steps 1-4.
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
if (!handler) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED);
return false;
}
return SetPrototype(cx, target, proto, result);
// Step 5.
RootedObject target(cx, proxy->as<ProxyObject>().target());
MOZ_ASSERT(target);
// Step 6.
RootedValue trap(cx);
if (!GetProxyTrap(cx, handler, cx->names().setPrototypeOf, &trap))
return false;
// Step 7.
if (trap.isUndefined())
return SetPrototype(cx, target, proto, result);
// Step 8.
bool booleanTrapResult;
{
FixedInvokeArgs<2> args(cx);
args[0].setObject(*target);
args[1].setObjectOrNull(proto);
RootedValue hval(cx, ObjectValue(*handler));
if (!js::Call(cx, trap, hval, args, &hval))
return false;
booleanTrapResult = ToBoolean(hval);
}
// Step 9.
if (!booleanTrapResult)
return result.fail(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE);
// Step 10.
bool extensibleTarget;
if (!IsExtensible(cx, target, &extensibleTarget))
return false;
// Step 11.
if (extensibleTarget)
return result.succeed();
// Step 12.
RootedObject targetProto(cx);
if (!GetPrototype(cx, target, &targetProto))
return false;
// Step 13.
if (proto != targetProto) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP);
return false;
}
// Step 14.
return result.succeed();
}
bool
ScriptedDirectProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy,
bool* isOrdinary,
MutableHandleObject protop) const
{
*isOrdinary = false;
return true;
}
// Not yet part of ES6, but hopefully to be standards-tracked -- and needed to

View File

@@ -34,6 +34,9 @@ class ScriptedDirectProxyHandler : public BaseProxyHandler {
MutableHandleObject protop) const override;
virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
ObjectOpResult& result) const override;
/* Non-standard, but needed to implement OrdinaryGetPrototypeOf. */
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
/* Non-standard, but needed to handle revoked proxies. */
virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
bool* succeeded) const override;

View File

@@ -0,0 +1,285 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var gTestfile = "getPrototypeOf.js";
var BUGNUMBER = 888969;
var summary = "Scripted proxies' [[GetPrototypeOf]] behavior";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
const log = [];
function observe(obj)
{
var observingHandler = new Proxy({}, {
get(target, p, receiver) {
log.push(p);
return Reflect.get(target, p, receiver);
}
});
return new Proxy(obj, observingHandler);
}
function nop() {}
var p, h;
// 1. Let handler be the value of the [[ProxyHandler]] internal slot of O.
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
var rev = Proxy.revocable({}, {});
p = rev.proxy;
assertEq(Object.getPrototypeOf(p), Object.prototype);
rev.revoke();
assertThrowsInstanceOf(() => Object.getPrototypeOf(p), TypeError);
// 4. Let target be the value of the [[ProxyTarget]] internal slot of O.
// 5. Let trap be ? GetMethod(handler, "getPrototypeOf").
// Getting handler.getPrototypeOf might throw.
assertThrowsValue(() => Object.getPrototypeOf(new Proxy({},
{ get getPrototypeOf() {
throw 17;
} })),
17);
// The handler's getPrototypeOf, once gotten, might throw.
p = new Proxy({}, { getPrototypeOf() { throw 42; } });
assertThrowsValue(() => Object.getPrototypeOf(p), 42);
// The trap might not be callable.
p = new Proxy({}, { getPrototypeOf: 17 });
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
// 6. If trap is undefined, then
// a. Return ? target.[[GetPrototypeOf]]().
var x, tp;
tp =
new Proxy(new Number(8675309), // behavior overridden by getPrototypeOf
{ getPrototypeOf() { x = "getPrototypeOf trap"; return null; } });
// The target's [[GetPrototypeOf]] should be invoked if the handler's
// .getPrototypeOf is undefined.
p = new Proxy(tp, { getPrototypeOf: undefined });
x = "unset";
assertEq(Object.getPrototypeOf(p), null);
assertEq(x, "getPrototypeOf trap");
// Likewise if the handler's .getPrototypeOf is null.
p = new Proxy(tp, { getPrototypeOf: null });
x = "unset";
assertEq(Object.getPrototypeOf(p), null);
assertEq(x, "getPrototypeOf trap");
// Now the target is an empty object with a Number object as its [[Prototype]].
var customProto = new Number(8675309);
tp =
new Proxy({},
{ getPrototypeOf() {
x = "getPrototypeOf trap";
return customProto;
} });
// The target's [[GetPrototypeOf]] should be invoked if the handler's
// .getPrototypeOf is undefined.
p = new Proxy(tp, { getPrototypeOf: undefined });
x = "unset";
assertEq(Object.getPrototypeOf(p), customProto);
assertEq(x, "getPrototypeOf trap");
// Likewise if the handler's .getPrototypeOf is null.
p = new Proxy(tp, { getPrototypeOf: null });
x = "unset";
assertEq(Object.getPrototypeOf(p), customProto);
assertEq(x, "getPrototypeOf trap");
// 7. Let handlerProto be ? Call(trap, handler, « target »).
// The trap callable might throw.
p = new Proxy({}, { getPrototypeOf() { throw "ohai"; } });
assertThrowsValue(() => Object.getPrototypeOf(p),
"ohai");
var throwingTrap =
new Proxy(function() { throw "not called"; },
{ apply() { throw 37; } });
p = new Proxy({}, { getPrototypeOf: throwingTrap });
assertThrowsValue(() => Object.getPrototypeOf(p),
37);
// The trap callable must *only* be called.
p = new Proxy({},
{
getPrototypeOf: observe(function() { throw "boo-urns"; })
});
log.length = 0;
assertThrowsValue(() => Object.getPrototypeOf(p),
"boo-urns");
assertEq(log.length, 1);
assertEq(log[0], "apply");
// 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception.
var rval;
var typeTestingTarget = {};
p = new Proxy(typeTestingTarget, { getPrototypeOf() { return rval; } });
function returnsPrimitives()
{
rval = undefined;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = true;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = false;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = 0.0;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = -0.0;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = 3.141592654;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = NaN;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = -Infinity;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = "[[Prototype]] FOR REALZ";
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
rval = Symbol("[[Prototype]] FOR REALZ");
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
}
returnsPrimitives();
Object.preventExtensions(typeTestingTarget);
returnsPrimitives();
// 9. Let extensibleTarget be ? IsExtensible(target).
var act, extens;
var typeTestingProxyTarget =
new Proxy({}, { isExtensible() {
seen = act();
return extens;
} });
p = new Proxy(typeTestingProxyTarget, { getPrototypeOf() { return rval; } });
rval = null;
act = () => { throw "fnord" };
assertThrowsValue(() => Object.getPrototypeOf(p),
"fnord");
rval = /abc/;
act = () => { throw "fnord again" };
assertThrowsValue(() => Object.getPrototypeOf(p),
"fnord again");
rval = Object.prototype;
act = () => { throw "fnord" };
assertThrowsValue(() => Object.getPrototypeOf(p),
"fnord");
// 10. If extensibleTarget is true, return handlerProto.
p = new Proxy({}, { getPrototypeOf() { return rval; } });
rval = Number.prototype;
assertEq(Object.getPrototypeOf(p), Number.prototype);
// 11. Let targetProto be ? target.[[GetPrototypeOf]]().
var targetProto;
var targetWithProto =
new Proxy(Object.preventExtensions(Object.create(null)),
{ getPrototypeOf() { act2(); return targetProto; } });
p = new Proxy(targetWithProto,
{ getPrototypeOf() { act1(); return rval; } });
rval = null;
targetProto = null;
var regex = /targetProto/;
act1 = () => log.push("act1");
act2 = () => log.push("act2");
log.length = 0;
assertEq(Object.getPrototypeOf(p), null);
assertEq(log.length, 2);
assertEq(log[0], "act1");
assertEq(log[1], "act2");
act1 = () => log.push("act1 again");
act2 = () => { throw "target throw"; };
log.length = 0;
assertThrowsValue(() => Object.getPrototypeOf(p),
"target throw");
assertEq(log.length, 1);
assertEq(log[0], "act1 again");
// 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception.
act1 = act2 = nop;
rval = /a/;
assertThrowsInstanceOf(() => Object.getPrototypeOf(p),
TypeError);
// 13. Return handlerProto.
rval = null;
targetProto = null;
assertEq(Object.getPrototypeOf(p), null);
p = new Proxy(Object.preventExtensions(new Number(55)),
{ getPrototypeOf() { return Number.prototype; } });
assertEq(Object.getPrototypeOf(p), Number.prototype);
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete");

View File

@@ -0,0 +1,258 @@
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/
*/
var gTestfile = "setPrototypeOf.js";
var BUGNUMBER = 888969;
var summary = "Scripted proxies' [[SetPrototypeOf]] behavior";
print(BUGNUMBER + ": " + summary);
/**************
* BEGIN TEST *
**************/
const log = [];
function observe(obj)
{
var observingHandler = new Proxy({}, {
get(target, p, receiver) {
log.push(p);
return Reflect.get(target, p, receiver);
}
});
return new Proxy(obj, observingHandler);
}
var p, h;
// 1. Assert: Either Type(V) is Object or Type(V) is Null.
// 2. Let handler be the value of the [[ProxyHandler]] internal slot of O.
// 3. If handler is null, throw a TypeError exception.
// 4. Assert: Type(handler) is Object.
// 5. Let target be the value of the [[ProxyTarget]] internal slot of O.
var rev = Proxy.revocable({}, {});
p = rev.proxy;
var originalProto = Reflect.getPrototypeOf(p);
assertEq(originalProto, Object.prototype);
rev.revoke();
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, originalProto),
TypeError);
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
// 6. Let trap be ? GetMethod(handler, "setPrototypeOf").
// handler has uncallable (and not null/undefined) property
p = new Proxy({}, { setPrototypeOf: 9 });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: -3.7 });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: NaN });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: Infinity });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: true });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: /x/ });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: Symbol(42) });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, { setPrototypeOf: class X {} });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
p = new Proxy({}, observe({}));
assertEq(Reflect.setPrototypeOf(p, Object.prototype), true);
assertEq(log.length, 1);
assertEq(log[0], "get");
h = observe({ setPrototypeOf() { throw 3.14; } });
p = new Proxy(Object.create(Object.prototype), h);
// "setting" without change
log.length = 0;
assertThrowsValue(() => Reflect.setPrototypeOf(p, Object.prototype),
3.14);
assertEq(log.length, 1);
assertEq(log[0], "get");
// "setting" with change
log.length = 0;
assertThrowsValue(() => Reflect.setPrototypeOf(p, /foo/),
3.14);
assertEq(log.length, 1);
assertEq(log[0], "get");
// 7. If trap is undefined, then
// a. Return ? target.[[SetPrototypeOf]](V).
var settingProtoThrows =
new Proxy({}, { setPrototypeOf() { throw "agnizes"; } });
p = new Proxy(settingProtoThrows, { setPrototypeOf: undefined });
assertThrowsValue(() => Reflect.setPrototypeOf(p, null),
"agnizes");
p = new Proxy(settingProtoThrows, { setPrototypeOf: null });
assertThrowsValue(() => Reflect.setPrototypeOf(p, null),
"agnizes");
var anotherProto =
new Proxy({},
{ setPrototypeOf(t, p) {
log.push("reached");
return Reflect.setPrototypeOf(t, p);
} });
p = new Proxy(anotherProto, { setPrototypeOf: undefined });
log.length = 0;
assertEq(Reflect.setPrototypeOf(p, null), true);
assertEq(log.length, 1);
assertEq(log[0], "reached");
p = new Proxy(anotherProto, { setPrototypeOf: null });
log.length = 0;
assertEq(Reflect.setPrototypeOf(p, null), true);
assertEq(log.length, 1);
assertEq(log[0], "reached");
// 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler, « target, V »)).
// The trap callable might throw.
p = new Proxy({}, { setPrototypeOf() { throw "ohai"; } });
assertThrowsValue(() => Reflect.setPrototypeOf(p, /x/),
"ohai");
var throwingTrap =
new Proxy(function() { throw "not called"; },
{ apply() { throw 37; } });
p = new Proxy({}, { setPrototypeOf: throwingTrap });
assertThrowsValue(() => Reflect.setPrototypeOf(p, Object.prototype),
37);
// The trap callable must *only* be called.
p = new Proxy({},
{
setPrototypeOf: observe(function() { throw "boo-urns"; })
});
log.length = 0;
assertThrowsValue(() => Reflect.setPrototypeOf(p, Object.prototype),
"boo-urns");
assertEq(log.length, 1);
assertEq(log[0], "apply");
// 9. If booleanTrapResult is false, return false.
p = new Proxy({}, { setPrototypeOf() { return false; } });
assertEq(Reflect.setPrototypeOf(p, Object.prototype), false);
p = new Proxy({}, { setPrototypeOf() { return +0; } });
assertEq(Reflect.setPrototypeOf(p, Object.prototype), false);
p = new Proxy({}, { setPrototypeOf() { return -0; } });
assertEq(Reflect.setPrototypeOf(p, Object.prototype), false);
p = new Proxy({}, { setPrototypeOf() { return NaN; } });
assertEq(Reflect.setPrototypeOf(p, Object.prototype), false);
p = new Proxy({}, { setPrototypeOf() { return ""; } });
assertEq(Reflect.setPrototypeOf(p, Object.prototype), false);
p = new Proxy({}, { setPrototypeOf() { return undefined; } });
assertEq(Reflect.setPrototypeOf(p, Object.prototype), false);
// 10. Let extensibleTarget be ? IsExtensible(target).
var targetThrowIsExtensible =
new Proxy({}, { isExtensible() { throw "psych!"; } });
p = new Proxy(targetThrowIsExtensible, { setPrototypeOf() { return true; } });
assertThrowsValue(() => Reflect.setPrototypeOf(p, Object.prototype),
"psych!");
// 11. If extensibleTarget is true, return true.
var targ = {};
p = new Proxy(targ, { setPrototypeOf() { return true; } });
assertEq(Reflect.setPrototypeOf(p, /x/), true);
assertEq(Reflect.getPrototypeOf(targ), Object.prototype);
assertEq(Reflect.getPrototypeOf(p), Object.prototype);
p = new Proxy(targ, { setPrototypeOf() { return /x/; } });
assertEq(Reflect.setPrototypeOf(p, /x/), true);
assertEq(Reflect.getPrototypeOf(targ), Object.prototype);
assertEq(Reflect.getPrototypeOf(p), Object.prototype);
p = new Proxy(targ, { setPrototypeOf() { return Infinity; } });
assertEq(Reflect.setPrototypeOf(p, /x/), true);
assertEq(Reflect.getPrototypeOf(targ), Object.prototype);
assertEq(Reflect.getPrototypeOf(p), Object.prototype);
p = new Proxy(targ, { setPrototypeOf() { return Symbol(true); } });
assertEq(Reflect.setPrototypeOf(p, /x/), true);
assertEq(Reflect.getPrototypeOf(targ), Object.prototype);
assertEq(Reflect.getPrototypeOf(p), Object.prototype);
// 12. Let targetProto be ? target.[[GetPrototypeOf]]().
var targetNotExtensibleGetProtoThrows =
new Proxy(Object.preventExtensions({}),
{ getPrototypeOf() { throw NaN; } });
p = new Proxy(targetNotExtensibleGetProtoThrows,
{ setPrototypeOf() { log.push("goober"); return true; } });
log.length = 0;
assertThrowsValue(() => Reflect.setPrototypeOf(p, /abcd/),
NaN);
// 13. If SameValue(V, targetProto) is false, throw a TypeError exception.
var newProto;
p = new Proxy(Object.preventExtensions(Object.create(Math)),
{ setPrototypeOf(t, p) { return true; } });
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(p, null),
TypeError);
// 14. Return true.
assertEq(Reflect.setPrototypeOf(p, Math), true);
/******************************************************************************/
if (typeof reportCompare === "function")
reportCompare(true, true);
print("Tests complete");

View File

@@ -6,16 +6,11 @@ assertEq(Reflect.getPrototypeOf({}), Object.prototype);
assertEq(Reflect.getPrototypeOf(Object.prototype), null);
assertEq(Reflect.getPrototypeOf(Object.create(null)), null);
// Sleeper test for when scripted proxies support the getPrototypeOf handler
// method (bug 888969).
var proxy = new Proxy({}, {
getPrototypeOf(t) { return Math; }
});
var result = Reflect.getPrototypeOf(proxy);
if (result === Math) {
throw new Error("Congratulations on fixing bug 888969! " +
"Please update this test to cover scripted proxies.");
}
assertEq(Reflect.getPrototypeOf(proxy), Math);
// For more Reflect.getPrototypeOf tests, see target.js.

View File

@@ -22,7 +22,7 @@ for (proto of [undefined, false, 0, 1.6, "that", Symbol.iterator]) {
assertThrowsInstanceOf(() => Reflect.setPrototypeOf(obj, proto), TypeError);
}
// Return false if the target is inextensible.
// Return false if the target isn't extensible.
proto = {};
obj = Object.preventExtensions(Object.create(proto));
assertEq(Reflect.setPrototypeOf(obj, {}), false);
@@ -46,14 +46,10 @@ assertEq(Reflect.setPrototypeOf(proto, obj), false);
// cycle check quietly exits on encountering the proxy.)
obj = {};
var proxy = new Proxy(Object.create(obj), {});
if (Reflect.setPrototypeOf(obj, proxy) !== false) {
throw new Error("Congratulations on implementing ES6 [[SetPrototype]]! " +
"Update this test for 1 karma point!");
// ...by deleting this if-block and uncommenting the three assertions below.
}
// assertEq(Reflect.setPrototypeOf(obj, proxy), true);
// assertEq(Reflect.getPrototypeOf(obj), proxy);
// assertEq(Reflect.getPrototypeOf(proxy), obj);
assertEq(Reflect.setPrototypeOf(obj, proxy), true);
assertEq(Reflect.getPrototypeOf(obj), proxy);
assertEq(Reflect.getPrototypeOf(proxy), obj);
// If a proxy handler returns a false-y value, return false.
var hits = 0;
@@ -67,14 +63,9 @@ proxy = new Proxy(obj, {
return 0;
}
});
if (Reflect.setPrototypeOf(proxy, proto) !== true) {
throw new Error("Congratulations on implementing the setPrototypeOf trap for proxies! " +
"Please update this test.");
// ...by deleting this if-block and uncommenting the two assertions below.
// As of this writing, the setPrototypeOf hook is never called; see bug 888969.
}
// assertEq(Reflect.setPrototypeOf(proxy, proto), false);
// assertEq(hits, 1);
assertEq(Reflect.setPrototypeOf(proxy, proto), false);
assertEq(hits, 1);
// For more Reflect.setPrototypeOf tests, see target.js.

View File

@@ -127,6 +127,7 @@
macro(getOwnPropertyDescriptor, getOwnPropertyDescriptor, "getOwnPropertyDescriptor") \
macro(getOwnPropertyNames, getOwnPropertyNames, "getOwnPropertyNames") \
macro(getPropertyDescriptor, getPropertyDescriptor, "getPropertyDescriptor") \
macro(getPrototypeOf, getPrototypeOf, "getPrototypeOf") \
macro(global, global, "global") \
macro(Handle, Handle, "Handle") \
macro(has, has, "has") \
@@ -238,6 +239,7 @@
macro(separator, separator, "separator") \
macro(set, set, "set") \
macro(setPrefix, setPrefix, "set ") \
macro(setPrototypeOf, setPrototypeOf, "setPrototypeOf") \
macro(shape, shape, "shape") \
macro(size, size, "size") \
macro(source, source, "source") \

View File

@@ -27,10 +27,53 @@ function testInheritedCrossGlobal(SimpleURIClassByID)
do_check_eq(inheritedCross instanceof SimpleURIClassByID, true);
}
function testCrossGlobalArbitraryGetPrototype(SimpleURIClassByID)
{
var simpleURI =
Components.classes["@mozilla.org/network/simple-uri;1"].createInstance();
var sb = new Components.utils.Sandbox(this, { wantComponents: true });
var firstLevel = Object.create(simpleURI);
var obj = { shouldThrow: false };
var secondLevel =
new sb.Proxy(Object.create(firstLevel),
{
getPrototypeOf: new sb.Function("obj", `return function(t) {
if (obj.shouldThrow)
throw 42;
return Reflect.getPrototypeOf(t);
};`)(obj)
});
var thirdLevel = Object.create(secondLevel);
obj.shouldThrow = true;
var threw = false;
var err;
try
{
void (thirdLevel instanceof SimpleURIClassByID);
}
catch (e)
{
threw = true;
err = e;
}
do_check_eq(threw, true);
do_check_eq(err, 42);
obj.shouldThrow = false;
do_check_eq(thirdLevel instanceof SimpleURIClassByID, true);
}
function run_test() {
var SimpleURIClassByID = Components.classesByID["{e0da1d70-2f7b-11d3-8cd0-0060b0fc14a3}"];
testActual(SimpleURIClassByID);
testInherited(SimpleURIClassByID);
testInheritedCrossGlobal(SimpleURIClassByID);
testCrossGlobalArbitraryGetPrototype(SimpleURIClassByID);
}

View File

@@ -94,4 +94,12 @@ WaiveXrayWrapper::getPrototype(JSContext* cx, HandleObject wrapper, MutableHandl
(!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop));
}
bool
WaiveXrayWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper, bool* isOrdinary,
MutableHandleObject protop) const
{
return CrossCompartmentWrapper::getPrototypeIfOrdinary(cx, wrapper, isOrdinary, protop) &&
(!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop));
}
} // namespace xpc

View File

@@ -22,6 +22,9 @@ class WaiveXrayWrapper : public js::CrossCompartmentWrapper {
JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
virtual bool getPrototype(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::MutableHandle<JSObject*> protop) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> wrapper,
bool* isOrdinary,
JS::MutableHandle<JSObject*> protop) const override;
virtual bool get(JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JS::Value> receiver,
JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) const override;
virtual bool call(JSContext* cx, JS::Handle<JSObject*> wrapper,

View File

@@ -2345,6 +2345,22 @@ XrayWrapper<Base, Traits>::setPrototype(JSContext* cx, JS::HandleObject wrapper,
return result.succeed();
}
template <typename Base, typename Traits>
bool
XrayWrapper<Base, Traits>::getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject wrapper,
bool* isOrdinary,
JS::MutableHandleObject protop) const
{
// We want to keep the Xray's prototype distinct from that of content, but
// only if there's been a set. This different-prototype-over-time behavior
// means that the [[GetPrototypeOf]] trap *can't* be ECMAScript's ordinary
// [[GetPrototypeOf]]. This also covers cross-origin Window behavior that
// per <https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof>
// must be non-ordinary.
*isOrdinary = false;
return true;
}
template <typename Base, typename Traits>
bool
XrayWrapper<Base, Traits>::setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper,

View File

@@ -431,6 +431,8 @@ class XrayWrapper : public Base {
JS::MutableHandleObject protop) const override;
virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper,
JS::HandleObject proto, JS::ObjectOpResult& result) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary,
JS::MutableHandleObject protop) const override;
virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper,
bool* succeeded) const override;
virtual bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> wrapper,