Bug 645416, part 27 - Implement Object.getOwnPropertySymbols(). r=Waldo.

--HG--
extra : rebase_source : 88b44837f0b845f88a881d4b472d3c9ef44bcbf0
This commit is contained in:
Jason Orendorff 2014-06-23 10:57:03 -05:00
Родитель 9a4a0f2647
Коммит 3d134987af
7 изменённых файлов: 191 добавлений и 80 удалений

Просмотреть файл

@ -889,34 +889,46 @@ obj_is(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static bool
obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp)
bool
js::IdToStringOrSymbol(JSContext *cx, HandleId id, MutableHandleValue result)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
if (!GetFirstArgumentAsObject(cx, args, "Object.getOwnPropertyNames", &obj))
if (JSID_IS_INT(id)) {
JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
if (!str)
return false;
result.setString(str);
} else if (JSID_IS_ATOM(id)) {
result.setString(JSID_TO_STRING(id));
} else {
result.setSymbol(JSID_TO_SYMBOL(id));
}
return true;
}
/* ES6 draft rev 25 (2014 May 22) 19.1.2.8.1 */
static bool
GetOwnPropertyKeys(JSContext *cx, const CallArgs &args, unsigned flags, const char *fnname)
{
// steps 1-2
RootedObject obj(cx, ToObject(cx, args.get(0)));
if (!obj)
return false;
// steps 3-10
AutoIdVector keys(cx);
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &keys))
if (!GetPropertyNames(cx, obj, flags, &keys))
return false;
// step 11
AutoValueVector vals(cx);
if (!vals.resize(keys.length()))
return false;
for (size_t i = 0, len = keys.length(); i < len; i++) {
jsid id = keys[i];
if (JSID_IS_INT(id)) {
JSString *str = Int32ToString<CanGC>(cx, JSID_TO_INT(id));
if (!str)
return false;
vals[i].setString(str);
} else if (JSID_IS_ATOM(id)) {
vals[i].setString(JSID_TO_STRING(id));
} else {
vals[i].setSymbol(JSID_TO_SYMBOL(id));
}
MOZ_ASSERT_IF(JSID_IS_SYMBOL(keys[i]), flags & JSITER_SYMBOLS);
MOZ_ASSERT_IF(!JSID_IS_SYMBOL(keys[i]), !(flags & JSITER_SYMBOLSONLY));
if (!IdToStringOrSymbol(cx, keys[i], vals[i]))
return false;
}
JSObject *aobj = NewDenseCopiedArray(cx, vals.length(), vals.begin());
@ -927,6 +939,24 @@ obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp)
return true;
}
static bool
obj_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY | JSITER_HIDDEN,
"Object.getOwnPropertyNames");
}
/* ES6 draft rev 25 (2014 May 22) 19.1.2.8 */
static bool
obj_getOwnPropertySymbols(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return GetOwnPropertyKeys(cx, args,
JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY,
"Object.getOwnPropertySymbols");
}
/* ES5 15.2.3.6: Object.defineProperty(O, P, Attributes) */
static bool
obj_defineProperty(JSContext *cx, unsigned argc, Value *vp)
@ -1179,6 +1209,7 @@ const JSFunctionSpec js::object_static_methods[] = {
JS_FN("defineProperties", obj_defineProperties, 2,0),
JS_FN("create", obj_create, 2,0),
JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1,0),
JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 1,0),
JS_FN("isExtensible", obj_isExtensible, 1,0),
JS_FN("preventExtensions", obj_preventExtensions, 1,0),
JS_FN("freeze", obj_freeze, 1,0),

Просмотреть файл

@ -21,6 +21,14 @@ extern const JSFunctionSpec object_static_methods[];
bool
obj_construct(JSContext *cx, unsigned argc, JS::Value *vp);
/*
* Like IdToValue, but convert int jsids to strings. This is used when
* exposing a jsid to script for Object.getOwnProperty{Names,Symbols}
* or scriptable proxy traps.
*/
bool
IdToStringOrSymbol(JSContext *cx, JS::HandleId id, JS::MutableHandleValue result);
#if JS_HAS_TOSOURCE
// Object.prototype.toSource. Function.prototype.toSource and uneval use this.
JSString *

Просмотреть файл

@ -825,6 +825,7 @@ IsObjectInContextCompartment(JSObject *obj, const JSContext *cx);
#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 */
#define JSITER_SYMBOLSONLY 0x40 /* exclude string property keys */
JS_FRIEND_API(bool)
RunningWithTrustedPrincipals(JSContext *cx);

Просмотреть файл

