diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index af999adb92f..d3aadc4974b 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -50,9 +50,39 @@ obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); + HandleValue idValue = args.get(0); + + // As an optimization, provide a fast path when rooting is not necessary and + // we can safely retrieve the attributes from the object's shape. + + /* Steps 1-2. */ + jsid id; + if (args.thisv().isObject() && ValueToId(cx, idValue, &id)) { + JSObject *obj = &args.thisv().toObject(), *pobj; + + /* Step 3. */ + Shape *shape; + if (!obj->is() && + HasOwnProperty(cx, obj->getOps()->lookupGeneric, obj, id, &pobj, &shape)) + { + /* Step 4. */ + if (!shape) { + args.rval().setBoolean(false); + return true; + } + + /* Step 5. */ + if (pobj->isNative()) { + unsigned attrs = GetShapeAttributes(pobj, shape); + args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0); + return true; + } + } + } + /* Step 1. */ - RootedId id(cx); - if (!ValueToId(cx, args.get(0), &id)) + RootedId idRoot(cx); + if (!ValueToId(cx, idValue, &idRoot)) return false; /* Step 2. */ @@ -60,29 +90,13 @@ obj_propertyIsEnumerable(JSContext *cx, unsigned argc, Value *vp) if (!obj) return false; - /* Steps 3. */ - RootedObject pobj(cx); - RootedShape prop(cx); - if (!JSObject::lookupGeneric(cx, obj, id, &pobj, &prop)) + /* Step 3. */ + Rooted desc(cx); + if (!GetOwnPropertyDescriptor(cx, obj, idRoot, &desc)) return false; - /* Step 4. */ - if (!prop) { - args.rval().setBoolean(false); - return true; - } - - if (pobj != obj) { - args.rval().setBoolean(false); - return true; - } - - /* Step 5. */ - unsigned attrs; - if (!JSObject::getGenericAttributes(cx, pobj, id, &attrs)) - return false; - - args.rval().setBoolean((attrs & JSPROP_ENUMERATE) != 0); + /* Steps 4-5. */ + args.rval().setBoolean(desc.object() && desc.isEnumerable()); return true; } diff --git a/js/src/tests/ecma_6/Object/propertyIsEnumerable-proxy.js b/js/src/tests/ecma_6/Object/propertyIsEnumerable-proxy.js new file mode 100644 index 00000000000..766296cc17f --- /dev/null +++ b/js/src/tests/ecma_6/Object/propertyIsEnumerable-proxy.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function logProxy(object) { + var log = []; + var handler = { + getOwnPropertyDescriptor(target, propertyKey) { + log.push(propertyKey); + return Object.getOwnPropertyDescriptor(target, propertyKey); + } + }; + var proxy = new Proxy(object, new Proxy(handler, { + get(target, propertyKey, receiver) { + if (!(propertyKey in target)) { + throw new Error(`Unexpected call to trap: "${propertyKey}"`); + } + return target[propertyKey]; + } + })); + return {proxy, log}; +} + +for (var property of ["string-property", Symbol("symbol-property")]) { + // Test 1: property is not present on object + var {proxy, log} = logProxy({}); + var result = Object.prototype.propertyIsEnumerable.call(proxy, property); + assertEq(result, false); + assertDeepEq(log, [property]); + + // Test 2: property is present on object and enumerable + var {proxy, log} = logProxy({[property]: 0}); + var result = Object.prototype.propertyIsEnumerable.call(proxy, property); + assertEq(result, true); + assertDeepEq(log, [property]); + + // Test 3: property is present on object, but not enumerable + var {proxy, log} = logProxy(Object.defineProperty({[property]: 0}, property, {enumerable: false})); + var result = Object.prototype.propertyIsEnumerable.call(proxy, property); + assertEq(result, false); + assertDeepEq(log, [property]); + + // Test 4: property is present on prototype object + var {proxy, log} = logProxy(Object.create({[property]: 0})); + var result = Object.prototype.propertyIsEnumerable.call(proxy, property); + assertEq(result, false); + assertDeepEq(log, [property]); + + // Test 5: property is present on prototype object, prototype is proxy object + var {proxy, log} = logProxy({[property]: 0}); + var result = Object.prototype.propertyIsEnumerable.call(Object.create(proxy), property); + assertEq(result, false); + assertDeepEq(log, []); +} + +reportCompare(0, 0);