Proxying Native Objects: Use case

# François REMY (13 years ago)

I must admit I didn't follow the whole thread about native element proxyfication but when I left the consensus was that the native element should not be proxied (ie: it would not work like Object.create(...) do not work for them).

I've however a compelling use case that may make some of us rethink the situation.

Let's say I want to implement a polyfill for MapEvent whose definition is as follows:

interface MapEvent : Event {
    getter any item(DOMString name);
}

In case of no-proxy-for-native-objects, I've no way to do it properly because implementing 'getter' requires me to use a Proxy but having a functional event will force me to use a natively-branded element (like document.createEvent("CustomEvent")) and changes its prototype chain to match my needs.

So, I would like to reiterate the need for a proxification of native objects: the proxification would be used in the case of polyfills and it doesn't matter if it's possible to extract information about the underlying element, the only goal of the prolyfill is to implement the getter anyway.

To solve the wrapping/unwrapping, the solution I already talked about would be a good fit:

ProxiedCustomEvent implements CustomEvent
- nativeEvent : CustomEvent
- proxy : ECMAScriptObject

so that if the object has to be returned again later (ie: to a callback) the unwrapping works as expected.

The other option would be to allow to transform any native object into a proxy but I heard it wasn't an approach that did get a lot of love. Maybe we should reconsider it?

ECMAScriptObject

  • object nativeBrand
  • ...

In this case, the wrapping/unwrapping would work exactly as usual, with the small difference that the ECMAScript wrapper of the CustomEvent would be a Proxy. Using symbols, this could be as easy as:

function MapEvent() {
    var e = document.createEvent("CustomEvent");
    e.initCustomEvent(...);

    e[Object.__Prototype__] = MapEvent.prototype;
    e[Object.__Get__] = function getter(name) { ... }
    e[Object.__Has__] = function has(name) { ... }
    ...

    return e;
}

var e = new MapEvent();
e instanceof MapEvent; // true
document.body.dispatchEvent('map', e); // works

Any thought on this? François

# David Bruant (13 years ago)

Le 31/01/2013 16:10, François REMY a écrit :

Hi.

I must admit I didn't follow the whole thread about native element proxyfication but when I left the consensus was that the native element should not be proxied (ie: it would not work like Object.create(...) do not work for them).

Just to clarify, both for Object.create and proxy, your "does not work" means "cannot be put in the DOM tree". The output object still behaves as expected from an ECMAScript point of view. It remain possible to do wrappedNode1.appendChild(wrappedNode2) by unwrapping both wrappedNode under the hood.

I've however a compelling use case that may make some of us rethink the situation.

Let's say I want to implement a polyfill for MapEvent whose definition is as follows:

 interface MapEvent : Event {
     getter any item(DOMString name);
 }

Out of curiosity, where does MapEvent come from? I can't remember having read about it on any spec and Google isn't helping. As a side note, I hope this is not a new API, because such getters are particularly bad taste. I remember it generated some issues with HTMLCollection. Maybe something about making it inherit from Array and static analysis on websites. I think such a getter notation exists in WebIDL to formalize scars from the past (like HTMLCollection) rather than to be used in new APIs

For this kind of API, I largely prefer the CustomEvent approach where the (unique!) "detail" field is by-spec expected to be a "go crazy" type of field. The "detail" attribute also doesn't collide with any existing field; in the MapEvent case, a getter could shadow an inherited property.

In case of no-proxy-for-native-objects, I've no way to do it properly because implementing 'getter' requires me to use a Proxy but having a functional event will force me to use a natively-branded element (like document.createEvent("CustomEvent")) and changes its prototype chain to match my needs.

The conclusion I was personally at was that every single API will have to decide how it behaves with proxies. We've seen previously that proxies in appendChild were a no-go because browsers traverse the DOM tree for selector matching and putting proxies in the tree would reveal how selector matching is performed, thus ruining hope of making it parallel. However, off top of my head, I don't see a main restriction to call .dispatch with a proxied event as argument. I'm not enough expert of this topic; people who are in how the APIs are used and implemented will better tell whether it's appropriate to accept a proxy.

I'll post to public-script-coord to talk about that.

# François REMY (13 years ago)

