diff -r 9abbaa249e0e js/src/jit-test/tests/basic/testDirectProxyGetOwnPropertyNames.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit-test/tests/basic/testDirectProxyGetOwnPropertyNames.js Mon Aug 20 16:59:09 2012 +0200 @@ -0,0 +1,167 @@ +// Forward to the target if the trap is not defined +var names = Object.getOwnPropertyNames(new Proxy(Object.create(Object.create(null, { + a: { + enumerable: true, + configurable: true + }, + b: { + enumerable: false, + configurable: true + } +}), { + c: { + enumerable: true, + configurable: true + }, + d: { + enumerable: false, + configurable: true + } +}), {})); +assertEq(names.length, 2); +assertEq(names[0], 'c'); +assertEq(names[1], 'd'); + +/* + * Call the trap with the handler as the this value, and the target as the first + * argument + */ +var target = {}; +var called = false; +var handler = { + getOwnPropertyNames: function (target1) { + assertEq(this, handler); + assertEq(target1, target); + called = true; + return []; + } +}; +Object.getOwnPropertyNames(new Proxy(target, handler)); +assertEq(called, true); + +// Throw a TypeError if the trap does not return an object +var caught = false; +try { + Object.getOwnPropertyNames(new Proxy({}, { + getOwnPropertyNames: function (target) { + return; + } + })); +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +// Throw a TypeError if the trap reports the same property twice +var caught = false; +try { + Object.getOwnPropertyNames(new Proxy({}, { + getOwnPropertyNames: function (target) { + return [ 'foo', 'foo' ]; + } + })); +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +/* + * Throw a TypeError if the trap reports a new own property on a non-extensible + * object + */ +var target = {}; +Object.preventExtensions(target); +var caught = false; +try { + Object.getOwnPropertyNames(new Proxy(target, { + getOwnPropertyNames: function (target) { + return [ 'foo' ]; + } + })); +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +// Throw a TypeError if the trap skips a non-configurable property +var target = {}; +Object.defineProperty(target, 'foo', { + configurable: false +}); +var caught = false; +try { + Object.getOwnPropertyNames(new Proxy(target, { + getOwnPropertyNames: function (target) { + return []; + } + })); +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +/* + * Throw a TypeError if the trap skips an existing own property on a + * non-extensible object + */ +var target = {}; +Object.defineProperty(target, 'foo', { + configurable: true +}); +Object.preventExtensions(target); +var caught = false; +try { + Object.getOwnPropertyNames(new Proxy(target, { + getOwnPropertyNames: function (target) { + return []; + } + })); +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +// Return the names returned by the trap +var target = {}; +Object.defineProperty(target, 'foo', { + configurable: true +}); +var names = Object.getOwnPropertyNames(new Proxy(target, { + getOwnPropertyNames : function (target) { + return [ 'bar' ]; + } +})); +assertEq(names.length, 1); +assertEq(names[0], 'bar'); + +var names = Object.getOwnPropertyNames(new Proxy(Object.create(Object.create(null, { + a: { + enumerable: true, + configurable: true + }, + b: { + enumerable: false, + configurable: true + } +}), { + c: { + enumerable: true, + configurable: true + }, + d: { + enumerable: false, + configurable: true + } +}), { + getOwnPropertyNames: function (target) { + return [ 'c', 'e' ]; + } +})); +assertEq(names.length, 2); +assertEq(names[0], 'c'); +assertEq(names[1], 'e'); diff -r 9abbaa249e0e js/src/js.msg --- a/js/src/js.msg Mon Aug 20 16:58:23 2012 +0200 +++ b/js/src/js.msg Mon Aug 20 16:59:09 2012 +0200 @@ -356,8 +356,10 @@ MSG_DEF(JSMSG_INTRINSIC_NOT_DEFINED, 30 MSG_DEF(JSMSG_CANT_REPORT_NC_AS_NE, 303, 0, JSEXN_TYPEERR, "proxy can't report a non-configurable own property as non-existent") MSG_DEF(JSMSG_CANT_REPORT_E_AS_NE, 304, 0, JSEXN_TYPEERR, "proxy can't report an existing own property as non-existent on a non-extensible object") MSG_DEF(JSMSG_CANT_REPORT_NEW, 305, 0, JSEXN_TYPEERR, "proxy can't report a new property on a non-extensible object") MSG_DEF(JSMSG_CANT_REPORT_INVALID, 306, 0, JSEXN_TYPEERR, "proxy can't report an incompatible property descriptor") MSG_DEF(JSMSG_CANT_REPORT_NE_AS_NC, 307, 0, JSEXN_TYPEERR, "proxy can't report a non-existent property as non-configurable") MSG_DEF(JSMSG_CANT_DEFINE_NEW, 308, 0, JSEXN_TYPEERR, "proxy can't define a new property on a non-extensible object") MSG_DEF(JSMSG_CANT_DEFINE_INVALID, 309, 0, JSEXN_TYPEERR, "proxy can't define an incompatible property descriptor") MSG_DEF(JSMSG_CANT_DEFINE_NE_AS_NC, 310, 0, JSEXN_TYPEERR, "proxy can't define a non-existent property as non-configurable") +MSG_DEF(JSMSG_INVALID_TRAP_RESULT, 311, 2, JSEXN_TYPEERR, "trap {1} for {0} returned an invalid result") +MSG_DEF(JSMSG_CANT_SKIP_NC, 312, 0, JSEXN_TYPEERR, "proxy can't skip a non-configurable property") diff -r 9abbaa249e0e js/src/jsproxy.cpp --- a/js/src/jsproxy.cpp Mon Aug 20 16:58:23 2012 +0200 +++ b/js/src/jsproxy.cpp Mon Aug 20 16:59:09 2012 +0200 @@ -1461,16 +1461,119 @@ TrapDefineOwnProperty(JSContext *cx, Han } // step 9 // FIXME: API does not include a Throw parameter vp.set(BooleanValue(false)); return true; } +static inline void +ReportInvalidTrapResult(JSContext *cx, JSObject *proxy, JSAtom *atom) +{ + RootedValue v(cx, ObjectOrNullValue(proxy)); + JSAutoByteString bytes; + if (!js_AtomToPrintableString(cx, atom, &bytes)) + return; + js_ReportValueError2(cx, JSMSG_INVALID_TRAP_RESULT, JSDVG_IGNORE_STACK, v, + NullPtr(), bytes.ptr()); +} + +static bool +ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleValue v, + AutoIdVector &props, JSAtom *trapName) +{ + JS_ASSERT(v.isObject()); + RootedObject array(cx, &v.toObject()); + + // steps g-h + uint32_t n; + if (!js_GetLengthProperty(cx, array, &n)) + return false; + + // steps i-k + for (uint32_t i = 0; i < n; ++i) { + // step i + RootedValue v(cx); + if (!array->getElement(cx, i, &v)) + return false; + + // step ii + RootedId id(cx); + if (!ValueToId(cx, v, id.address())) + return false; + + // step iii + for (uint32_t j = 0; j < i; ++j) { + if (props[j] == id) { + ReportInvalidTrapResult(cx, proxy, trapName); + return false; + } + } + + // step iv + bool isFixed; + if (!HasOwn(cx, target, id, &isFixed)) + return false; + + // step v + if (!target->isExtensible() && !isFixed) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_NEW); + return false; + } + + // step vi + if (!props.append(id)) + return false; + } + + // step l + AutoIdVector ownProps(cx); + if (!GetPropertyNames(cx, target, JSITER_OWNONLY | JSITER_HIDDEN, &ownProps)) + return false; + + // step m + for (size_t i = 0; i < ownProps.length(); ++i) { + RootedId id(cx, ownProps[i]); + + bool found = false; + for (size_t j = 0; j < props.length(); ++j) { + if (props[j] == id) { + found = true; + break; + } + } + if (found) + continue; + + // step i + bool sealed; + if (!IsSealed(cx, target, id, &sealed)) + return false; + if (sealed) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SKIP_NC); + return false; + } + + // step ii + bool isFixed; + if (!HasOwn(cx, target, id, &isFixed)) + return false; + + // step iii + if (!target->isExtensible() && isFixed) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_REPORT_E_AS_NE); + return false; + } + } + + // step n + return true; +} + ScriptedDirectProxyHandler::ScriptedDirectProxyHandler() : DirectProxyHandler(&sScriptedDirectProxyHandlerFamily) { } ScriptedDirectProxyHandler::~ScriptedDirectProxyHandler() { } @@ -1531,21 +1634,52 @@ ScriptedDirectProxyHandler::defineProper if (!FromGenericPropertyDescriptor(cx, d, &v)) return false; // step 2 return TrapDefineOwnProperty(cx, proxy, id, &v); } bool -ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy, +ScriptedDirectProxyHandler::getOwnPropertyNames(JSContext *cx, JSObject *proxy_, AutoIdVector &props) { - JS_NOT_REACHED("not yet implemented"); - return false; + RootedObject proxy(cx, proxy_); + + // step a + RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); + + // step b + RootedObject target(cx, GetProxyTargetObject(proxy)); + + // step c + RootedValue trap(cx); + if (!handler->getProperty(cx, ATOM(getOwnPropertyNames), &trap)) + return false; + + // step d + if (trap.isUndefined()) + return DirectProxyHandler::getOwnPropertyNames(cx, proxy_, props); + + // step e + Value argv[] = { + ObjectValue(*target) + }; + RootedValue trapResult(cx); + if (!Invoke(cx, ObjectValue(*handler), trap, 1, argv, trapResult.address())) + return false; + + // step f + if (trapResult.isPrimitive()) { + ReportInvalidTrapResult(cx, proxy, ATOM(getOwnPropertyNames)); + return false; + } + + // steps g to n are shared + return ArrayToIdVector(cx, proxy, target, trapResult, props, ATOM(getOwnPropertyNames)); } bool ScriptedDirectProxyHandler::delete_(JSContext *cx, JSObject *proxy, jsid id, bool *bp) { JS_NOT_REACHED("not yet implemented"); return false; }