зеркало из https://github.com/mozilla/gecko-dev.git
Bug 789897 - Implement the preventExtensions trap for proxies; r=jorendorff
This commit is contained in:
Родитель
dfa8197300
Коммит
5a65bc3488
|
@ -0,0 +1,6 @@
|
|||
// Forward to the target if the trap is not defined
|
||||
target = {};
|
||||
var proxy = new Proxy(target, {});
|
||||
Object.preventExtensions(proxy);
|
||||
assertEq(Object.isExtensible(target), false);
|
||||
assertEq(Object.isExtensible(proxy), false);
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Call the trap with the handler as the this value, and the target as the first
|
||||
* argument
|
||||
*/
|
||||
var target = {};
|
||||
var called = false;
|
||||
var handler = {
|
||||
preventExtensions: function (target1) {
|
||||
assertEq(this, handler);
|
||||
assertEq(target1, target);
|
||||
called = true;
|
||||
Object.preventExtensions(target);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Object.preventExtensions(new Proxy(target, handler));
|
||||
assertEq(called, true);
|
|
@ -0,0 +1,13 @@
|
|||
load(libdir + "asserts.js");
|
||||
|
||||
/*
|
||||
* Throw a TypeError if the trap reports that the proxy has been made
|
||||
* non-extensible, while the target is still extensible
|
||||
*/
|
||||
assertThrowsInstanceOf(function () {
|
||||
Object.preventExtensions(new Proxy({}, {
|
||||
preventExtensions: function (target) {
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
}, TypeError);
|
|
@ -0,0 +1,13 @@
|
|||
load(libdir + "asserts.js");
|
||||
|
||||
/*
|
||||
* Throw a TypeError if the trap reports that the proxy cannot be made
|
||||
* non-extensible
|
||||
*/
|
||||
assertThrowsInstanceOf(function () {
|
||||
Object.preventExtensions(new Proxy({}, {
|
||||
preventExtensions: function (target) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
}, TypeError);
|
|
@ -0,0 +1,12 @@
|
|||
// Reflect side-effects from the trap
|
||||
target = {};
|
||||
var proxy = new Proxy(target, {
|
||||
preventExtensions: function (target) {
|
||||
Object.preventExtensions(target);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
Object.preventExtensions(proxy);
|
||||
assertEq(Object.isExtensible(target), false);
|
||||
assertEq(Object.isExtensible(proxy), false);
|
||||
|
|
@ -375,3 +375,5 @@ MSG_DEF(JSMSG_MUST_REPORT_UNDEFINED, 321, 0, JSEXN_TYPEERR, "proxy must report
|
|||
MSG_DEF(JSMSG_CANT_SET_NW_NC, 322, 0, JSEXN_TYPEERR, "proxy can't successfully set a non-writable, non-configurable property")
|
||||
MSG_DEF(JSMSG_CANT_SET_WO_SETTER, 323, 0, JSEXN_TYPEERR, "proxy can't succesfully set an accessor property without a setter")
|
||||
MSG_DEF(JSMSG_DEBUG_BAD_REFERENT, 324, 2, JSEXN_TYPEERR, "{0} does not refer to {1}")
|
||||
MSG_DEF(JSMSG_CANT_REPORT_NON_EXT, 325, 0, JSEXN_TYPEERR, "proxy can't report extensible object as non-extensible")
|
||||
MSG_DEF(JSMSG_CANT_MAKE_NON_EXT, 326, 0, JSEXN_TYPEERR, "can't prevent extensions on object")
|
||||
|
|
|
@ -3482,7 +3482,7 @@ JS_DeepFreezeObject(JSContext *cx, JSObject *objArg)
|
|||
assertSameCompartment(cx, obj);
|
||||
|
||||
/* Assume that non-extensible objects are already deep-frozen, to avoid divergence. */
|
||||
if (!obj->isExtensible())
|
||||
if (obj->isExtensible())
|
||||
return true;
|
||||
|
||||
if (!JSObject::freeze(cx, obj))
|
||||
|
|
|
@ -548,6 +548,9 @@ struct JSObject : public js::ObjectImpl
|
|||
static inline unsigned getSealedOrFrozenAttributes(unsigned attrs, ImmutabilityType it);
|
||||
|
||||
public:
|
||||
friend class js::BaseProxyHandler;
|
||||
|
||||
bool isExtensible() const;
|
||||
bool preventExtensions(JSContext *cx);
|
||||
|
||||
/* ES5 15.2.3.8: non-extensible, all props non-configurable */
|
||||
|
|
|
@ -412,6 +412,12 @@ IndirectProxyHandler::defineProperty(JSContext *cx, JSObject *proxy, jsid id_,
|
|||
JS_DefinePropertyById(cx, obj, id, v, desc->getter, desc->setter, desc->attrs);
|
||||
}
|
||||
|
||||
bool
|
||||
IndirectProxyHandler::preventExtensions(JSContext *cx, JSObject *proxy)
|
||||
{
|
||||
return GetProxyTargetObject(proxy)->preventExtensions(cx);
|
||||
}
|
||||
|
||||
bool
|
||||
IndirectProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy,
|
||||
AutoIdVector &props)
|
||||
|
@ -442,6 +448,12 @@ IndirectProxyHandler::enumerate(JSContext *cx, JSObject *proxy,
|
|||
return GetPropertyNames(cx, target, 0, &props);
|
||||
}
|
||||
|
||||
bool
|
||||
IndirectProxyHandler::isExtensible(JSObject *proxy)
|
||||
{
|
||||
return GetProxyTargetObject(proxy)->isExtensible();
|
||||
}
|
||||
|
||||
bool
|
||||
IndirectProxyHandler::call(JSContext *cx, JSObject *proxy, unsigned argc,
|
||||
Value *vp)
|
||||
|
@ -1031,6 +1043,7 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
|
|||
PropertyDescriptor *desc) MOZ_OVERRIDE;
|
||||
virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id,
|
||||
PropertyDescriptor *desc) MOZ_OVERRIDE;
|
||||
virtual bool preventExtensions(JSContext *cx, JSObject *proxy) MOZ_OVERRIDE;
|
||||
virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props);
|
||||
virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) MOZ_OVERRIDE;
|
||||
virtual bool enumerate(JSContext *cx, JSObject *proxy, AutoIdVector &props) MOZ_OVERRIDE;
|
||||
|
@ -1654,6 +1667,47 @@ ScriptedDirectProxyHandler::defineProperty(JSContext *cx, JSObject *proxy_, jsid
|
|||
return TrapDefineOwnProperty(cx, proxy, id, &v);
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::preventExtensions(JSContext *cx, JSObject *proxy_)
|
||||
{
|
||||
RootedObject proxy(cx, proxy_);
|
||||
|
||||
// step a
|
||||
RootedObject handler(cx, GetDirectProxyHandlerObject(proxy));
|
||||
|
||||
// step b
|
||||
RootedObject target(cx, GetProxyTargetObject(proxy));
|
||||
|
||||
// step c
|
||||
RootedValue trap(cx);
|
||||
if (!JSObject::getProperty(cx, handler, handler, cx->names().preventExtensions, &trap))
|
||||
return false;
|
||||
|
||||
// step d
|
||||
if (trap.isUndefined())
|
||||
return DirectProxyHandler::preventExtensions(cx, proxy_);
|
||||
|
||||
// step e
|
||||
Value argv[] = {
|
||||
ObjectValue(*target)
|
||||
};
|
||||
RootedValue trapResult(cx);
|
||||
if (!Invoke(cx, ObjectValue(*handler), trap, 1, argv, trapResult.address()))
|
||||
return false;
|
||||
|
||||
// steps f-h
|
||||
if (ToBoolean(trapResult)) {
|
||||
if (target->isExtensible()) {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NON_EXT);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_MAKE_NON_EXT);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy_,
|
||||
AutoIdVector &props)
|
||||
|
@ -2286,6 +2340,14 @@ Proxy::defineProperty(JSContext *cx, JSObject *proxy_, jsid id_, const Value &v)
|
|||
Proxy::defineProperty(cx, proxy, id, &desc);
|
||||
}
|
||||
|
||||
bool
|
||||
Proxy::preventExtensions(JSContext *cx, JSObject *proxy_)
|
||||
{
|
||||
JS_CHECK_RECURSION(cx, return false);
|
||||
RootedObject proxy(cx, proxy_);
|
||||
return GetProxyHandler(proxy)->preventExtensions(cx, proxy);
|
||||
}
|
||||
|
||||
bool
|
||||
Proxy::getOwnPropertyNames(JSContext *cx, JSObject *proxy_, AutoIdVector &props)
|
||||
{
|
||||
|
@ -2453,6 +2515,12 @@ Proxy::iterate(JSContext *cx, HandleObject proxy_, unsigned flags, MutableHandle
|
|||
return EnumeratedIdVectorToIterator(cx, proxy, flags, props, vp);
|
||||
}
|
||||
|
||||
bool
|
||||
Proxy::isExtensible(JSObject *proxy)
|
||||
{
|
||||
return GetProxyHandler(proxy)->isExtensible(proxy);
|
||||
}
|
||||
|
||||
bool
|
||||
Proxy::call(JSContext *cx, JSObject *proxy_, unsigned argc, Value *vp)
|
||||
{
|
||||
|
|
|
@ -49,6 +49,7 @@ class JS_FRIEND_API(Wrapper);
|
|||
class JS_FRIEND_API(BaseProxyHandler) {
|
||||
void *mFamily;
|
||||
bool mHasPrototype;
|
||||
|
||||
protected:
|
||||
// Subclasses may set this in their constructor.
|
||||
void setHasPrototype(bool hasPrototype) { mHasPrototype = hasPrototype; }
|
||||
|
@ -91,6 +92,7 @@ class JS_FRIEND_API(BaseProxyHandler) {
|
|||
PropertyDescriptor *desc) = 0;
|
||||
virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id,
|
||||
PropertyDescriptor *desc) = 0;
|
||||
virtual bool preventExtensions(JSContext *cx, JSObject *proxy);
|
||||
virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy,
|
||||
AutoIdVector &props) = 0;
|
||||
virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) = 0;
|
||||
|
@ -109,6 +111,7 @@ class JS_FRIEND_API(BaseProxyHandler) {
|
|||
Value *vp);
|
||||
|
||||
/* Spidermonkey extensions. */
|
||||
virtual bool isExtensible(JSObject *proxy);
|
||||
virtual bool call(JSContext *cx, JSObject *proxy, unsigned argc, Value *vp);
|
||||
virtual bool construct(JSContext *cx, JSObject *proxy, unsigned argc, Value *argv, Value *rval);
|
||||
virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
|
||||
|
@ -148,6 +151,7 @@ class JS_PUBLIC_API(IndirectProxyHandler) : public BaseProxyHandler {
|
|||
PropertyDescriptor *desc) MOZ_OVERRIDE;
|
||||
virtual bool defineProperty(JSContext *cx, JSObject *proxy, jsid id,
|
||||
PropertyDescriptor *desc) MOZ_OVERRIDE;
|
||||
virtual bool preventExtensions(JSContext *cx, JSObject *proxy) MOZ_OVERRIDE;
|
||||
virtual bool getOwnPropertyNames(JSContext *cx, JSObject *proxy,
|
||||
AutoIdVector &props) MOZ_OVERRIDE;
|
||||
virtual bool delete_(JSContext *cx, JSObject *proxy, jsid id,
|
||||
|
@ -156,6 +160,7 @@ class JS_PUBLIC_API(IndirectProxyHandler) : public BaseProxyHandler {
|
|||
AutoIdVector &props) MOZ_OVERRIDE;
|
||||
|
||||
/* Spidermonkey extensions. */
|
||||
virtual bool isExtensible(JSObject *proxy);
|
||||
virtual bool call(JSContext *cx, JSObject *proxy, unsigned argc,
|
||||
Value *vp) MOZ_OVERRIDE;
|
||||
virtual bool construct(JSContext *cx, JSObject *proxy, unsigned argc,
|
||||
|
@ -218,6 +223,7 @@ class Proxy {
|
|||
Value *vp);
|
||||
static bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, PropertyDescriptor *desc);
|
||||
static bool defineProperty(JSContext *cx, JSObject *proxy, jsid id, const Value &v);
|
||||
static bool preventExtensions(JSContext *cx, JSObject *proxy);
|
||||
static bool getOwnPropertyNames(JSContext *cx, JSObject *proxy, AutoIdVector &props);
|
||||
static bool delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp);
|
||||
static bool enumerate(JSContext *cx, JSObject *proxy, AutoIdVector &props);
|
||||
|
@ -234,6 +240,7 @@ class Proxy {
|
|||
static bool iterate(JSContext *cx, HandleObject proxy, unsigned flags, MutableHandleValue vp);
|
||||
|
||||
/* Spidermonkey extensions. */
|
||||
static bool isExtensible(JSObject *proxy);
|
||||
static bool call(JSContext *cx, JSObject *proxy, unsigned argc, Value *vp);
|
||||
static bool construct(JSContext *cx, JSObject *proxy, unsigned argc, Value *argv, Value *rval);
|
||||
static bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
|
||||
|
|
|
@ -1028,12 +1028,21 @@ Shape::setObjectParent(JSContext *cx, JSObject *parent, TaggedProto proto, Shape
|
|||
return replaceLastProperty(cx, base, proto, last);
|
||||
}
|
||||
|
||||
bool
|
||||
JSObject::isExtensible() const
|
||||
{
|
||||
if (isProxy())
|
||||
return Proxy::isExtensible(const_cast<JSObject *>(this));
|
||||
return !lastProperty()->hasObjectFlag(js::BaseShape::NOT_EXTENSIBLE);
|
||||
}
|
||||
|
||||
bool
|
||||
JSObject::preventExtensions(JSContext *cx)
|
||||
{
|
||||
JS_ASSERT(isExtensible());
|
||||
|
||||
RootedObject self(cx, this);
|
||||
if (self->isProxy())
|
||||
return Proxy::preventExtensions(cx, self);
|
||||
|
||||
/*
|
||||
* Force lazy properties to be resolved by iterating over the objects' own
|
||||
|
@ -1049,6 +1058,38 @@ JSObject::preventExtensions(JSContext *cx)
|
|||
return self->setFlag(cx, BaseShape::NOT_EXTENSIBLE, GENERATE_SHAPE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Per spec, preventExtensions should be a fundamental trap. We don't want to
|
||||
* force consumers of BaseProxyHandler (i.e. DOM bindings) to implement yet
|
||||
* another fundamental trap, however, so we provide a default implementation
|
||||
* here. We cannot simply forward the operation to the target object, since
|
||||
* BaseProxyHandler is not guaranteed to have one. Instead, we use the proxy's
|
||||
* shape tree (as we would for normal objects) to store a flag indicating that
|
||||
* the proxy is not extensible.
|
||||
*/
|
||||
bool
|
||||
BaseProxyHandler::isExtensible(JSObject *proxy)
|
||||
{
|
||||
return !proxy->lastProperty()->hasObjectFlag(js::BaseShape::NOT_EXTENSIBLE);
|
||||
}
|
||||
|
||||
bool
|
||||
BaseProxyHandler::preventExtensions(JSContext *cx, JSObject *proxy_)
|
||||
{
|
||||
JS_ASSERT(isExtensible());
|
||||
RootedObject proxy(cx, proxy_);
|
||||
|
||||
/*
|
||||
* Force lazy properties to be resolved by iterating over the objects' own
|
||||
* properties.
|
||||
*/
|
||||
AutoIdVector props(cx);
|
||||
if (!js::GetPropertyNames(cx, proxy, JSITER_HIDDEN | JSITER_OWNONLY, &props))
|
||||
return false;
|
||||
JS_ASSERT(!proxy->isDenseArray());
|
||||
return proxy_->setFlag(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE);
|
||||
}
|
||||
|
||||
bool
|
||||
JSObject::setFlag(JSContext *cx, /*BaseShape::Flag*/ uint32_t flag_, GenerateShape generateShape)
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#endif
|
||||
|
||||
#include "jsobj.h"
|
||||
#include "jsproxy.h"
|
||||
#include "jspropertytree.h"
|
||||
#include "jstypes.h"
|
||||
|
||||
|
|
|
@ -106,6 +106,7 @@
|
|||
macro(of, of, "of") \
|
||||
macro(parseFloat, parseFloat, "parseFloat") \
|
||||
macro(parseInt, parseInt, "parseInt") \
|
||||
macro(preventExtensions, preventExtensions, "preventExtensions") \
|
||||
macro(propertyIsEnumerable, propertyIsEnumerable, "propertyIsEnumerable") \
|
||||
macro(proto, proto, "__proto__") \
|
||||
macro(return, return_, "return") \
|
||||
|
|
|
@ -100,12 +100,6 @@ js::ObjectImpl::nativeContainsNoAllocation(Shape &shape)
|
|||
return nativeLookupNoAllocation(shape.propid()) == &shape;
|
||||
}
|
||||
|
||||
inline bool
|
||||
js::ObjectImpl::isExtensible() const
|
||||
{
|
||||
return !lastProperty()->hasObjectFlag(BaseShape::NOT_EXTENSIBLE);
|
||||
}
|
||||
|
||||
inline bool
|
||||
js::ObjectImpl::isDenseArray() const
|
||||
{
|
||||
|
|
|
@ -414,7 +414,7 @@ DenseElementsHeader::defineElement(JSContext *cx, Handle<ObjectImpl*> obj, uint3
|
|||
* If the element doesn't exist, we can only add it if the object is
|
||||
* extensible.
|
||||
*/
|
||||
if (!obj->isExtensible()) {
|
||||
if (!obj->asObjectPtr()->isExtensible()) {
|
||||
*succeeded = false;
|
||||
if (!shouldThrow)
|
||||
return true;
|
||||
|
|
|
@ -1016,8 +1016,6 @@ class ObjectImpl : public gc::Cell
|
|||
return type_->proto;
|
||||
}
|
||||
|
||||
inline bool isExtensible() const;
|
||||
|
||||
/*
|
||||
* XXX Once the property/element split of bug 586842 is complete, these
|
||||
* methods should move back to JSObject.
|
||||
|
|
Загрузка…
Ссылка в новой задаче