Let's say I want to implement a polyfill for MapEvent whose definition is as follows:

interface MapEvent : Event { getter any item(DOMString name); } Out of curiosity, where does MapEvent come from? I can't remember having read about it on any spec and Google isn't helping.

Just to make it clear, it doesn't come from any spec.

I'm currently working on a WebIDL implementation in JavaScript (so: take any piece of WebIDL, generate a stub for it that works as expected) so even if 'getter' isn't that beautiful I still want it to work. We could also envision getter being used as in indexer for array-like types.

The fact is: a polyfill cannot entirely live in the user world because at some point it will have to rely on browser features (dispatchEvent in this case). If a polyfill is not able to trap the [[...]] traps of a 'native-branded' object, it will fail to simulate some of its behavior properly if the polyfilled object involves proxy-like operations.

The goal is not to get the browser use the proxy traps, it's just to have any user code to use the proxy traps like any user code can actually use the expandos made on native objects today.

I think such a getter notation exists in WebIDL to formalize scars from the past (like HTMLCollection) rather than to be used in new APIs

Yes and no. For exemple, something alike is envisionned to support custom properties in CSS. Something like:

element.style.myCustomProperty = true;
// set the my-custom-property custom CSS property

For this kind of API, I largely prefer the CustomEvent approach where the (unique!) "detail" field is by-spec expected to be a "go crazy" type of field. The "detail" attribute also doesn't collide with any existing field; in the MapEvent case, a getter could shadow an inherited property.

I agree nobody would ever do that. That was just a minimal sample that used getter and a native brand at the same time. We could imagine more realistic and more complex patterns, that would just make the discussion more complex ;-)

However, off top of my head, I don't see a main restriction to call .dispatch with a proxied event as argument. I'm not enough expert of this topic; people who are in how the APIs are used and implemented will better tell whether it's appropriate to accept a proxy.

I'll post to public-script-coord to talk about that.

Thanks, that's a good idea. But you didn't comment on the possibility to simply turn any object into a proxy using

obj[Object.__Get__] = function getter() {} 

or

function wrap(obj) { 
    var old = Object.getProxy(obj); 
    Object.setProxy(obj, {...});
}

To me, it remains the best option when you work on native objects. You get proxy-traps-expandos in the JS world and no impact in the native world.

François

# David Bruant (13 years ago)

Le 31/01/2013 17:26, François REMY a écrit :

I think such a getter notation exists in WebIDL to formalize scars from the past (like HTMLCollection) rather than to be used in new APIs Yes and no. For exemple, something alike is envisionned to support custom properties in CSS. Something like:

 element.style.myCustomProperty = true;
 // set the my-custom-property custom CSS property

How is this not future-hostile? Do you have a link to where people are discussing this?

However, off top of my head, I don't see a main restriction to call .dispatch with a proxied event as argument. I'm not enough expert of this topic; people who are in how the APIs are used and implemented will better tell whether it's appropriate to accept a proxy.

I'll post to public-script-coord to talk about that. Thanks, that's a good idea. But you didn't comment on the possibility to simply turn any object into a proxy using

Indeed, sorry. Turning any object into a proxy is a no-go, because it means that things are aren't observed suddenly have to be observed and that anyone can insert arbitrary code in the middle of any operation on any object. It's pretty bad both for security and probably for performance too.

I think having dispatchEvent accepting proxies is the easiest thing to do in your case.

If creating proxies for exotic objects become a real thing, maybe each exotic type can define its own reduced version of proxies like:

 document.createProxiedElement('string', proxiedElementHandler)

The power proxiedElementHandler could be arbitrarily reduced by the spec of createProxiedElement. It could allow creating reduced "proxies" that can be inserted in the DOM without providing full abusive power than have the bad consequences for selector matching. It's possible in theory. In practice, I don't believe it'll happen :

# François REMY (13 years ago)

I think such a getter notation exists in WebIDL to formalize scars from the past (like HTMLCollection) rather than to be used in new APIs Yes and no. For exemple, something alike is envisionned to support custom properties in CSS. Something like:

element.style.myCustomProperty = true; // set the my-custom-property custom CSS property How is this not future-hostile?

