I promised to give an update on the work I've been doing to investigate to what extent the Proxy API may support non-extensible objects.<div>In a nutshell: I now think it is possible for Proxies to emulate not just non-extensible objects, but also sealed and frozen objects, given appropriate invariant checks.</div>
<div><br></div><div>Before I continue, a brief history of how we got here:</div><div><history></div><div>When Mark and I originally designed the Proxy API, we took care that proxies could not violate a number of invariants of ES5.1 objects, related to non-configurable properties and object extensibility (cf. ES5.1 section 8.6.2).</div>
<div> The net result was that:</div><div> a) proxies could not emulate non-configurable properties (i.e. all property descriptors returned by the get{Own}PropertyDescriptor trap must be configurable:true)</div><div> b) Object.{preventExtensions,seal,freeze} would "fix" a proxy, after which it could no longer trap.</div>
<div><br></div><div>While b) seemed a reasonable restriction, a) was controversial and discussed both during earlier TC39 meetings and here on es-discuss (see a.o. <<a href="https://mail.mozilla.org/pipermail/es-discuss/2011-June/015114.html">https://mail.mozilla.org/pipermail/es-discuss/2011-June/015114.html</a>>). That discussion revealed that proxies really need to be able to emulate non-configurable properties.  One motivating use case was emulating Array's non-configurable |length| property.</div>
<div><br></div><div>To alleviate restriction a), Mark and I looked for alternatives, recorded as successive strawmen at <<a href="http://wiki.ecmascript.org/doku.php?id=strawman:fixed_properties">http://wiki.ecmascript.org/doku.php?id=strawman:fixed_properties</a>>.</div>
<div>A first strawman (the "short-circuiting approach" on the wiki page) was based on having a proxy "cache" non-configurable properties, such that if the proxy hit the cache, it would no longer trap the handler. That design proved to be flawed, for several reasons, as pointed out by David Bruant, IIRC.</div>
<div><br></div><div>Out of that discussion grew a second approach (the "trap-and-enforce approach" on the wiki page). In that approach, the handler can fully emulate non-configurable properties, but the handler is "monitored" for invariant violations. Because FF4+ proxies support emulation of non-configurable properties (without invariant checking), I was able to implement such a "monitoring" proxy handler in Javascript itself. That was the FixedHandler I proposed earlier: <<a href="https://mail.mozilla.org/pipermail/es-discuss/2011-July/015686.html">https://mail.mozilla.org/pipermail/es-discuss/2011-July/015686.html</a>>.</div>
<div><br></div><div>That approach was well received, but it did not alleviate restriction b) (see <<a href="https://mail.mozilla.org/pipermail/es-discuss/2011-July/015944.html">https://mail.mozilla.org/pipermail/es-discuss/2011-July/015944.html</a>>). IIRC, the motivation here was again Array: emulating the magical |length| property even if the Array is made non-extensible.</div>
<div></history></div><div><br></div><div>Over the past few weeks I have been working on an extension of the FixedHandler that allows proxies to additionally emulate non-extensible objects. These proxies keep track of previously exposed non-configurable properties (as in the earlier FixedHandler design), but additionally have an "isExtensible" flag. Once a proxy is fixed, the flag is set to false. Via the return value of the fix() trap, the proxy knows the set of own property names of the non-extensible object it needs to emulate. It then becomes easy for e.g. the getOwnPropertyDescriptor trap to check that |undefined| is returned for any |name| that is not in the set of own properties. Combined with a number of other checks in a handful of other traps, that is how the ES5.1 invariants w.r.t. [[Extensible]] are enforced.</div>
<div><br></div><div>As it turns out, once a proxy can emulate both non-configurable properties and non-extensible objects, the ability to emulate sealed and frozen objects falls out naturally.</div><div><br></div><div>The full implementation of such "non-extensible proxies" is here: <<a href="http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/FixedTrappingProxy.js">http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/FixedTrappingProxy.js</a>></div>
<div>The file's comments near the top contain a detailed description of the invariants enforced by this type of proxy.</div><div><br></div><div>I (lightly) tested this implementation on FF6 (the implementation depends on both proxies and WeakMaps). For those interested:</div>
<div>console-based test: <<a href="http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/testFixedTrappingProxy.js">http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/testFixedTrappingProxy.js</a>></div>
<div>browser-based test: <<a href="http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/testFixedTrappingProxy.html">http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/testFixedTrappingProxy.html</a>></div>
<div><br></div><div>Given the intricacy of the invariant checks, this code can use more eyeballs. There's also a number of unresolved TODO's that merit discussion, and some hints on a redesign of the fix() protocol. In particular, Mark and I have been discussing a protocol where a handler can _either_ tell the proxy that it wants to continue trapping, even after being fixed, or that it wants to "become" a regular object as before, with no more overhead for invariant checks. I think this gives handler writers the "best of both worlds" in terms of flexibility and performance.</div>
<div><br></div><div>On a final note: while implementing this I realized that the FixedHandler is a partial self-hosted implementation of the Proxy API in Javascript. That prompted me to go "all the way" and additionally make the normalization on the input arguments and return value of traps explicit, even though that would not have been strictly necessary. My hope is that people may get a better understanding of the interaction between proxies and handlers if they can study a self-hosted implementation of the Proxy API.</div>
<div><br></div><div>Cheers,</div><div>Tom</div>