@ -119,8 +119,9 @@ Enumerate(JSContext *cx, HandleObject pobj, jsid id,
}
// Symbol-keyed properties and nonenumerable properties are skipped unless
// the caller specifically asks for them.
if (JSID_IS_SYMBOL(id) && !(flags & JSITER_SYMBOLS))
// the caller specifically asks for them. A caller can also filter out
// non-symbols by asking for JSITER_SYMBOLSONLY.
if (JSID_IS_SYMBOL(id) ? !(flags & JSITER_SYMBOLS) : (flags & JSITER_SYMBOLSONLY))
return true;
if (!enumerable && !(flags & JSITER_HIDDEN))
return true;
@ -132,50 +133,57 @@ static bool
EnumerateNativeProperties(JSContext *cx, HandleObject pobj, unsigned flags, IdSet &ht,
AutoIdVector *props)
{
/* Collect any dense elements from this object. */
size_t initlen = pobj->getDenseInitializedLength();
const Value *vp = pobj->getDenseElements();
for (size_t i = 0; i < initlen; ++i, ++vp) {
if (!vp->isMagic(JS_ELEMENTS_HOLE)) {
/* Dense arrays never get so large that i would not fit into an integer id. */
if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
bool enumerateSymbols;
if (flags & JSITER_SYMBOLSONLY) {
enumerateSymbols = true;
} else {
/* Collect any dense elements from this object. */
size_t initlen = pobj->getDenseInitializedLength();
const Value *vp = pobj->getDenseElements();
for (size_t i = 0; i < initlen; ++i, ++vp) {
if (!vp->isMagic(JS_ELEMENTS_HOLE)) {
/* Dense arrays never get so large that i would not fit into an integer id. */
if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
return false;
}
}
/* Collect any typed array elements from this object. */
if (pobj->is<TypedArrayObject>()) {
size_t len = pobj->as<TypedArrayObject>().length();
for (size_t i = 0; i < len; i++) {
if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
return false;
}
}
size_t initialLength = props->length();
/* Collect all unique property names from this object's shape. */
bool symbolsFound = false;
Shape::Range<NoGC> r(pobj->lastProperty());
for (; !r.empty(); r.popFront()) {
Shape &shape = r.front();
jsid id = shape.propid();
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());
enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS);
}
/* Collect any typed array elements from this object. */
if (pobj->is<TypedArrayObject>()) {
size_t len = pobj->as<TypedArrayObject>().length();
for (size_t i = 0; i < len; i++) {
if (!Enumerate(cx, pobj, INT_TO_JSID(i), /* enumerable = */ true, flags, ht, props))
return false;
}
}
size_t initialLength = props->length();
/* 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 (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)) {
if (enumerateSymbols) {
// 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();
size_t initialLength = props->length();
for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
Shape &shape = r.front();
jsid id = shape.propid();
@ -388,7 +396,9 @@ 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 | JSITER_SYMBOLS), props);
return Snapshot(cx, obj,
flags & (JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS | JSITER_SYMBOLSONLY),
props);
}
size_t sCustomIteratorCount = 0;

Просмотреть файл

@ -650,23 +650,10 @@ Trap(JSContext *cx, HandleObject handler, HandleValue fval, unsigned argc, Value
return Invoke(cx, ObjectValue(*handler), fval, argc, argv, rval);
}
static bool
IdToExposableValue(JSContext *cx, HandleId id, MutableHandleValue value)
{
value.set(IdToValue(id)); // Re-use out-param to avoid Rooted overhead.
if (value.isSymbol())
return true;
JSString *name = ToString<CanGC>(cx, value);
if (!name)
return false;
value.setString(name);
return true;
}
static bool
Trap1(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, MutableHandleValue rval)
{
if (!IdToExposableValue(cx, id, rval)) // Re-use out-param to avoid Rooted overhead.
if (!IdToStringOrSymbol(cx, id, rval))
return false;
return Trap(cx, handler, fval, 1, rval.address(), rval);
}
@ -676,7 +663,7 @@ Trap2(JSContext *cx, HandleObject handler, HandleValue fval, HandleId id, Value
MutableHandleValue rval)
{
RootedValue v(cx, v_);
if (!IdToExposableValue(cx, id, rval)) // Re-use out-param to avoid Rooted overhead.
if (!IdToStringOrSymbol(cx, id, rval))
return false;
JS::AutoValueArray<2> argv(cx);
argv[0].set(rval);
@ -950,7 +937,7 @@ ScriptedIndirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObjec
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue idv(cx);
if (!IdToExposableValue(cx, id, &idv))
if (!IdToStringOrSymbol(cx, id, &idv))
return false;
JS::AutoValueArray<2> argv(cx);
argv[0].setObjectOrNull(receiver);
@ -969,7 +956,7 @@ ScriptedIndirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObjec
{
RootedObject handler(cx, GetIndirectProxyHandlerObject(proxy));
RootedValue idv(cx);
if (!IdToExposableValue(cx, id, &idv))
if (!IdToStringOrSymbol(cx, id, &idv))
return false;
JS::AutoValueArray<3> argv(cx);
argv[0].setObjectOrNull(receiver);
@ -1465,7 +1452,7 @@ ScriptedDirectProxyHandler::getOwnPropertyDescriptor(JSContext *cx, HandleObject
// step 8-9
RootedValue propKey(cx);
if (!IdToExposableValue(cx, id, &propKey))
if (!IdToStringOrSymbol(cx, id, &propKey))
return false;
Value argv[] = {
@ -1588,7 +1575,7 @@ ScriptedDirectProxyHandler::defineProperty(JSContext *cx, HandleObject proxy, Ha
// step 10, 12
RootedValue propKey(cx);
if (!IdToExposableValue(cx, id, &propKey))
if (!IdToStringOrSymbol(cx, id, &propKey))
return false;
Value argv[] = {
@ -1711,7 +1698,7 @@ ScriptedDirectProxyHandler::delete_(JSContext *cx, HandleObject proxy, HandleId
// step 8
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
if (!IdToStringOrSymbol(cx, id, &value))
return false;
Value argv[] = {
ObjectValue(*target),
@ -1809,7 +1796,7 @@ ScriptedDirectProxyHandler::has(JSContext *cx, HandleObject proxy, HandleId id,
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
if (!IdToStringOrSymbol(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),
@ -1871,7 +1858,7 @@ ScriptedDirectProxyHandler::get(JSContext *cx, HandleObject proxy, HandleObject
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
if (!IdToStringOrSymbol(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),
@ -1934,7 +1921,7 @@ ScriptedDirectProxyHandler::set(JSContext *cx, HandleObject proxy, HandleObject
// step 5
RootedValue value(cx);
if (!IdToExposableValue(cx, id, &value))
if (!IdToStringOrSymbol(cx, id, &value))
return false;
Value argv[] = {
ObjectOrNullValue(target),

Просмотреть файл

@ -0,0 +1,28 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
// getOwnPropertySymbols(proxy) calls the getOwnPropertyNames hook (only).
var symbols = [Symbol(), Symbol("moon"), Symbol.for("sun"), Symbol.iterator];
var hits = 0;
function HandlerProxy() {
return new Proxy({}, {
get: function (t, key) {
if (key !== "ownKeys")
throw new Error("tried to access handler[" + uneval(key) + "]");
hits++;
return t => symbols;
}
});
}
function OwnKeysProxy() {
return new Proxy({}, new HandlerProxy);
}
assertDeepEq(Object.getOwnPropertySymbols(new OwnKeysProxy), symbols);
assertEq(hits, 1);
if (typeof reportCompare === "function")
reportCompare(0, 0);

Просмотреть файл

@ -0,0 +1,46 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/licenses/publicdomain/ */
assertDeepEq(Object.getOwnPropertySymbols({}), []);
// String keys are ignored.
assertEq(Object.getOwnPropertySymbols({a: 1, b: 2}).length, 0);
assertEq(Object.getOwnPropertySymbols([0, 1, 2, 3]).length, 0);
// Symbol keys are observed.
var iterable = {};
Object.defineProperty(iterable, Symbol.iterator, {
value: () => [][Symbol.iterator]()
});
assertDeepEq(Object.getOwnPropertySymbols(iterable), [Symbol.iterator]);
assertDeepEq(Object.getOwnPropertySymbols(new Proxy(iterable, {})), [Symbol.iterator]);
// Test on an object with a thousand own properties.
var obj = {};
for (var i = 0; i < 1000; i++) {
obj[Symbol.for("x" + i)] = 1;
}
assertEq(Object.getOwnPropertyNames(obj).length, 0);
var symbols = Object.getOwnPropertySymbols(obj);
assertEq(symbols.length, 1000);
assertEq(symbols.indexOf(Symbol.for("x0")) !== -1, true);
assertEq(symbols.indexOf(Symbol.for("x241")) !== -1, true);
assertEq(symbols.indexOf(Symbol.for("x999")) !== -1, true);
assertEq(Object.getOwnPropertySymbols(new Proxy(obj, {})).length, 1000);
// The prototype chain is not consulted.
assertEq(Object.getOwnPropertySymbols(Object.create(obj)).length, 0);
assertEq(Object.getOwnPropertySymbols(new Proxy(Object.create(obj), {})).length, 0);
// Primitives are coerced to objects; but there are never any symbol-keyed
// properties on the resulting wrapper objects.
assertThrowsInstanceOf(() => Object.getOwnPropertySymbols(), TypeError);
assertThrowsInstanceOf(() => Object.getOwnPropertySymbols(undefined), TypeError);
assertThrowsInstanceOf(() => Object.getOwnPropertySymbols(null), TypeError);
for (var primitive of [true, 1, 3.14, "hello", Symbol()])
assertEq(Object.getOwnPropertySymbols(primitive).length, 0);
assertEq(Object.getOwnPropertySymbols.length, 1);
if (typeof reportCompare === "function")
reportCompare(0, 0);