Custom properties are guarenteed to start with a prefix (EWCG sports 'my' and Tab sports 'var' but in concept this is identical) so that the getter only get in the way if the property name starts by that prefix. That prefix being secured for author additions only, it's impossible that any conflict will happen in the future.

Do you have a link to where people are discussing this?

Feel free to comment on www-style, but if you want Tab's editor draft, it's here:

www.w3.org/TR/css-variables/#cssstyledeclaration-interface The most natural way seems to be to first, set up a getter behavior on the interface that deals with variable properties, and second, set up a vars map that exposes the variable properties that aren't set to their initial value.

FWIW, I support both additions, given the author prefix.

Turning any object into a proxy is a no-go, because it means that things are aren't observed suddenly have to be observed and that anyone can insert arbitrary code in the middle of any operation on any object. It's pretty bad both for security and probably for performance too.

Yet you can already freeze an object in the middle of any operation. But I agree turning an object into a proxy is a bit more complex.

Would it be acceptable to instead just add an equivalent to getNoSuchThing and setNoSuchThing to native objects? It would, in fact, solve most of the envisioned use cases and is maybe more lightweight from an implementation point of view.

I think having dispatchEvent accepting proxies is the easiest thing to do in your case.

The problem is that in the case of polyfilling you can't be sure the browser will think about proxy usage when they first implement the API then you're out of luck.

If creating proxies for exotic objects become a real thing, maybe each exotic type can define its own reduced version of proxies like:

document.createProxiedElement('string', proxiedElementHandler)

The power proxiedElementHandler could be arbitrarily reduced by the spec of createProxiedElement. It could allow creating reduced "proxies" that can be inserted in the DOM without providing full abusive power than have the bad consequences for selector matching. It's possible in theory. In practice, I don't believe it'll happen :-s

That would satisfy even more objectives but as you said it put a lot of burden on the implementor side, not sure it's gonna happen anytime soon. But that's an approach worth pursuing in the long term if some cases really have to rely on it.

# David Bruant (13 years ago)

Le 31/01/2013 18:34, François REMY a écrit :

I think such a getter notation exists in WebIDL to formalize scars from the past (like HTMLCollection) rather than to be used in new APIs Yes and no. For exemple, something alike is envisionned to support custom properties in CSS. Something like:

element.style.myCustomProperty = true; // set the my-custom-property custom CSS property How is this not future-hostile? Custom properties are guarenteed to start with a prefix (EWCG sports 'my' and Tab sports 'var' but in concept this is identical) so that the getter only get in the way if the property name starts by that prefix. That prefix being secured for author additions only, it's impossible that any conflict will happen in the future.

Ok, good to know. Thanks :-)

Do you have a link to where people are discussing this? Feel free to comment on www-style, but if you want Tab's editor draft, it's here:

www.w3.org/TR/css-variables/#cssstyledeclaration-interface The most natural way seems to be to first, set up a getter behavior on the interface that deals with variable properties, and second, set up a vars map that exposes the variable properties that aren't set to their initial value.

FWIW, I support both additions, given the author prefix.

In this instance, it's possible for you as a polyfill author to replace Element.prototype.style by your own getter which returns your special proxy objects which do what you expect on property set.

For the dispatchEvent/addEventListener case, it's possible for you to override these methods (and maybe the Proxy constructor?) to accept proxies the way you want them to.

I think web browsers implementing proxies are/will be sufficiently WebIDL compliant to make such a polyfill not that hard to write.

# François REMY (13 years ago)

In this instance, it's possible for you as a polyfill author to replace Element.prototype.style by your own getter which returns your special proxy objects which do what you expect on property set.

For the style case, it's maybe possible to do so (in a WebIDL compatible browser at least, not sure it would work on Chrome for example).

For the dispatchEvent/addEventListener case, it's possible for you to override these methods (and maybe the Proxy constructor?) to accept proxies the way you want them to.

How would I do so? It seems impossible to me, or at least very tedious. Do not forget that the browser itself will add event handlers on objects via Web Components, Decorators, HTML Attributes, ...

Being able to catch all cases seems very improbable.

# David Bruant (13 years ago)

Le 31/01/2013 19:12, François REMY a écrit :

