Permalink
Browse files

added support for mutable prototype through setPrototypeOf on platfor…

…ms with mutable
  • Loading branch information...
1 parent 66a8c00 commit a8e665a7530f1edbf0cc6e784f53b7340a00008b @tvcutsem committed May 23, 2013
Showing with 153 additions and 6 deletions.
  1. +10 −0 doc/api.md
  2. +19 −1 doc/handler_api.md
  3. +7 −1 doc/traps.md
  4. +76 −4 reflect.js
  5. +41 −0 test/testProxies.js
View
@@ -111,6 +111,16 @@ Returns the prototype link of the `target` object.
Same as the ES5 built-in Object.getPrototypeOf(target).
If `target` is a proxy, calls that proxy's `getPrototypeOf` trap.
+## Reflect.setPrototypeOf(target, newProto)
+
+Mutates the prototype link of the `target` object and sets it to `newProto`. One can only set the prototype of extensible `target` objects. The operation will throw if `target` is non-extensible. This operation returns a boolean success value, indicating whether the operation succeeded.
+
+This operation only works on platforms that support the non-standard `__proto__`
+property, the property is mutable, and is represented as an accessor property on
+`Object.prototype`. On these platforms, this library will also define a corresponding `Object.setPrototypeOf(target, newProto)` function.
+
+If `target` is a proxy, calls that proxy's `setPrototypeOf` trap.
+
## Reflect.deleteProperty(target, name)
Attempts to delete the `name` property on `target`. Calling this function is equivalent to performing `delete target[name]`, except that this function returns a boolean that indicates whether or not the deletion was successful.
View
@@ -198,7 +198,25 @@ This trap intercepts the following operations:
The proxy throws a TypeError if:
- * The return value of the trap is not the actual prototype link of the wrapped target object. This restriction is imposed to ensure that `getPrototypeOf` cannot by itself be used to introduce a mutable prototype link.
+ * The target object is non-extensible and the return value of the trap is not the actual prototype link of the wrapped target object. This restriction is imposed to ensure that `getPrototypeOf` cannot by itself be used to introduce a mutable prototype link on non-extensible objects.
+
+## setPrototypeOf(target, newProto)
+
+Called when the proxy's prototype is mutated. Supported only on platforms
+with mutable `__proto__` (and where `__proto__` is an accessor on `Object.prototype`).
+
+This trap should return a boolean indicating whether or not the prototype
+was successfully modified.
+
+This trap intercepts the following operations:
+
+ * `Object.setPrototypeOf(proxy)`
+ * `Reflect.setPrototypeOf(proxy)`
+ * `Object.getOwnPropertyDescriptor(Object.prototype,'__proto__').set.call(proxy,newProto)`
+
+The proxy throws a TypeError if:
+
+ * The target object is non-extensible, the trap returns true, and the target object's prototype does not match the `newProto` argument.
## deleteProperty(target, name)
View
@@ -189,6 +189,11 @@ try {
<td>Object.getPrototypeOf(proxy)</td>
<td>handler.getPrototypeOf(target)</td>
</tr>
+ <tr>
+ <td>setPrototypeOf (11)</td>
+ <td>Object.setPrototypeOf(proxy, newProto)</td>
+ <td>handler.setPrototypeOf(target, newProto)</td>
+ </tr>
</table>
## Notes
@@ -202,4 +207,5 @@ try {
* (7): assuming that `proxy.valueOf`, which triggers the proxy's "get" trap, returned `Object.prototype.valueOf`.
* (8): assuming that `proxy.toString`, which triggers the proxy's "get" trap, returned `Object.prototype.toString`.
* (9): the return value of the `getOwnPropertyDescriptor` trap (the property descriptor object) is not the original value returned from the intercepted `Object.getOwnPropertyDescriptor` call. Rather, it is a fresh descriptor object that is guaranteed to be "complete" (i.e. to define values for all relevant ECMAScript property attributes).
- * (10): the third argument to the `defineProperty` trap (the property descriptor object) is not the original value passed into the intercepted `Object.defineProperty` call. Rather, it is a fresh descriptor object that is guaranteed to be "complete" (i.e. to define values for all relevant ECMAScript property attributes).
+ * (10): the third argument to the `defineProperty` trap (the property descriptor object) is not the original value passed into the intercepted `Object.defineProperty` call. Rather, it is a fresh descriptor object that is guaranteed to be "complete" (i.e. to define values for all relevant ECMAScript property attributes).
+ * (11): only on platforms that support mutable `__proto__` and where `__proto__` is an accessor property defined on `Object.prototype`.
View
@@ -431,6 +431,7 @@ function isCompatibleDescriptor(extensible, current, desc) {
return true;
}
+
// ---- The Validator handler wrapper around user handlers ----
/**
@@ -875,11 +876,39 @@ Validator.prototype = {
}
var allegedProto = trap(this.target);
- var actualProto = Object_getPrototypeOf(this.target);
- if (!sameValue(allegedProto, actualProto)) {
- throw new TypeError("prototype value does not match: " + this.target);
+
+ if (!Object_isExtensible(this.target)) {
+ var actualProto = Object_getPrototypeOf(this.target);
+ if (!sameValue(allegedProto, actualProto)) {
+ throw new TypeError("prototype value does not match: " + this.target);
+ }
+ }
+
+ return allegedProto;
+ },
+
+ /**
+ * If target is non-extensible and setPrototypeOf trap returns true,
+ * check whether the trap result corresponds to the target's [[Prototype]]
+ */
+ setPrototypeOf: function(newProto) {
+ var trap = this.getTrap("setPrototypeOf");
+ if (trap === undefined) {
+ // default forwarding behavior
+ return Reflect.setPrototypeOf(this.target, newProto);
+ }
+
+ var success = trap(this.target, newProto);
+
+ success = !!success;
+ if (success && !Object_isExtensible(this.target)) {
+ var actualProto = Object_getPrototypeOf(this.target);
+ if (!sameValue(newProto, actualProto)) {
+ throw new TypeError("prototype value does not match: " + this.target);
+ }
}
- return actualProto;
+
+ return success;
},
/**
@@ -1483,6 +1512,44 @@ Array.isArray = function(subject) {
}
}
+// setPrototypeOf support on platforms that support __proto__
+
+// patch and extract original __proto__ setter
+var __proto__setter = (function() {
+ var protoDesc = prim_getOwnPropertyDescriptor(Object.prototype,'__proto__');
+ if (protoDesc === undefined ||
+ typeof protoDesc.set !== "function") {
+ return function() {
+ throw new TypeError("setPrototypeOf not supported on this platform");
+ }
+ }
+
+ prim_defineProperty(Object.prototype, '__proto__', {
+ set: function(newProto) {
+ return Object.setPrototypeOf(this, newProto);
+ }
+ });
+
+ return protoDesc.set;
+}());
+
+Object.setPrototypeOf = function(target, newProto) {
+ var handler = directProxies.get(target);
+ if (handler !== undefined) {
+ if (handler.setPrototypeOf(newProto)) {
+ return target;
+ } else {
+ throw new TypeError("proxy rejected prototype mutation");
+ }
+ } else {
+ if (!Object_isExtensible(target)) {
+ throw new TypeError("can't set prototype on non-extensible object: " + target);
+ }
+ __proto__setter.call(target, newProto);
+ return target;
+ }
+}
+
// ============= Reflection module =============
// see http://wiki.ecmascript.org/doku.php?id=harmony:reflect_api
@@ -1565,6 +1632,10 @@ var Reflect = global.Reflect = {
getPrototypeOf: function(target) {
return Object.getPrototypeOf(target);
},
+ setPrototypeOf: function(target, newProto) {
+ Object.setPrototypeOf(target, newProto);
+ return true;
+ },
freeze: function(target) {
Object.freeze(target);
return true;
@@ -1751,6 +1822,7 @@ Handler.prototype = {
getOwnPropertyDescriptor: forward("getOwnPropertyDescriptor"),
getOwnPropertyNames: forward("getOwnPropertyNames"),
getPrototypeOf: forward("getPrototypeOf"),
+ setPrototypeOf: forward("setPrototypeOf"),
defineProperty: forward("defineProperty"),
deleteProperty: forward("deleteProperty"),
preventExtensions: forward("preventExtensions"),
View
@@ -96,6 +96,7 @@ load('../reflect.js');
testSet();
testTransparentWrappers();
testRevocableProxies();
+ testSetPrototypeOf();
for (var testName in TESTS) {
emulatedProps = {};
@@ -762,6 +763,46 @@ load('../reflect.js');
assertThrows('proxy is revoked', function() { delete p.x });
assert(typeof p === 'object', 'typeof still works on revoked proxy');
}
+
+ function testSetPrototypeOf() {
+ var desc = Object.getOwnPropertyDescriptor(Object.prototype,'__proto__');
+ if (desc === undefined || typeof desc.get !== "function") {
+ // setPrototypeOf not supported on this platform
+ assertThrows('setPrototypeOf not supported on this platform',
+ function() { Object.setPrototypeOf({}, {}); });
+ } else {
+ var parent = {};
+ var child = Object.create(parent);
+ var newParent = {};
+ assert(Object.getPrototypeOf(child) === parent, 'getPrototypeOf before');
+ assert(Object.setPrototypeOf(child, newParent) === child, 'setPrototypeOf return');
+ assert(Object.getPrototypeOf(child) === newParent, 'getPrototypeOf after');
+
+ assertThrows("can't set prototype on non-extensible object: "+({}),
+ function() {
+ Object.setPrototypeOf(Object.preventExtensions({}), {});
+ });
+
+ var p = Proxy({}, {
+ setPrototypeOf: function(target, newProto) {
+ assert(newProto === newParent, 'newProto === newParent');
+ return Reflect.setPrototypeOf(target, newProto);
+ }
+ });
+ Object.setPrototypeOf(p, newParent);
+ assert(Object.getPrototypeOf(p) === newParent, 'getPrototypeOf proxy after');
+
+ assertThrows("prototype value does not match: " + {},
+ function() {
+ var p = Proxy(Object.preventExtensions({}), {
+ setPrototypeOf: function(target, newProto) {
+ return true;
+ }
+ });
+ Object.setPrototypeOf(p, newParent);
+ });
+ }
+ }
if (typeof window === "undefined") {
test();

0 comments on commit a8e665a

Please sign in to comment.