зеркало из https://github.com/mozilla/gecko-dev.git
Bug 645416, part 25 - Add support for enumerating symbol-keyed properties. r=Waldo.
Object.keys, Object.getOwnPropertyNames, and for-in loops skip symbol-keyed properties per spec, but Object.defineProperties sees them, and a future Reflect.ownKeys API will need to be able to see them. This patch changes the comments on JSITER_FOREACH and JSITER_KEYVALUE, but not the behavior. The comments were just wrong. --HG-- extra : rebase_source : f1ad99d416df8a8acef5598bef2cde4b72dcdb31
This commit is contained in:
Родитель
04be8d105f
Коммит
0582d104f2
|
@ -170,7 +170,7 @@ js::ObjectToSource(JSContext *cx, HandleObject obj)
|
|||
MutableHandleString gsop[2] = {&str0, &str1};
|
||||
|
||||
AutoIdVector idv(cx);
|
||||
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &idv))
|
||||
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
|
||||
return nullptr;
|
||||
|
||||
bool comma = false;
|
||||
|
|
|
@ -863,7 +863,7 @@ StructMetaTypeDescr::create(JSContext *cx,
|
|||
{
|
||||
// Obtain names of fields, which are the own properties of `fields`
|
||||
AutoIdVector ids(cx);
|
||||
if (!GetPropertyNames(cx, fields, JSITER_OWNONLY, &ids))
|
||||
if (!GetPropertyNames(cx, fields, JSITER_OWNONLY | JSITER_SYMBOLS, &ids))
|
||||
return nullptr;
|
||||
|
||||
// Iterate through each field. Collect values for the various
|
||||
|
|
|
@ -26,3 +26,8 @@ test("[0, 1, 2]");
|
|||
test("[,,,,,]");
|
||||
test("/a*a/");
|
||||
test("function () {}");
|
||||
test("(function () {\n" +
|
||||
" var x = {};\n" +
|
||||
" x[Symbol()] = 1; x[Symbol.for('moon')] = 2; x[Symbol.iterator] = 3;\n" +
|
||||
" return x;\n" +
|
||||
"})()");
|
||||
|
|
|
@ -4,4 +4,9 @@ var g = newGlobal();
|
|||
var dbg = Debugger();
|
||||
var gobj = dbg.addDebuggee(g);
|
||||
g.p = {xyzzy: 8}; // makes a cross-compartment wrapper
|
||||
assertEq(gobj.getOwnPropertyDescriptor("p").value.getOwnPropertyDescriptor("xyzzy").value, 8);
|
||||
g.p[Symbol.for("plugh")] = 9;
|
||||
var wp = gobj.getOwnPropertyDescriptor("p").value;
|
||||
var names = wp.getOwnPropertyNames();
|
||||
assertEq(names.length, 1);
|
||||
assertEq(names[0], "xyzzy");
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Forward to the target if the trap is not defined
|
||||
var names = Object.getOwnPropertyNames(Proxy(Object.create(Object.create(null, {
|
||||
var objAB = Object.create(null, {
|
||||
a: {
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
|
@ -8,7 +8,9 @@ var names = Object.getOwnPropertyNames(Proxy(Object.create(Object.create(null, {
|
|||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
}), {
|
||||
});
|
||||
|
||||
var objCD = Object.create(objAB, {
|
||||
c: {
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
|
@ -17,7 +19,11 @@ var names = Object.getOwnPropertyNames(Proxy(Object.create(Object.create(null, {
|
|||
enumerable: false,
|
||||
configurable: true
|
||||
}
|
||||
}), {}));
|
||||
});
|
||||
|
||||
var outerProxy = new Proxy(objCD, {});
|
||||
objCD[Symbol("moon")] = "something";
|
||||
var names = Object.getOwnPropertyNames(outerProxy);
|
||||
assertEq(names.length, 2);
|
||||
assertEq(names[0], 'c');
|
||||
assertEq(names[1], 'd');
|
||||
|
|
|
@ -820,10 +820,11 @@ IsObjectInContextCompartment(JSObject *obj, const JSContext *cx);
|
|||
* XDR_BYTECODE_VERSION.
|
||||
*/
|
||||
#define JSITER_ENUMERATE 0x1 /* for-in compatible hidden default iterator */
|
||||
#define JSITER_FOREACH 0x2 /* return [key, value] pair rather than key */
|
||||
#define JSITER_KEYVALUE 0x4 /* destructuring for-in wants [key, value] */
|
||||
#define JSITER_FOREACH 0x2 /* get obj[key] for each property */
|
||||
#define JSITER_KEYVALUE 0x4 /* obsolete destructuring for-in wants [key, value] */
|
||||
#define JSITER_OWNONLY 0x8 /* iterate over obj's own properties only */
|
||||
#define JSITER_HIDDEN 0x10 /* also enumerate non-enumerable properties */
|
||||
#define JSITER_SYMBOLS 0x20 /* also include symbol property keys */
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
RunningWithTrustedPrincipals(JSContext *cx);
|
||||
|
|
|
@ -96,36 +96,36 @@ static inline bool
|
|||
Enumerate(JSContext *cx, HandleObject pobj, jsid id,
|
||||
bool enumerable, unsigned flags, IdSet& ht, AutoIdVector *props)
|
||||
{
|
||||
/*
|
||||
* We implement __proto__ using a property on |Object.prototype|, but
|
||||
* because __proto__ is highly deserving of removal, we don't want it to
|
||||
* show up in property enumeration, even if only for |Object.prototype|
|
||||
* (think introspection by Prototype-like frameworks that add methods to
|
||||
* the built-in prototypes). So exclude __proto__ if the object where the
|
||||
* property was found has no [[Prototype]] and might be |Object.prototype|.
|
||||
*/
|
||||
// We implement __proto__ using a property on |Object.prototype|, but
|
||||
// because __proto__ is highly deserving of removal, we don't want it to
|
||||
// show up in property enumeration, even if only for |Object.prototype|
|
||||
// (think introspection by Prototype-like frameworks that add methods to
|
||||
// the built-in prototypes). So exclude __proto__ if the object where the
|
||||
// property was found has no [[Prototype]] and might be |Object.prototype|.
|
||||
if (MOZ_UNLIKELY(!pobj->getTaggedProto().isObject() && JSID_IS_ATOM(id, cx->names().proto)))
|
||||
return true;
|
||||
|
||||
if (!(flags & JSITER_OWNONLY) || pobj->is<ProxyObject>() || pobj->getOps()->enumerate) {
|
||||
/* If we've already seen this, we definitely won't add it. */
|
||||
// If we've already seen this, we definitely won't add it.
|
||||
IdSet::AddPtr p = ht.lookupForAdd(id);
|
||||
if (MOZ_UNLIKELY(!!p))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* It's not necessary to add properties to the hash table at the end of
|
||||
* the prototype chain, but custom enumeration behaviors might return
|
||||
* duplicated properties, so always add in such cases.
|
||||
*/
|
||||
// It's not necessary to add properties to the hash table at the end of
|
||||
// the prototype chain, but custom enumeration behaviors might return
|
||||
// duplicated properties, so always add in such cases.
|
||||
if ((pobj->is<ProxyObject>() || pobj->getProto() || pobj->getOps()->enumerate) && !ht.add(p, id))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enumerable || (flags & JSITER_HIDDEN))
|
||||
return props->append(id);
|
||||
// Symbol-keyed properties and nonenumerable properties are skipped unless
|
||||
// the caller specifically asks for them.
|
||||
if (JSID_IS_SYMBOL(id) && !(flags & JSITER_SYMBOLS))
|
||||
return true;
|
||||
if (!enumerable && !(flags & JSITER_HIDDEN))
|
||||
return true;
|
||||
|
||||
return true;
|
||||
return props->append(id);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -154,16 +154,39 @@ EnumerateNativeProperties(JSContext *cx, HandleObject pobj, unsigned flags, IdSe
|
|||
|
||||
size_t initialLength = props->length();
|
||||
|
||||
/* Collect all unique properties from this object's scope. */
|
||||
/* Collect all unique property names from this object's shape. */
|
||||
Shape::Range<NoGC> r(pobj->lastProperty());
|
||||
bool symbolsFound = false;
|
||||
for (; !r.empty(); r.popFront()) {
|
||||
Shape &shape = r.front();
|
||||
jsid id = shape.propid();
|
||||
|
||||
if (!Enumerate(cx, pobj, shape.propid(), shape.enumerable(), flags, ht, props))
|
||||
if (JSID_IS_SYMBOL(id)) {
|
||||
symbolsFound = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
|
||||
return false;
|
||||
}
|
||||
|
||||
::Reverse(props->begin() + initialLength, props->end());
|
||||
|
||||
if (symbolsFound && (flags & JSITER_SYMBOLS)) {
|
||||
// Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22)
|
||||
// 9.1.12 requires that all symbols appear after all strings in the
|
||||
// result.
|
||||
initialLength = props->length();
|
||||
for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
|
||||
Shape &shape = r.front();
|
||||
jsid id = shape.propid();
|
||||
if (JSID_IS_SYMBOL(id)) {
|
||||
if (!Enumerate(cx, pobj, id, shape.enumerable(), flags, ht, props))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
::Reverse(props->begin() + initialLength, props->end());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -210,7 +233,8 @@ Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props)
|
|||
const Class *clasp = pobj->getClass();
|
||||
if (pobj->isNative() &&
|
||||
!pobj->getOps()->enumerate &&
|
||||
!(clasp->flags & JSCLASS_NEW_ENUMERATE)) {
|
||||
!(clasp->flags & JSCLASS_NEW_ENUMERATE))
|
||||
{
|
||||
if (!clasp->enumerate(cx, pobj))
|
||||
return false;
|
||||
if (!EnumerateNativeProperties(cx, pobj, flags, ht, props))
|
||||
|
@ -220,6 +244,9 @@ Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props)
|
|||
AutoIdVector proxyProps(cx);
|
||||
if (flags & JSITER_OWNONLY) {
|
||||
if (flags & JSITER_HIDDEN) {
|
||||
// This gets all property keys, both strings and
|
||||
// symbols. The call to Enumerate in the loop below
|
||||
// will filter out unwanted keys, per the flags.
|
||||
if (!Proxy::getOwnPropertyNames(cx, pobj, proxyProps))
|
||||
return false;
|
||||
} else {
|
||||
|
@ -230,11 +257,14 @@ Snapshot(JSContext *cx, JSObject *pobj_, unsigned flags, AutoIdVector *props)
|
|||
if (!Proxy::enumerate(cx, pobj, proxyProps))
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
|
||||
if (!Enumerate(cx, pobj, proxyProps[n], true, flags, ht, props))
|
||||
return false;
|
||||
}
|
||||
/* Proxy objects enumerate the prototype on their own, so we are done here. */
|
||||
|
||||
// Proxy objects enumerate the prototype on their own, so we're
|
||||
// done here.
|
||||
break;
|
||||
}
|
||||
RootedValue state(cx);
|
||||
|
@ -318,7 +348,7 @@ js::VectorToIdArray(JSContext *cx, AutoIdVector &props, JSIdArray **idap)
|
|||
JS_FRIEND_API(bool)
|
||||
js::GetPropertyNames(JSContext *cx, JSObject *obj, unsigned flags, AutoIdVector *props)
|
||||
{
|
||||
return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN), props);
|
||||
return Snapshot(cx, obj, flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS), props);
|
||||
}
|
||||
|
||||
size_t sCustomIteratorCount = 0;
|
||||
|
|
|
@ -1116,7 +1116,7 @@ bool
|
|||
js::ReadPropertyDescriptors(JSContext *cx, HandleObject props, bool checkAccessors,
|
||||
AutoIdVector *ids, AutoPropDescVector *descs)
|
||||
{
|
||||
if (!GetPropertyNames(cx, props, JSITER_OWNONLY, ids))
|
||||
if (!GetPropertyNames(cx, props, JSITER_OWNONLY | JSITER_SYMBOLS, ids))
|
||||
return false;
|
||||
|
||||
RootedId id(cx);
|
||||
|
@ -1215,7 +1215,7 @@ JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it)
|
|||
return false;
|
||||
|
||||
AutoIdVector props(cx);
|
||||
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
|
||||
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY | JSITER_SYMBOLS, &props))
|
||||
return false;
|
||||
|
||||
/* preventExtensions must sparsify dense objects, so we can assign to holes without checks. */
|
||||
|
@ -1321,7 +1321,7 @@ JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it,
|
|||
}
|
||||
|
||||
AutoIdVector props(cx);
|
||||
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
|
||||
if (!GetPropertyNames(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY | JSITER_SYMBOLS, &props))
|
||||
return false;
|
||||
|
||||
RootedId id(cx);
|
||||
|
@ -1858,7 +1858,7 @@ JS_CopyPropertiesFrom(JSContext *cx, HandleObject target, HandleObject obj)
|
|||
JSAutoCompartment ac(cx, obj);
|
||||
|
||||
AutoIdVector props(cx);
|
||||
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &props))
|
||||
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props))
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < props.length(); ++i) {
|
||||
|
|
|
@ -239,6 +239,9 @@ BaseProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props)
|
|||
for (size_t j = 0, len = props.length(); j < len; j++) {
|
||||
JS_ASSERT(i <= j);
|
||||
id = props[j];
|
||||
if (JSID_IS_SYMBOL(id))
|
||||
continue;
|
||||
|
||||
AutoWaivePolicy policy(cx, proxy, id, BaseProxyHandler::GET);
|
||||
if (!getOwnPropertyDescriptor(cx, proxy, id, &desc))
|
||||
return false;
|
||||
|
@ -419,7 +422,7 @@ DirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject proxy,
|
|||
{
|
||||
assertEnteredPolicy(cx, proxy, JSID_VOID, ENUMERATE);
|
||||
RootedObject target(cx, proxy->as<ProxyObject>().target());
|
||||
return GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN, &props);
|
||||
return GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &props);
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -1068,7 +1071,8 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler {
|
|||
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
|
||||
virtual bool defineProperty(JSContext *cx, HandleObject proxy, HandleId id,
|
||||
MutableHandle<PropertyDescriptor> desc) MOZ_OVERRIDE;
|
||||
virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props);
|
||||
virtual bool getOwnPropertyNames(JSContext *cx, HandleObject proxy, AutoIdVector &props)
|
||||
MOZ_OVERRIDE;
|
||||
virtual bool delete_(JSContext *cx, HandleObject proxy, HandleId id, bool *bp) MOZ_OVERRIDE;
|
||||
virtual bool enumerate(JSContext *cx, HandleObject proxy, AutoIdVector &props) MOZ_OVERRIDE;
|
||||
|
||||
|
@ -1679,7 +1683,8 @@ ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, HandleObject prox
|
|||
|
||||
// Here we add a bunch of extra sanity checks. It is unclear if they will also appear in
|
||||
// the spec. See step 10-11
|
||||
return ArrayToIdVector(cx, proxy, target, trapResult, props, JSITER_OWNONLY | JSITER_HIDDEN,
|
||||
return ArrayToIdVector(cx, proxy, target, trapResult, props,
|
||||
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS,
|
||||
cx->names().getOwnPropertyNames);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/ */
|
||||
|
||||
// ES6 draft rev 25 (2014 May 22), 9.1.12 "[[OwnPropertyKeys]] ()":
|
||||
//
|
||||
var log;
|
||||
function LoggingProxy() {
|
||||
return new Proxy({}, {
|
||||
defineProperty: (t, key, desc) => {
|
||||
log.push(key);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var keys = [
|
||||
"before",
|
||||
Symbol(),
|
||||
"during",
|
||||
Symbol.for("during"),
|
||||
Symbol.iterator,
|
||||
"after"
|
||||
];
|
||||
var descs = {};
|
||||
for (var k of keys)
|
||||
descs[k] = {configurable: true, value: 0};
|
||||
|
||||
function test(descsObj) {
|
||||
log = [];
|
||||
Object.defineProperties(LoggingProxy(), descs);
|
||||
assertEq(log.length, keys.length);
|
||||
assertDeepEq(log.map(k => typeof k), ["string", "string", "string", "symbol", "symbol", "symbol"]);
|
||||
for (var key of keys)
|
||||
assertEq(log.indexOf(key) !== -1, true);
|
||||
}
|
||||
|
||||
test(descs);
|
||||
test(new Proxy(descs, {}));
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/ */
|
||||
|
||||
// for-in loops skip properties with symbol keys, even enumerable properties.
|
||||
var obj = {};
|
||||
obj[Symbol.for("moon")] = "sun";
|
||||
obj[Symbol("asleep")] = "awake";
|
||||
obj[Symbol.iterator] = "List";
|
||||
for (var x in obj)
|
||||
throw "FAIL: " + uneval(x);
|
||||
|
||||
// This includes inherited properties.
|
||||
var obj2 = Object.create(obj);
|
||||
for (var x in obj2)
|
||||
throw "FAIL: " + uneval(x);
|
||||
|
||||
// The same goes for proxies.
|
||||
var p = new Proxy(obj, {});
|
||||
for (var x in p)
|
||||
throw "FAIL: " + uneval(x);
|
||||
var p2 = new Proxy(obj2, {});
|
||||
for (var x in p2)
|
||||
throw "FAIL: " + uneval(x);
|
||||
|
||||
// Object.keys() and .getOwnPropertyNames() also skip symbols.
|
||||
assertEq(Object.keys(obj).length, 0);
|
||||
assertEq(Object.keys(p).length, 0);
|
||||
assertEq(Object.keys(obj2).length, 0);
|
||||
assertEq(Object.keys(p2).length, 0);
|
||||
assertEq(Object.getOwnPropertyNames(obj).length, 0);
|
||||
assertEq(Object.getOwnPropertyNames(p).length, 0);
|
||||
assertEq(Object.getOwnPropertyNames(obj2).length, 0);
|
||||
assertEq(Object.getOwnPropertyNames(p2).length, 0);
|
||||
|
||||
// Test interaction of Object.keys(), proxies, and symbol property keys.
|
||||
var log = [];
|
||||
var h = {
|
||||
ownKeys: (t) => {
|
||||
log.push("ownKeys");
|
||||
return ["a", "0", Symbol.for("moon"), Symbol("asleep"), Symbol.iterator];
|
||||
},
|
||||
getOwnPropertyDescriptor: (t, key) => {
|
||||
log.push("gopd", key);
|
||||
return {configurable: true, enumerable: true, value: 0, writable: true};
|
||||
}
|
||||
};
|
||||
p = new Proxy({}, h);
|
||||
assertDeepEq(Object.keys(p), ["a", "0"]);
|
||||
assertDeepEq(log, ["ownKeys", "gopd", "a", "gopd", "0"]);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
|
@ -0,0 +1,33 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/ */
|
||||
|
||||
// ES6 does not specify enumeration order, but implementations mostly retain
|
||||
// property insertion order -- and must, for web compatibility. This test checks
|
||||
// that symbol-keyed properties do not interfere with that order.
|
||||
|
||||
var obj = {};
|
||||
obj[Symbol("moon")] = 0;
|
||||
obj.x = 1;
|
||||
obj[Symbol.for("y")] = 2
|
||||
obj.y = 3;
|
||||
obj[Symbol.iterator] = function* () { yield 4; };
|
||||
obj.z = 5;
|
||||
Object.prototype[Symbol.for("comet")] = 6;
|
||||
|
||||
var keys = [];
|
||||
for (var k in obj)
|
||||
keys.push(k);
|
||||
assertDeepEq(keys, ["x", "y", "z"]);
|
||||
assertDeepEq(Object.keys(obj), ["x", "y", "z"]);
|
||||
|
||||
// Test with more properties.
|
||||
for (var i = 0; i < 1000; i++)
|
||||
obj[Symbol(i)] = i;
|
||||
obj.w = 1000;
|
||||
keys = []
|
||||
for (var k in obj)
|
||||
keys.push(k);
|
||||
assertDeepEq(keys, ["x", "y", "z", "w"]);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(0, 0);
|
Загрузка…
Ссылка в новой задаче