[Harmony Proxies] Non-extensible, sealed and frozen Proxies
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
> 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
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
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
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
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
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
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
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.
More information about the es-discuss