In this instance, it's possible for you as a polyfill author to replace Element.prototype.style by your own getter which returns your special proxy objects which do what you expect on property set. For the style case, it's maybe possible to do so (in a WebIDL compatible browser at least, not sure it would work on Chrome for example).

hmm... we're sidetracking a bit but Chrome doesn't have proxies, so you can't polyfill what you want anyway. By the time Chrome does have proxies, maybe its WebIDL conformance will be better.

For the dispatchEvent/addEventListener case, it's possible for you to override these methods (and maybe the Proxy constructor?) to accept proxies the way you want them to. How would I do so? It seems impossible to me, or at least very tedious. Do not forget that the browser itself will add event handlers on objects via Web Components, Decorators, HTML Attributes, ...

I'm not familiar with this. Do you have links to spec saying that the browser adds native event handlers?

A point I hadn't answered:

The problem is that in the case of polyfilling you can't be sure the browser will think about proxy usage when they first implement the API then you're out of luck.

The best that can be done here is contact the DOM Core people (that's where events are now apparently), and ask them to specifically say they accept proxies in dispatchEvent. Then, write test cases, file (or fix) bugs in browsers in case some tests fails. Nothing better can be done I think.

# François REMY (13 years ago)

Do you have links to spec saying that the browser adds native event handlers?

Here you are: dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html#events-in-decorators

The best that can be done here is contact the DOM Core people (that's where events are now apparently), and ask them to specifically say they accept proxies in dispatchEvent. Then, write test cases, file (or fix) bugs in browsers in case some tests fails. Nothing better can be done I think.

The problem is that you generally need a polyfill for older browsers, not the next ones.

Is a kind of noSuchProperty really impossible to support for native objects? Given the fact you didn't respond I guess this is a no, but is there any valid reason you won't allow it? Performance-wise Gecko already supports noSuchMethod so a properly standardized solution should be possible, right?

I'm a bit under the impression that proxies were created because they were more powerful than noSucXXX, and now I'm under the impression that we end up in a situation that's much less powerful in the case of polyfills... and polyfills were one of the very good reasons to support proxies in the first case.

This lead me to mixed feelings.

# Tom Van Cutsem (13 years ago)

2013/1/31 François REMY <francois.remy.dev at outlook.com>

Is a kind of noSuchProperty really impossible to support for native objects? Given the fact you didn't respond I guess this is a no, but is there any valid reason you won't allow it? Performance-wise Gecko already supports noSuchMethod so a properly standardized solution should be possible, right?

Hi François,

I don't know if native objects have a mutable [[Prototype]] (by assigning proto) but if they do, then one solution might be to have your native objects inherit from a proxy. The proxy should then be able to intercept all missing properties, as I once sketched: < esdiscuss/2011-December/018834>

I'm a bit under the impression that proxies were created because they were more powerful than noSucXXX, and now I'm under the impression that we end up in a situation that's much less powerful in the case of polyfills... and polyfills were one of the very good reasons to support proxies in the first case.

As David argued, turning arbitrary existing objects into proxies is a no-go. The main reason why Proxies gained any traction in TC39 in the first place was that they introduce a new, distinct object type, without impacting the semantics or performance of existing objects.

# François REMY (13 years ago)

I don't know if native objects have a mutable [[Prototype]] (by assigning proto) but if they do, then one solution might be to have your native objects inherit from a proxy. The proxy should then be able to intercept all missing properties, as I once sketched: esdiscuss/2011-December/018834

Good idea. Should I feel stupid for not having found the solution myself or be happy to have asked the question anyway? Or both :-)

Thank you Tom, I guess it is suitable to solve my use case.

# Tom Van Cutsem (13 years ago)

2013/2/4 François REMY <francois.remy.dev at outlook.com>

I don't know if native objects have a mutable [[Prototype]] (by assigning proto) but if they do, then one solution might be to have your native objects inherit from a proxy. The proxy should then be able to intercept all missing properties, as I once sketched: esdiscuss/2011-December/018834

Good idea. Should I feel stupid for not having found the solution myself or be happy to have asked the question anyway? Or both :-)

Definitely don't feel stupid :)

Thank you Tom, I guess it is suitable to solve my use case.

Glad to hear this solution would work for you.