[Harmony Proxies] Non-extensible, sealed and frozen Proxies

David Flanagan dflanagan at mozilla.com
Wed Sep 7 13:07:51 PDT 2011


On 9/1/11 8:40 AM, Tom Van Cutsem wrote:
> 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.
> 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.
>
Since this thread has started discussing NodeList and other DOM-related 
issues, and since I'm using Proxies to implement the DOM in pure 
JavaScript (see https://github.com/andreasgal/dom.js)  I'll weigh in 
here with a few thoughts.

First though, I'd like to say that Tom's original post in this thread 
was a thing of beauty.  Thanks, Tom, for the clearly articulated preface 
and history!

David Bruant wrote:
> Regarding the open
> issue ("do we default to Object.create or do we want to allow for the
> possibility of Array.create etc.?"), I think that the choice should be
> given to create any sort of object (why not host objects such as
> NodeList?). One question would be: how could this (easily) be achieved?
That seems like a can of worms that shouldn't be opened. I think the 
"become" option for fix() should only be used for objects that are 
sufficiently locked-down that they can be emulated with a plain-old 
object with appropriately set [[Prototype]] and [[Class]].  If I'm using 
a Proxy to emulate Array and its magical length property and 
preventExtensions() is called, I still need magical length behavior 
because properties can be removed.  I suggest that it is not worth 
allowing a proxy to "become" an Array in this case.  The proxy would 
just have to keep trapping so it can continue to provide the desired 
magical behavior.  But if my emulated array is frozen or sealed, then I 
can safely become a plain object with prototype Array.prototype and 
[[Class]] "Array".  It seems to me that if a proxy needs to "become" 
something other than a plain object, then it isn't really fixed, and it 
should keep trapping.

As for [[Class]], I'm not sure if we really need to care about it, or if 
we only really care about the behavior of Object.prototype.toString().   
For my work with dom.js I'm unable to comply with all WebIDL 
requirements because Proxies do not allow me to specify the [[Class]] 
for the proxy.  I only care about the return value of 
Object.prototype.toString(), however, so Allen's proposal (only barely 
articulated here: 
http://wiki.ecmascript.org/doku.php?id=strawman:es5_internal_nominal_typing) 
to allow the return value of O.p.toString() to be set via object 
properties would solve my problem.

In the Array case above, however, I suspect that an array-like Proxy 
that wanted to "become" an Array when frozen or sealed would like 
Array.isArray() to work for it, and so would like to be able to set 
[[Class]].  Allen is proposing to change the way that Array.isArray() 
works, however, so it wouldn't be tied to [[Class]] anymore. If he 
succeeds at that, then I think proxies (as currently defined) would 
never return true when passed to Array.isArray().  And if 
Array.isArray() can never return true for proxy objects, then presumably 
a proxy should not be able to fix() itself to "become" a true array 
object for which Array.isArray() does return true.

If Allen is successful in abolishing [[Class]], then maybe you'll have 
to add Proxy.createArray() along with Proxy.create() and 
Proxy.createFunction()?

If Allen is not successful, then it seems to me that we ought to be able 
to specify a [[Class]] value when calling Proxy.create() and that that 
value ought to be used when the proxy "becomes" a plain object via fix().

This, I think, is what David Bruant proposed when he wrote:

> Indeed, so if a [[Class]] is given to a proxy, it has to be done at 
> proxy creation so that the proxy and its fixed version are stable 
> regarding [[Class]] checks.
>

Tom Van Cutsem wrote:

> How about we adapt ideas from the "<|" proposal? Proxy.create already 
> takes a proto argument. The third argument could say whether the 
> [[Class]] of the proxy should be "Object" or should be the same as 
> that proto. No constructors.
>
> That would probably also allow Object.prototype.toString.call(proxy) 
> to return, e.g. [object Array], which is another outstanding issue.
>
> I can see this working for Object and Array. Functions are covered by 
> function proxies. Do we need to consider other primitives?
>
> I still don't see how it would work out for NodeList or other host 
> objects. Then again, I don't know whether there is a use case that 
> requires this.
WebIDL requires NodeList objects to have a [[Class]] of "NodeList", 
Document object to have a [[Class]] of "Document", and so on.  Of 
course, since [[Class]] is only observable through 
Object.prototype.toString(), Allen's proposal to parameterize 
Object.prototype.toString() would address this, or proxies could address 
this directly.

As for the more fundamental question: is there a use case for a proxy to 
"become" a host object?  I don't think there is much of a use case (at 
least not for DOM objects).  In my work with dom.js, the goal is to 
replace the
native DOM entirely with a new implementation written in JS.  If it 
works, then my proxy-based NodeList implementation would be the only one 
there is: there wouldn't be any other kind of NodeList for it to become.

Or, consider the case of a proxy that forwards to a native DOM Element 
object.  When frozen, it wouldn't make sense for it to "become" a new 
Element object because that new object wouldn't appear in the correct 
position in the document tree.

Furthermore, the question of what the fix() method of a NodeList proxy 
should do is getting ahead of the DOM itself.  I don't think the 
behavior of DOM interfaces have been defined when preventExtensions, 
seal, or freeze() are called on them.  A fundamental feature of 
NodeList, for example, is that it is usually "live".   So if we're 
allowed to freeze a NodeList, we must either break the liveness contract 
and turn it into a static snapshot, or we must make the node from which 
the nodelist was derived non-modifiable.  This is unexpected 
action-at-a-distance, and the DOM doesn't even have a well-defined 
notion of readonly nodes, so neither option is really viable.  For now, 
at least, I suspect that trying to freeze a nodelist should just throw 
an exception.

David Bruant asked:
> Would it even be possible to have an "hybrid" DOM tree with native DOM 
> Node and emulated DOM Node?
DOM Level 3 tried to define ways for document nodes to be shared between 
implementations, I think.  But that was really Java and XML stuff, and 
it has been dropped from the DOM Core spec.  I don't think that anyone 
has a goal to allow a proxy-based implementation of Element, for 
example, to be inserted into a native Document.  My understanding is 
that that is never going to work.  So the Proxy spec does not need to 
work to enable that.

     David Flanagan


More information about the es-discuss mailing list