-
Notifications
You must be signed in to change notification settings - Fork 10
Description
Historical mistake
We made a mistake in the Proxy design (@tvcutsem agrees): The proxy does a [[Get]] on the handler's trap properties each time it wants to trap. Rather, the proxy should have treated the handler object much like an options object, doing all those [[Get]]s up front and remembering these methods internally.
Either way, any user can of course create a taxonomy of partial customizations, using inheritance or copying between partially or fully populated options/handler objects as they wish. This shift would have both pros and cons:
Cons:
- When a proxy invokes one of these methods, what does it pass as the
this-argument?- If it passes the options/handler object itself, then the traps can delegate among each other as normal. But if the options/handler is mutated, then these self sends will diverge from what the proxy is invoking.
- If it passes anything else or nothing, then delegation between traps needs to invent some other pattern beside the normal inheritable self-sends. These other patterns may not be worse --- they may even be better --- but they will be less familiar.
- Not compatible with existing proxies as shipped and as specced in prior specs.
Pros:
- The trapping mechanism can avoid an extra runtime
[[Get]]potentially being a bit faster. - The proxy knows up front which traps are absent, and so can heavily optimize those cases. For example, it can pass the operation directly through to the target without lifting and lowering. Doing these optimizations at proxy construction time amortizes the costs of the optimization itself.
- We could resolve the inheritance-cycle issue Apple raised (need link) with much less loss of transparency.
Cycle checking
Let us say that a non-proxy inherits simply if its [[GetPrototype]] behavior is known to always immediately return its [[Prototype]] or nothing, and do nothing else. An object whose [[GetPrototype]] behavior is not known to do so inherits exotically. All non-exotic objects inherit simply. Only exotic objects can inherits exotically, but most exotic objects inherit simply. These definitions do not care about the behavior of an object's [[SetPrototype]] trap.
A proxy inherits simply iff its target inherits simply and its handler can never provide a getPrototype trap. If the traps were gotten from the handler only once at proxy construction time, this would be a useful distinction. Since both handler and target may be proxies, this definition is recursive on the target. If a proxy inherits simply, then its [[GetPrototype]] behavior either returns the leaf target's [[Prototype]], or nothing if any proxy in the target chain has been revoked. If a proxy inherits simply, then its [[GetPrototype]] behavior does nothing else.
With these definitions, we can change the cycle check to reliably prohibit an inheritance cycle among objects that inherit simply. The cycle check would operate atomically and with no side effects. It would cause no traps. A proxy that wishes to be truly transparent would need to inherit simply, which would not be a burden for almost all uses of proxies. A proxy that inherits exotically thereby opts out of full transparency. The cycle check can sometimes be used to reveal the presence of a proxy that inherits exotically, but it cannot reveal the presence of a proxy that inherits simply.
All Pros and no Cons
Fortunately, because of the invariants, a different change to the spec will give us all the pros, none of the cons, and will likely not break any existing code. We propose to have the proxy inspect the handler at proxy construction time. For a given handler and a given trap name, if any of the following conditions hold:
- Doing
[[GetOwnPropertyDescriptor]]on a that trap name of that handler reveals a non-configurable non-writable data property whose value isundefined. - All of
- Doing
[[GetOwnPropertyDescriptor]]reveals that the property is absent. - Doing
[[IsExtensible]]reveals that the handler may not vary in is answers to[[GetPrototype]]. - (optional: the handler inherits simply so `[[GetPrototype]] cannot have other effects).
- Doing
[[GetPrototype]]on the handler reveals that it inherits either from nothing or from an object that satisfies these same constraints,
- Doing
then we know that a [[Get]] of that trap name on that handler will never return a trapping function. Under these circumstances, the proxy should remember that this trap is absent and not do those [[Get]]s at trapping time. In the case where the handler is a proxy (or possible other exotic objects), this has an observable difference: Doing the [[Get]] could cause other side effects which skipping the [[Get]] does not have. But in neither case could these side effects have caused the [[Get]] to return a trapping function. The absence of these effects under these circumstances likely does not break any existing code.
Cycle checking again
When the trap name in question is getPrototype then the above change becomes more than just a slightly non-transparent optimization. It allows us to distinguish proxies that inherit simply from those that inherit exotically, in order to make exactly the change to cycle detection explained above.
Possible remaining optimizations.
For properties that satisfy the relaxed requirement
- Doing
[[GetOwnPropertyDescriptor]]on a that trap name of that handler reveals a non-configurable non-writable data property. - All of ...same text...
then we know that whatever the current value reported for that trap name, it will always report the same value or nothing. If there are no revocable proxies in the handler chain, then we even know that it will always report the same value, period. As part of this overall change, we might want to allow the proxy to cache these trap functions as well. This isn't much of an optimization since these traps will still happen. But it would allow the proxy to avoid these [[Get]]s at trap time. For high speed operations through simple membranes, even this may be significant.