[Harmony proxies] Thoughts on the almost-dead "receiver" argument

# David Bruant (14 years ago)

I recently filed a bug on Firefox [1] regarding the fact that i find the prototype chain of event objects to be messy (half of the problem is Firefox specific, the other half is standard as per WebIDL). In this page [2], click anywhere to see a click event prototype chain (top-left object is the own layer, then, in reading order, its prototype, then its prototype, etc. Initial screen is the global object).

On the prototype chain are all event properties (type, target, eventPhase, etc.) which all appear on the prototype chain and as getter/setter pairs as per WebIDL [3] [4]. In the long term, I wish my vizualisation tool to be used live to take snapshots of an object (and allow to visually compare 2 states of an object at 2 different times, for instance), so I made the choice to not call getters in order to avoid unexpected side effects. This goes with the constraint that in the very case of WebIDL, i can't retrieve the type of the different properties nor the value. Unlike in Chrome which does not follow WebIDL, but provides a better visualization (in my opinion).

The discussion in the bug revealed that the WebIDL choice is made in order to allow composition: "Making the properties live on the prototype allows hooking them on all objects of a given type, which is something web developers commonly want.". Consequently, the properties must remain in the prototype object representing each interface. Given this constraint, it becomes compulsory in pure ECMAScript 5.1 for properties to be setter/getter pairs in order to return a value per object inheriting from the interface-prototype-object (thanks to the |this| binding).

In an ES5.1 + proxies world, it could be imagined that interface-prototype-object could be proxies returning a different /data/ property descriptor based on the "requesting" object. Both responding to the WebIDL constraint and providing the ability to automatically retrieve value and type.

Unfortunatly, there is currently no way to have per-inheriting object behavior as there is no way to pass the inheriting object. There was with the "receiver" argument of get and set traps but it's on its way to be killed. Anyway, it wouldn't be enough. I think that all proto-climbing read traps (getPropertyDescriptor, getPropertyNames, has, get, enumerate) should have access to the requesting/receiver object. It's more than necessary for the use case that i raise, but sounds consistent to me. In accessor properties the receiver object is provided as a |this| binding. This is not possible in proxy traps as |this| is used to denote the handler object. Consequently, an additional argument seems to be the way to go.

David

[1] bugzilla.mozilla.org/show_bug.cgi?id=684696 [2] davidbruant.github.com/ObjectViz [3] www.w3.org/TR/WebIDL/#interface-prototype-object [4] www.w3.org/TR/WebIDL/#es

# Tom Van Cutsem (14 years ago)

Even without a |receiver| parameter, a Proxy used as a prototype could still access the receiver for accessor properties and function-valued data properties (cf. the last code snippet at < strawman:proxy_drop_receiver>)

Of course, it would not work for data properties in general, which seems to be what you need here.

I'm not sure whether this use case is sufficiently convincing to adapt proxies though. Accessor properties seem to be the right tool for this kind of behavior. The problem here is that you don't want your visualization tool to cause side-effects by invoking getters. But that's not a strong guarantee anyway: if your visualization tool encounters a proxy, it will invoke traps which could also cause side-effects. So why not bite the bullet and have your visualization tool invoke the getter?

I do agree that if we would reintroduce |receiver|, we should add it to all proto-climbing traps for consistency.

Cheers, Tom

2011/9/18 David Bruant <david.bruant at labri.fr>

# David Bruant (14 years ago)

Le 23/09/2011 17:45, Tom Van Cutsem a écrit :

Hi David,

Even without a |receiver| parameter, a Proxy used as a prototype could still access the receiver for accessor properties and function-valued data properties (cf. the last code snippet at strawman:proxy_drop_receiver)

True.

Of course, it would not work for data properties in general, which seems to be what you need here.

I'm not sure whether this use case is sufficiently convincing to adapt proxies though. Accessor properties seem to be the right tool for this kind of behavior. The problem here is that you don't want your visualization tool to cause side-effects by invoking getters. But that's not a strong guarantee anyway: if your visualization tool encounters a proxy, it will invoke traps which could also cause side-effects. So why not bite the bullet and have your visualization tool invoke the getter?

I agree and I'm thinking about configuration options for my tool. But the issue was not necessarily only about my tool.

The bigger issue is that currently if A1 and A2 inherits from B, there is a way at property-level for B properties to adapt their behavior to A1 or A2. But there is no way at the object-level (in handlers) to do the same thing. WebIDL shows a use case for such a feature at property-level. And i have the impression that the limitations of this use case require to have an object-level feature.

If not allowing an object-level adaptation, we are stuck to impose accessor properties when expecting a per-instance behavior. The thread starting at [1] shows that WebIDL imposes properties of the global object to be data properties while dom.js authors would prefer them to be allowed to be accessors in order to add them lazily. I admit, this is a different case because we're dealing with the global object which cannot be implemented replaced by a proxy. But this example shows that specs may have constraints on the property descriptor types that may be different from accessor properties.

