diff -r c08e36cdd14e js/src/jit-test/tests/basic/testDirectProxySet.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit-test/tests/basic/testDirectProxySet.js Mon Aug 20 17:04:54 2012 +0200 @@ -0,0 +1,80 @@ +// Forward to the target if the trap is not defined +assertEq(new Proxy({ + foo: 'bar' +}, {})['foo'] = 'baz', 'baz'); + +/* + * Call the trap with the handler as the this value, the target as the first + * argument, the name of the property as the second argument, the value as the + * third argument, and the receiver as the fourth argument + */ +var target = {}; +var called = false; +var handler = { + set: function (target1, name, val, receiver) { + assertEq(this, handler); + assertEq(target1, target); + assertEq(name, 'foo'); + assertEq(val, 'baz'); + assertEq(receiver, proxy); + called = true; + } +}; +var proxy = new Proxy(target, handler); +proxy['foo'] = 'baz'; +assertEq(called, true); + +// Throw a TypeError if the trap sets a non-writable, non-configurable property +var target = {}; +Object.defineProperty(target, 'foo', { + value: 'bar', + writable: false, + configurable: false +}); +var caught = false; +try { + new Proxy(target, { + set: function (target, name, val, receiver) { + return true; + } + })['foo'] = 'baz'; +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +/* + * Throw a TypeError if the trap sets a non-configurable accessor property that + * doest not have a setter + */ +var target = {}; +Object.defineProperty(target, 'foo', { + get: function () { + return 'bar' + }, + configurable: false +}); +var caught = false; +try { + new Proxy(target, { + set: function (target, name, val, receiver) { + return true; + } + })['foo'] = 'baz'; +} catch (e) { + assertEq(String(e).indexOf('TypeError'), 0); + caught = true; +} +assertEq(caught, true); + +// Reflect side-effects from the trap +var target = { + foo: 'bar' +}; +new Proxy(target, { + set: function (target, name, val, receiver) { + target[name] = 'qux'; + } +})['foo'] = 'baz'; +assertEq(target['foo'], 'qux'); diff -r c08e36cdd14e js/src/js.msg --- a/js/src/js.msg Mon Aug 20 17:04:00 2012 +0200 +++ b/js/src/js.msg Mon Aug 20 17:04:54 2012 +0200 @@ -360,8 +360,11 @@ MSG_DEF(JSMSG_CANT_REPORT_INVALID, 30 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") MSG_DEF(JSMSG_MUST_REPORT_SAME_VALUE, 313, 0, JSEXN_TYPEERR, "proxy must report the same value for a non-writable, non-configurable property") MSG_DEF(JSMSG_MUST_REPORT_UNDEFINED, 314, 0, JSEXN_TYPEERR, "proxy must report undefined for a non-configurable accessor property without a getter") + +MSG_DEF(JSMSG_CANT_SET_NW_NC, 315, 0, JSEXN_TYPEERR, "proxy can't successfully set a non-writable, non-configurable property") +MSG_DEF(JSMSG_CANT_SET_WO_SETTER, 316, 0, JSEXN_TYPEERR, "proxy can't succesfully set an accessor property without a setter") diff -r c08e36cdd14e js/src/jsproxy.cpp --- a/js/src/jsproxy.cpp Mon Aug 20 17:04:00 2012 +0200 +++ b/js/src/jsproxy.cpp Mon Aug 20 17:04:54 2012 +0200 @@ -1979,22 +1979,87 @@ ScriptedDirectProxyHandler::get(JSContex } } // step 8 *vp = trapResult; return true; } +// Proxy.[[SetP]](P, V, Receiver) bool -ScriptedDirectProxyHandler::set(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, +ScriptedDirectProxyHandler::set(JSContext *cx, JSObject *proxy_, JSObject *receiver_, jsid id_, bool strict, Value *vp) { - JS_NOT_REACHED("not yet implemented"); - return false; + RootedObject proxy(cx, proxy_); + RootedObject receiver(cx, receiver_); + RootedId id(cx, id_); + + // step 1 + RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); + + // step 2 + RootedObject target(cx, GetProxyTargetObject(proxy)); + + // step 3 + RootedValue trap(cx); + if (!handler->getProperty(cx, ATOM(set), &trap)) + return false; + + // step 4 + if (trap.isUndefined()) + return DirectProxyHandler::get(cx, proxy_, receiver_, id_, vp); + + // step 5 + JSString *name = ToString(cx, IdToValue(id)); + if (!name) + return false; + Value argv[] = { + ObjectOrNullValue(target), + StringValue(name), + *vp, + ObjectValue(*receiver) + }; + RootedValue trapResult(cx); + if (!Invoke(cx, ObjectValue(*handler), trap, 4, argv, trapResult.address())) + return false; + + // step 6 + bool success = ToBoolean(trapResult); + + // step 7 + if (success) { + AutoPropertyDescriptorRooter desc(cx); + if (!GetOwnPropertyDescriptor(cx, target, id, &desc)) + return false; + + if (desc.obj) { + if (IsDataDescriptor(desc) && (desc.attrs & JSPROP_PERMANENT) && + (desc.attrs & JSPROP_READONLY)) { + bool same; + if (!SameValue(cx, *vp, desc.value, &same)) + return false; + if (!same) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SET_NW_NC); + return false; + } + } + + if (IsAccessorDescriptor(desc) && (desc.attrs & JSPROP_PERMANENT)) { + if (!(desc.attrs & JSPROP_SETTER)) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SET_WO_SETTER); + return false; + } + } + } + } + + // step 8 + *vp = BooleanValue(success); + return true; } bool ScriptedDirectProxyHandler::keys(JSContext *cx, JSObject *proxy, AutoIdVector &props) { JS_NOT_REACHED("not yet implemented"); return false; }