Bug 888969 - Make the getPrototypeOf/setPrototypeOf traps scriptable. r=efaust, r=bholley
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
285
js/src/tests/ecma_6/Proxy/getPrototypeOf.js
Normal file
285
js/src/tests/ecma_6/Proxy/getPrototypeOf.js
Normal 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");
|
||||
258
js/src/tests/ecma_6/Proxy/setPrototypeOf.js
Normal file
258
js/src/tests/ecma_6/Proxy/setPrototypeOf.js
Normal 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");
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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") \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user