My intuition is that WebIDL imposes getter/setters since it is the only way in ES5.1 to have a per-instance behavior while intuition would rather be to describe all properties as regular data properties which is what all these getter/setters emulate anyway (see getter and setter definitions at www.w3.org/TR/WebIDL/#es-attributes).

If proxies had this power, the WebIDL spec could be fixed to return data descriptor as, in my opinion, it should.

David

[1] lists.w3.org/Archives/Public/public-script-coord/2011JulSep/0434.html

# Brendan Eich (14 years ago)

On Sep 23, 2011, at 11:18 AM, David Bruant wrote:

My intuition is that WebIDL imposes getter/setters since it is the only way in ES5.1 to have a per-instance behavior while intuition would rather be to describe all properties as regular data properties which is what all these getter/setters emulate anyway (see getter and setter definitions at www.w3.org/TR/WebIDL/#es-attributes).

Really, though, are the WebIDL attributes all accurately representable by data properties without trapping magic that normalizes at least on set? DOM attributes do convert to a target type for the attribute. Data properties can' do that in JS.

# David Bruant (14 years ago)

Le 24/09/2011 22:49, Brendan Eich a écrit :

On Sep 23, 2011, at 11:18 AM, David Bruant wrote:

My intuition is that WebIDL imposes getter/setters since it is the only way in ES5.1 to have a per-instance behavior while intuition would rather be to describe all properties as regular data properties which is what all these getter/setters emulate anyway (see getter and setter definitions at www.w3.org/TR/WebIDL/#es-attributes). Really, though, are the WebIDL attributes all accurately representable by data properties without trapping magic that normalizes at least on set? DOM attributes do convert to a target type for the attribute. Data properties can' do that in JS.

Data properties of regular objects can't. Data properties of proxies could (because they are made up dynamically). But I've been thinking about this more and it wouldn't work. If the programmer ever sets "configurable" to true for a data property on the prototype, all objects would inherit the same (last) value which is not what we'd want. The proxy could reject the non-configurability (but this is hacky) or turn the property as a non-configurable getter/setter (which has more or less no benefit over having a getter/setter pair in the first place). So, my previous wouldn't really solve the problem I mentionned.

Yet, I still think that not having at the object-level the equivalent of what we can do at property-level (with getter/setters changing things based on the receiver object) would be a miss.

# Tom Van Cutsem (14 years ago)

2011/9/24 David Bruant <david.bruant at labri.fr>

Yet, I still think that not having at the object-level the equivalent of what we can do at property-level (with getter/setters changing things based on the receiver object) would be a miss.

Would it really? Javascript data properties are currently simple and easy to understand. In pure JS, it's not possible to have a data property's value depend on the receiver. If you need such a thing, you resort to accessors. The good thing about this is that the "magical" behavior of the property shows up via the Reflection API (getOwnPropertyDescriptor will return an accessor property descriptor), warning the client about the property's behavior.

If proxies would allow data properties to depend on the receiver, that may lead library and/or spec writers away from using accessors, which is not necessarily beneficial. I realize it's a fairly weak argument, since proxies make the behavior of all (configurable) properties magical. Still, proxies currently can't easily emulate receiver-dependent data properties.

Of course, changing the inheritance-related traps to accept an additional receiver argument is not a big deal, so if there are good use cases, why not. So far though, I don't see what is wrong with WebIDL using accessors. It seems accessors are the right tool for the job in this case.

The problematic case would be one where WebIDL would require receiver-dependent data properties.

# David Bruant (14 years ago)

Le 26/09/2011 14:35, Tom Van Cutsem a écrit :

2011/9/24 David Bruant <david.bruant at labri.fr <mailto:david.bruant at labri.fr>>

Yet, I still think that not having at the object-level the
equivalent of
what we can do at property-level (with getter/setters changing things
based on the receiver object) would be a miss.

Would it really? Javascript data properties are currently simple and easy to understand. In pure JS, it's not possible to have a data property's value depend on the receiver. If you need such a thing, you resort to accessors. The good thing about this is that the "magical" behavior of the property shows up via the Reflection API (getOwnPropertyDescriptor will return an accessor property descriptor), warning the client about the property's behavior.

If proxies would allow data properties to depend on the receiver, that may lead library and/or spec writers away from using accessors, which is not necessarily beneficial. I realize it's a fairly weak argument, since proxies make the behavior of all (configurable) properties magical. Still, proxies currently can't easily emulate receiver-dependent data properties.

Of course, changing the inheritance-related traps to accept an additional receiver argument is not a big deal, so if there are good use cases, why not. So far though, I don't see what is wrong with WebIDL using accessors. It seems accessors are the right tool for the job in this case.

The problematic case would be one where WebIDL would require receiver-dependent data properties.

My point was more general than WebIDL, but i agree.

I've been thinking about it more, trying to find examples, relavant use cases (and trying to keep my bad faith as low as possible :-p). I have not found one, but I have come to the conclusion that there is a fundamental difference between a per-instance behavior at property and object level: if you want a per instance object-level behavior, well, just inherit from another object!

There is no equivalent for properties. When several objects inherit from the same object and wish one inherited property acted differently, getter/setters are necessary. If several objects inherit from the same and wish the prototype object acted differently, it could as well be a different object! There is a loss of object identity equality, it doesn't make much sense when the behavior is different per instance.