direct_proxies "problem"

# Andrea Giammarchi (13 years ago)

So, I am playing with FF 18 and I have this behavior:

var a = new Proxy([], {}); console.log(a instanceof Array); // true console.log(Array.isArray(a)); // true console.log({}.toString.call(a));// [object Array] Function.apply(null, a); // anonymous()

Cool uh? there's no way to tell that a is not actually an array but rather a proxy: awesome!!!

Now I go in that dark place called DOM:

var n = new Proxy(document.createElement("p"), {}); console.log(n instanceof HTMLElement);// true console.log({}.toString.call(n)); // true document.body.appendChild(n); // Error: Could not convert JavaScript argument arg 0 [nsIDOMHTMLBodyElement.appendChild]

Is this meant? 'cause it looks lik ewe have half power here and once again inconsistencies ... thanks for explaining me this.

br

# François REMY (13 years ago)

This is a known problem called Object identity. We already have this problem today with Object.create(document.createElement("p")) which is an "instanceof HTMLElement" but is invalid for any DOM purpose.

However, if you take the same P element and set its proto to null, it will still be a valid P element for DOM purposes even though it's no an "instanceof HTMLElement" anymore. I don't know is this behavior is specced anywhere, but this is how it works.

Maybe it would be great to have a way to "confer" the object identify to another object, and it could be useful for polyfill use cases. Until now, it's not possible to create an object whose native object identity is not "neutral". Proxies don't escape to this rule...

# Andrea Giammarchi (13 years ago)

never even tried that since cloneNode() has been good enough but Proxies are more powerful than "just inheritance" so, as it is now, we are trapped for the DOM while Object.observe(), if I remember correctly, was reacting/working with DOM too plus makes object somehow "proxified" or better "observed".

I assume we have no solutions now and no solution is discussed, am I correct?

# Brandon Benvie (13 years ago)

Since proxies always have a target, it would be simple enough for the DOM internally to use the proxy target. As to whether that's going to happen, I guess that would be up to implementers.

# Tab Atkins Jr. (13 years ago)

On Tue, Jan 8, 2013 at 12:40 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

So, I am playing with FF 18 and I have this behavior:

var a = new Proxy([], {}); console.log(a instanceof Array); // true console.log(Array.isArray(a)); // true console.log({}.toString.call(a));// [object Array]

Function.apply(null, a); // anonymous()

Cool uh? there's no way to tell that a is not actually an array but rather a proxy: awesome!!!

Now I go in that dark place called DOM:

var n = new Proxy(document.createElement("p"), {}); console.log(n instanceof HTMLElement);// true console.log({}.toString.call(n)); // true document.body.appendChild(n); // Error: Could not convert JavaScript argument arg 0 [nsIDOMHTMLBodyElement.appendChild]

Is this meant? 'cause it looks lik ewe have half power here and once again inconsistencies ... thanks for explaining me this.

As Francois points out, this is a known problem. DOM objects don't live solely in JS - they tend to have C++ backing classes that do a lot of the heavy lifting. Your average JS object, without such C++ support, simply won't work with the JS methods that, when they call back into C++, expect to see the DOM C++ classes.

This is a problem elsewhere, too - Web Components really wants to make it easy to subclass DOM objects, but we've had to twist ourselves into knots to do it in a way that alerts the C++ side "early enough" that it can create the new objects with appropriate backing C++ classes.

It can potentially be solved by efforts like moving the DOM into JS, which browsers are pursuing to various degrees, but it's a hard problem on the impl side. Meanwhile, there is unforgeable magic behind DOM objects, and nothing we can really do about it.

# François REMY (13 years ago)

It's certainly possible. I think the point is that we would like to get this specced instead? Or at least, can we get the TC39-committee do find an (informative) agreement on the issue if it doesn't fit in a spec?

# Andrea Giammarchi (13 years ago)

so you are saying that Object.observe() does not suffer these problems ? Or is just much simpler than Proxies ? ( yeah, I know, I guess both ... )

# Tab Atkins Jr. (13 years ago)

On Tue, Jan 8, 2013 at 1:30 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

so you are saying that Object.observe() does not suffer these problems ? Or is just much simpler than Proxies ? ( yeah, I know, I guess both ... )

I believe it just wouldn't suffer the same problems - it needs to observe the JS-visible stuff, which DOM objects expose normally, so it can just hook those. Alternately, it can be specialized to handle DOM stuff correctly even with their craziness. I'm not familiar with the implementations of it.

# David Bruant (13 years ago)

Le 08/01/2013 21:40, Andrea Giammarchi a écrit :

So, I am playing with FF 18 and I have this behavior: var a = new Proxy([], {}); console.log(a instanceof Array); // true console.log(Array.isArray(a)); // true console.log({}.toString.call(a));// [object Array]

Function.apply(null, a); // anonymous() Cool uh? there's no way to tell that a is not actually an array but rather a proxy: awesome!!! Now I go in that dark place called DOM: var n = new Proxy(document.createElement("p"), {}); console.log(n instanceof HTMLElement);// true console.log({}.toString.call(n)); // true

really, "true" for toString?

document.body.appendChild(n); // Error: Could not convert JavaScript argument arg 0 [nsIDOMHTMLBodyElement.appendChild]

Is this meant? 'cause it looks lik ewe have half power here and once again inconsistencies ... thanks for explaining me this.

I don't remember when, but we had brief an exchange about it with Tom on es-discuss. I'm in favor for direct proxies to work as replacement of browser objects when they wrap it (Tom was against IIRC, but i can't remember the reason). It wasn't conceivable in the previous design, but the direct proxy design technically allow it.

Reading bugmail, following how implementations support WebIDL (bridge between ECMAScript and browser objects), I doubt it would be possible to see that happening in the short term. For this to happen would require a huge amount of spec work (define every single web spec algorithm in terms of ECMAScript object internal operations) and implementation obviously as Tab explained.

One idea would be in the short term that implementations reliably always throw when trying to wrap non-ECMAScript objects and play with them as if they were legit browser objects. If the need becomes strong, implementation can later allow it (turning an error into a legit thing is usually not web breaking)

# Erik Arvidsson (13 years ago)

On Tue, Jan 8, 2013 at 4:30 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

so you are saying that Object.observe() does not suffer these problems ? Or is just much simpler than Proxies ? ( yeah, I know, I guess both ... )

Object.observe is different, it works on at [[Get]], [[Put]], [[DefineProperty]] level.

In WebKit/V8, the DOM object is represented by a normal JS object with has some internal pointers to the C++ object it wraps. Simply speaking the implementation does not know how to unwrap a proxy to the underlying C++ object.

more below

On Tue, Jan 8, 2013 at 1:23 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

It can potentially be solved by efforts like moving the DOM into JS, which browsers are pursuing to various degrees, but it's a hard problem on the impl side. Meanwhile, there is unforgeable magic behind DOM objects, and nothing we can really do about it.

We could potentially switch from magic to private symbols but DOM bindings are highly optimized and I don't think we could afford making these use symbols. (Right now the pointer is stored at a statically know offset of the js object.)

-- erik

# Erik Arvidsson (13 years ago)

On Tue, Jan 8, 2013 at 4:46 PM, David Bruant <bruant.d at gmail.com> wrote:

One idea would be in the short term that implementations reliably always throw when trying to wrap non-ECMAScript objects and play with them as if they were legit browser objects. If the need becomes strong, implementation can later allow it (turning an error into a legit thing is usually not web breaking)

At least in V8/JSC for WebKit DOM objects are real ECMAScript objects.

# Andrea Giammarchi (13 years ago)

ooops, that's a typo, the toString is HTMLParagraphElement or the correct one.

The problem I have here is with identity indeed where this is "same as target" in JS world and "something else" behind the scene in C++ world ... this is bad because we cannot recognize proxies and we cannot then avoid Errors or problems with automated code.

We think is a DOM node, everything tell us is a DOM node, when we use it everything breaks? this is going nowhere ... I'd rather prefer errors during initialization if the target object cannot be shadowed by the proxy, no matter if real ECMA or fake ECMA :D

br

# François REMY (13 years ago)

We could potentially switch from magic to private symbols but DOM bindings are highly optimized and I don't think we could afford making these use symbols. (Right now the pointer is stored at a statically know offset of the js object.)

Can we use both systems in parallel? In case you hit the "invalid cast" case, you can check if you received a Proxy. If you received a proxy, you retry the cast using its target instead. This seems reasonable: you keep the very-fast-path for the most common case, but polyfills authors accept to pay a (small price) when they use a proxy.

Do you think this would be in the "acceptable" domain for you?

# Allen Wirfs-Brock (13 years ago)

On Jan 8, 2013, at 1:23 PM, Tab Atkins Jr. wrote:

This is a problem elsewhere, too - Web Components really wants to make it easy to subclass DOM objects, but we've had to twist ourselves into knots to do it in a way that alerts the C++ side "early enough" that it can create the new objects with appropriate backing C++ classes.

It can potentially be solved by efforts like moving the DOM into JS, which browsers are pursuing to various degrees, but it's a hard problem on the impl side. Meanwhile, there is unforgeable magic behind DOM objects, and nothing we can really do about it.

~TJ

This is solved in the current ES6 draft by splitting (within the [[Construct]] internal method) object creation in to a two step process. First it calls the @@create method of the constructor. This method is responsible for actually allocating and pre-formatting the new object. A @@create method can allocate special object representations (if it knows how) or preinitialize properties of the newly allocated object to magic values, such as a brand tag. Only after this is completed is the actual constructor code called on the newly allocated instance.

When subclassing something like Array or a DOM object you would typically not over-ride @@create but just inherit it from the "built-in" superclass. Your subclass then gets the special object representation and all the inherited special methods will work for it.

# Boris Zbarsky (13 years ago)

François REMY wrote:

In case you hit the "invalid cast" case, you can check if you received a Proxy. If you received a proxy, you retry the cast using its target instead.

For what it's worth, this is exactly how Gecko's WebIDL bindings work. We use something much like a Proxy to enforce cross-window security membranes, so a call to a WebIDL binding method may in fact have a "this" object or arguments that are not actual WebIDL objects but wrappers for them. This is a rare case, so we do exactly what you describe: check for a WebIDL object, then if what we have is not a WebIDL object check for a security wrapper and unwrap it as needed (the unwrapping performs a security check, which can of course fail).

So we could in fact add support for something like scripted Proxies for WebIDL objects without too much trouble in Gecko, I think. Using them would deoptimize things, of course, not only because of the extra code after the "WebIDL object" check fails, but also because it would force runtime typechecks on "this", as opposed to compile-time ones, at all callsites that used Proxies as the this object. That would not be that big a hit compared to the other things going on with Proxies, though, or so I suspect.

-Boris

P.S. Please cc me on replies; I'm not subscribed to this list. I was just pointed to this thread.

P.P.S. WebIDL objects are also normal ECMAScript objects in Gecko, except the ones that aren't because they're actually "proxies" without a target object. JSC uses objects with a custom getOwnPropertyDescriptor for similar purposes, last I looked. That said, clearly we have a way to test whether a given object is a WebIDL object, and so do other implementations.

# Brendan Eich (13 years ago)

Boris and I talked more 1:1 -- it is not clear when a direct proxy can be safely "cast" to its target. The internal proxies Gecko uses are known implementations where this is safe (with a security check). An arbitrary scripted direct proxy? Not safe in general, and stripping the proxy to its target may break the abstraction of that certain scripted proxy.

# Boris Zbarsky (13 years ago)

On 1/9/13 12:54 AM, Brendan Eich wrote:

Boris and I talked more 1:1 -- it is not clear when a direct proxy can be safely "cast" to its target. The internal proxies Gecko uses are known implementations where this is safe (with a security check).

And the reason it's safe is that when you then get the object back out again via WebIDL APIs we'll make sure to wrap it in the appropriate security membrane on the way out as needed. That's obviously not something we can easily do for scripted proxies.

And specifically, it's not clear what the behavior should be when there are two different scripted proxies for the same WebIDL object. Say it's a DOM node. One of the proxies gets passed to appendChild. When later getting that node out of the DOM with .firstChild, what should be handed back? The proxy that was passed in, the JS object that proxy was wrapping, something else (e.g. an exception is thrown)?

# David Bruant (13 years ago)

Le 08/01/2013 22:59, Erik Arvidsson a écrit :

On Tue, Jan 8, 2013 at 4:46 PM, David Bruant <bruant.d at gmail.com <mailto:bruant.d at gmail.com>> wrote:

One idea would be in the short term that implementations reliably
*always* throw when trying to wrap non-ECMAScript objects and play
with them as if they were legit browser objects. If the need
becomes strong, implementation can later allow it (turning an
error into a legit thing is usually not web breaking)

At least in V8/JSC for WebKit DOM objects are real ECMAScript objects.

I meant "non-ECMAScript standard objects" when I wrote "non-ECMAScript object". Sorry for the confusion.

# David Bruant (13 years ago)

Le 09/01/2013 06:57, Boris Zbarsky a écrit :

And specifically, it's not clear what the behavior should be when there are two different scripted proxies for the same WebIDL object.
Say it's a DOM node. One of the proxies gets passed to appendChild.
When later getting that node out of the DOM with .firstChild, what should be handed back? The proxy that was passed in, the JS object that proxy was wrapping, something else (e.g. an exception is thrown)?

The principle of least surprise would say the proxy that was passed in (at least for the sake of object identity checking). Proxies are supposed to be used as a replacement of their target. Also if you wrap untrusted code in a membrane [1], you don't want this untrusted code to be handed the actual target, but the wrapped object (so the proxy).

Regardless of the answer for the specific point you bring up, there are hundreds of such questions to be answered before proxies can wrap browser objects. W3C specs are nowhere near being ready to explain what would happen if a proxy was passed as argument of a method.

Proxies expose the guts of algorithms applied on objects (exact sequence of internal methods with arguments). It is true for ECMAScript standard objects, it would be equally true for browser objects. Since these guts aren't specified in ECMAScript semantics, exposing the guts now would lead at best to non-interoperable behaviors, at worst to security issues (due to C++ proximity).

I agree with Andreas about the convenience for web developers [2] but I doubt it would be practical to have it in the short term both because of under-specification and implementation complexity. Let's wait for a combinaison of 1) authors using proxies, 2) implementors move forward on WebIDL compliance and 3) proxies being introduced in the spec world (WindowProxy, live objects...). When these 3 aspects will have moved forward enough, maybe it will be time to think about wrapping browser objects, but now, none of the 3 populations seem in a mature enough state for this to happen.

David

[1] soft.vub.ac.be/~tvcutsem/invokedynamic/js-membranes [2] I regularly meet developers who aren't aware of the ECMAScript/Browser objects divide and call all that JavaScript.

# Andrea Giammarchi (13 years ago)

all this reminds me, hilariously, the Object.defineProperty case in Internet Explorer 8 ... the infamous implementation that works only on global and DOM objects. Here we have the opposite behavior, something so powerful that could let us redefine W3C specs or eventually make them consistent across browsers where every time something is needed we don't even need to wait for specs to be approved (I know, this might be the beginning of the Chaos too) will land in Web development world half-backed and with known inconsistencies able to break normal code.

So, at least, I would expect an error when the new Proxy is not able to replace the identity of the first argument and not after, because after is simply too late and I think completely pointless.

Just my 2 cents.

br

# David Bruant (13 years ago)

Le 09/01/2013 14:55, Andrea Giammarchi a écrit :

all this reminds me, hilariously, the Object.defineProperty case in Internet Explorer 8 ... the infamous implementation that works only on global and DOM objects. Here we have the opposite behavior, something so powerful that could let us redefine W3C specs or eventually make them consistent across browsers where every time something is needed we don't even need to wait for specs to be approved (I know, this might be the beginning of the Chaos too) will land in Web development world half-backed and with known inconsistencies able to break normal code.

With the shadow target idea, you could make a huge amount of things consistent across browsers I think. It costs twice as much objects and I don't know for time performance, but at least, you have the cross browser.

So, at least, I would expect an error when the new Proxy is not able to replace the identity of the first argument and not after, because after is simply too late and I think completely pointless.

I strongly agree with this idea. Either the object can be safely wrapped and it is or if it cannot, throw on the Proxy constructor call.

# Boris Zbarsky (13 years ago)

On 1/9/13 5:24 AM, David Bruant wrote:

When later getting that node out of the DOM with .firstChild, what should be handed back? The proxy that was passed in, the JS object that proxy was wrapping, something else (e.g. an exception is thrown)? The principle of least surprise would say the proxy that was passed in

That's actually not that great either. If you're handing out proxies as membranes, and the caller of .firstChild should be getting a different membrane than the caller of appendChild had, you lose.

Also if you wrap untrusted code in a membrane [1], you don't want this untrusted code to be handed the actual target, but the wrapped object (so the proxy).

If you want membranes you have to be able to pick the right membrane when handing out the object from any WebIDL method/getter, basically.

Proxies expose the guts of algorithms applied on objects (exact sequence of internal methods with arguments).

Yes, true, for purposes of ES stuff.

it would be equally true for browser objects

I'm not quite sure what this part means.

Since these guts aren't specified in ECMAScript semantics

Again, not sure what that means.

The way the DOM works in practice right now, if one were to implement it in ES, is that each script-exposed object is just a normal ES object with some getters/setters/methods on its proto chain. There is also a bunch of state that's not stored in the objects themselves, and a Map or WeakMap from the objects to their state, depending on the implementation; the GC issues are a bit complicated. The getters/setters/methods work on this out-of-band state, for the most part (there are some exceptions; e.g. dev.w3.org/2006/webapi/WebIDL/#dfn-attribute-setter for the [PutForwards] case, though that may not match UA behavior well enough; we'll see).

So in this view, passing in a proxy should not work, because it can't be used to look up the out-of-band state.

Now if you happen to have access to engine-level APIs you can unwrap the proxy and use the target to index into the Map... but at that point I agree that you've left ES semantics land.

Now maybe you're arguing that the above model is wrong and there should be some other model here. I welcome you describing what that model is. But the above model, I believe, is fully describable in ES semantics (and in fact dom.js does exist).

I agree with Andreas about the convenience for web developers [2] but I doubt it would be practical to have it in the short term both because of under-specification and implementation complexity.

Agreed, at this point.

Let's wait for a combinaison of 1) authors using proxies, 2) implementors move forward on WebIDL compliance

Of course the more investors invest in a rewrite of their DOM stuff, the less likely they are to want to change it.

So if we think we should be changing things somehow in the future, and have a good idea of what those changes will look like, the better it is to lay the groundwork now. Rewriting the binding layer for a browser is a pretty massive project, and there's a limit to how often UAs want to do it. ;)

# Andrea Giammarchi (13 years ago)

only part I am "slightly skeptical" is that if developers cannot use proxies with DOM nodes there's really not much to wait for ... they'll not use them, end of the story.

If you mean "waiting for developers to complain same way I did" ... oh well, I think is just a matter of time. I'll be here that day :D

Still, meanwhile, I would just change the current behavior throwing when Proxy cannot work, i.e. with DOM ... or every library will have to try/catch a generic appendChild in their code, as example, because of this misleading possibility to wrap the DOM without the ability to retrieve back the wrapped content.

br

# Alex Russell (13 years ago)

On Tuesday, January 8, 2013, Tab Atkins Jr. wrote:

On Tue, Jan 8, 2013 at 12:40 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com <javascript:;>> wrote:

So, I am playing with FF 18 and I have this behavior:

var a = new Proxy([], {}); console.log(a instanceof Array); // true console.log(Array.isArray(a)); // true console.log({}.toString.call(a));// [object Array]

Function.apply(null, a); // anonymous()

Cool uh? there's no way to tell that a is not actually an array but rather a proxy: awesome!!!

Now I go in that dark place called DOM:

var n = new Proxy(document.createElement("p"), {}); console.log(n instanceof HTMLElement);// true console.log({}.toString.call(n)); // true document.body.appendChild(n); // Error: Could not convert JavaScript argument arg 0 [nsIDOMHTMLBodyElement.appendChild]

Is this meant? 'cause it looks lik ewe have half power here and once again inconsistencies ... thanks for explaining me this.

As Francois points out, this is a known problem. DOM objects don't live solely in JS - they tend to have C++ backing classes that do a lot of the heavy lifting. Your average JS object, without such C++ support, simply won't work with the JS methods that, when they call back into C++, expect to see the DOM C++ classes.

This is a problem elsewhere, too - Web Components really wants to make it easy to subclass DOM objects, but we've had to twist ourselves into knots to do it in a way that alerts the C++ side "early enough" that it can create the new objects with appropriate backing C++ classes.

It can potentially be solved by efforts like moving the DOM into JS, which browsers are pursuing to various degrees, but it's a hard problem on the impl side. Meanwhile, there is unforgeable magic behind DOM objects, and nothing we can really do about it.

Well, we can attempt (as much as is practicable at any point) to avoid "blessing" this split and the crazy behaviors it begets. Designing for DOM-in-JS world is the only way to stay sane. What JS-in-DOM can't do that DOM needs, we should add. But not much more.

# Jason Orendorff (13 years ago)

On Tue, Jan 8, 2013 at 11:54 PM, Brendan Eich <brendan at mozilla.com> wrote:

Boris and I talked more 1:1 -- it is not clear when a direct proxy can be safely "cast" to its target. The internal proxies Gecko uses are known implementations where this is safe (with a security check). An arbitrary scripted direct proxy? Not safe in general, and stripping the proxy to its target may break the abstraction of that certain scripted proxy.

Hard-earned wisdom:

  1. Proxies that form membranes always have wrap/unwrap operations that can be sensibly applied in every situation—except access to an object's implementation details, like internal properties; then it’s unclear.

  2. Proxies that exist only to give a particular object magical behavior should never be "cast to its target".

What 1 and 2 have in common is that only the handler ever sees both the proxy and the target. The main lesson I've drawn from Mozilla's experience with proxies is that this property is crucial.

Without it, you get a mess that's impossible to reason about. The symptoms are a proliferation of special cases where something must be wrapped or unwrapped with a lengthy comment explaining why; code where a particular variable could be either a target or a proxy; and related bugs due to the code naively assuming one or the other.

The default direct proxy you get with an empty handler does not have this property; my hard-earned lesson predicts that we would therefore have a lot of trouble figuring out exactly what that could usefully do, and in fact we have had trouble. (It's not the only source of trouble; access to object implementation details are trouble too.)

# François REMY (13 years ago)

When later getting that node out of the DOM with .firstChild, what should be handed back? The proxy that was passed in, the JS object that proxy was wrapping, something else (e.g. an exception is thrown)? The principle of least surprise would say the proxy that was passed in

That's actually not that great either. If you're handing out proxies as membranes, and the caller of .firstChild should be getting a different membrane than the caller of appendChild had, you lose.

I'm not sure I got your idea, but I maybe did. Okay, let's take a simple example, then.

Let's say we have a <P> element and a membrane around it whose goal is to only allow you to change textContent (but you can read everything else, if you want to). When you read something and that the return value is an object, it's wrapped in a membrane where you can't change anything (that way if you take el.parentElement.firstChild you get a second, more restrictive membrane for el).

What you firstly claim is that the membrane is completely ineffective if you can retrieve the object via the DOM in any other form than the membrane you used in first hand because as soon as you added the element in the DOM, you can use document.getElementById(el.uniqueID) to retrieve it unprotected (or you can use something else).So, whatever membrane was used should continue to be used. From now one, any DOM API will need to return this "readonly except textContent" version to preserve compatibilty.

What you claimed in second position is that even if you do that, it's still a broken design. Indeed, let's say you sent the readonly version to a function to make sure it doesn't modify the object. Again, if you added the element in the DOM, it can retrieve it and the function will recieve the other membrane, which allow him to change textContent.

However, this second problem is not related to the DOM but to globally accessible variables. If you maintain an object stored somewhere in the globally accessible world (be it the DOM or any kind of global variable), even if you create a membrane around those objects, the user can retrieve an unsecure version of it via the Map. No membrane system can be secure through global variable access, so we should not worry about that.

According to me, the only thing we want to make sure with a Proxy is that you can't actually extract the value of a DOMObject that's not added nor addable to any globally accessible map (like a CustomEvent). If you can, by creating an element and calling dispatchEvent on that element using the membrane and get the unmembraned CustomEvent in the event listener, then we've a problem.

I think it should be possible to work around this problem by making sure every object has a C++ Decorator (in this case ProxiedCustomEvent) which inherits from the base class (CustomEvent) and forward all calls to the extracted object identity but has a method like "getScriptValue" that returns the membrane from which the native value was extracted.

When a native method is called with a Proxy, a new ProxiedCustomEvent(proxy.target, proxy) is created and passed to the native method. When the ProxiedCustomEvent is given back to the script world, his getScriptValue method is called to return the original proxy.

So, basically, el.appendChild(membrane) will cause el.lastChild===membrane to be true, and dispatchEvent(membranedEvent) will not allow to retrieve an unmembraned event object.

Albeit possible, this is quite a bit of work, however. In the mean time, we should probably make it impossible to proxy an host object whose object identity rely on something else that the prototype chain. That means that we should probably get this concept of "object identity" specced somewhere.

The question in this case should be: can I create a "secure but broken" readonly proxy from a DOM object that cannot be used as parameter for native functions (if we allow a Proxy to take the native object identity of the target when used in native calls OR in the mean time if we make the proxyfication throw)?

Yes: you can create a new, empty target with no object identity and create getters/setters to encapsulate the real DOM object. Actually, you didn't remove any ability by allow proxies to take the object identity.

However, if the Proxy cannot possibly use the identity of the target object in native calls, you'll not be able to emulate it. So, if we want to use the most capable solution, we need a way to "transfer" target -> proxy identity, and that means we need to "throw as not impelemented" the proxification of native objects in the mean time.

The only problem I still see with the ProxiedXXX decorator approach is that, normally, when you are using a proxy, you can "control" the value returned by some property calls (let's say you pretend innerHTML is "" while this is not true) but in the "native" world, your barrier will not apply and therefore one can get the innerHTML by using a DOMSerializer. If we want to use the proxy's own innerHTML we need to reveal to it the actual algorithm applied on the object and perform a lot of casting and typecheck. This is not ideal.

I would certainly understand if the ECMAScript group settled up not to work on Proxied native elements and specify that it should throw on creation. However, I would advise to create an Object.hasIdentity(...) method that returns true if the given object has a native identity (and can therefore not be proxied nor deep-cloned). Because, when you think about it, I can deep-clone any "JS" object and except that o!===o2 they will be the same and work in the same contexts, but this is not true for DOM objects, because the "clone" will not have a valid object identity.

We already leak that info, maybe it's time to acknowledge it in a spec.

# Boris Zbarsky (13 years ago)

On 1/9/13 11:30 AM, François REMY wrote:

However, this second problem is not related to the DOM but to globally accessible variables.

Well, right, but the DOM sort of forces the problem on us because of window.document.

No membrane system can be secure through global variable access, so we should not worry about that.

Actually, a membrane system can be secure through global variable access. You just have to make sure that all accesses are performed via the membrane and that you know for any accessor what the right membrane is.

An existence proof are the membranes Gecko+SpiderMonkey uses for security checks. But those require that any time you're returning an object from anywhere you check who your caller is and what membrane they should be getting....

According to me, the only thing we want to make sure with a Proxy is that you can't actually extract the value of a DOMObject that's not added nor addable to any globally accessible map (like a CustomEvent). If you can, by creating an element and calling dispatchEvent on that element using the membrane and get the unmembraned CustomEvent in the event listener, then we've a problem.

OK. So this assumes that all consumers should get the same membrane, right?

When the ProxiedCustomEvent is given back to the script world, his getScriptValue method is called to return the original proxy.

This is doable in theory. I'd have to think about performance impact. One of the goals here from a UA implementor point of view is that there should be no performance hit from supporting this use case if you don't proxy WebIDL objects.

Albeit possible, this is quite a bit of work, however.

Yep.

The only problem I still see with the ProxiedXXX decorator approach is that, normally, when you are using a proxy, you can "control" the value returned by some property calls (let's say you pretend innerHTML is "" while this is not true) but in the "native" world, your barrier will not apply and therefore one can get the innerHTML by using a DOMSerializer.

Yep. Lots of issues like that, actually...

Because, when you think about it, I can deep-clone any "JS" object and except that o!===o2 they will be the same and work in the same contexts

Well, you have to know about the underlying identity of the "JS" object, no?

If I deep-clone (in the sense of copying all own property descriptors and copying the proto chain) a Date onto an Object, I don't think that will work quite right in current implementations, for example. Same for Array. So properly deep-cloning involves detecting cases like that already and creating a clone of the right type...

# David Bruant (13 years ago)

Le 08/01/2013 22:23, Tab Atkins Jr. a écrit :

Meanwhile, there is unforgeable magic behind DOM objects, and nothing we can really do about it.

Do you have examples? Besides document.all being falsy, everything seem to be emulable with ES6 proxies.

# David Bruant (13 years ago)

Le 09/01/2013 16:02, Boris Zbarsky a écrit :

On 1/9/13 5:24 AM, David Bruant wrote:

When later getting that node out of the DOM with .firstChild, what should be handed back? The proxy that was passed in, the JS object that proxy was wrapping, something else (e.g. an exception is thrown)? The principle of least surprise would say the proxy that was passed in

That's actually not that great either. If you're handing out proxies as membranes, and the caller of .firstChild should be getting a different membrane than the caller of appendChild had, you lose.

Also if you wrap untrusted code in a membrane [1], you don't want this untrusted code to be handed the actual target, but the wrapped object (so the proxy).

If you want membranes you have to be able to pick the right membrane when handing out the object from any WebIDL method/getter, basically.

I went out of my way merging 2 incompatible use cases (put a wrapped node in a document and membrane for which the document would be wrapped in the same membrane). Sorry about that.

I still think the object that was introduced is the one that should be handed back. If you give access to some code to a membraned version of the DOM tree, you know whenever they want a given node and can pick the correct membraned node instead (a weakmap can do this job really well.)

it would be equally true for browser objects

I'm not quite sure what this part means.

Since these guts aren't specified in ECMAScript semantics

Again, not sure what that means.

The way the DOM works in practice right now, if one were to implement it in ES, is that each script-exposed object is just a normal ES object with some getters/setters/methods on its proto chain. There is also a bunch of state that's not stored in the objects themselves, and a Map or WeakMap

or private symbols (used to be called "private names")

from the objects to their state, depending on the implementation

There is some data associated with the object. Whether it's a (private) property or a map entry is an implementation detail; my point is that you can't state "a bunch of state that's not stored in the objects themselves". properties or the [[Extensible]] boolean could also not be stored in the objects themselves, that's an implementation concern.

Choosing symbols or a weakmap would make a huge difference in how proxy replacement would react to DOM algorithms. If the DOM in terms of accessing private properties, then proxies can replace DOM objects transparently. Their "unknownPrivateSymbol" trap will be called [2] and if they don't throw, the access to the private property will be transparently forwarded to the target without the private symbol ever leaking (it actually wouldn't need to exist in implementations).

That actually could work...

the GC issues are a bit complicated. The getters/setters/methods work on this out-of-band state, for the most part (there are some exceptions; e.g. dev.w3.org/2006/webapi/WebIDL/#dfn-attribute-setter for the [PutForwards] case, though that may not match UA behavior well enough; we'll see).

So in this view, passing in a proxy should not work, because it can't be used to look up the out-of-band state.

Now if you happen to have access to engine-level APIs you can unwrap the proxy and use the target to index into the Map... but at that point I agree that you've left ES semantics land.

I agree with this analysis.

Now maybe you're arguing that the above model is wrong and there should be some other model here. I welcome you describing what that model is.

That would be representing private state with private symbols properties.

But the above model, I believe, is fully describable in ES semantics

I think so too.

(and in fact dom.js does exist).

Dom.js started and is developed in a world where no engines has implemented symbols. Also, last I checked it adds non-standard convieniences [2] and _properties for "private" state [3][4]. Am I looking at the wrong version?

I agree with Andreas about the convenience for web developers [2] but I doubt it would be practical to have it in the short term both because of under-specification and implementation complexity.

Agreed, at this point.

Let's wait for a combinaison of 1) authors using proxies, 2) implementors move forward on WebIDL compliance

Of course the more investors invest in a rewrite of their DOM stuff, the less likely they are to want to change it.

So if we think we should be changing things somehow in the future, and have a good idea of what those changes will look like, the better it is to lay the groundwork now. Rewriting the binding layer for a browser is a pretty massive project, and there's a limit to how often UAs want to do it. ;)

I hear you :-) Yet, assuming the private symbol idea would be the way to go, there is still a need for specs to define private state in terms of private symbols before implementations can start, no? I guess, worst case scenario, with a lack of spec, implementations would be non-interoperable only in how many times the unknownPrivateSymbol trap is called which I don't think is really a big deal.

I feel I may have been too quick in my explanation. If I have, don't hesitate to ask questions or ask me to resolve a problem or your choice.

David

[1] strawman:proxies_names [2] andreasgal/dom.js/blob/master/src/impl/Node.js#L249 [3] andreasgal/dom.js/blob/master/src/impl/Node.js#L411 [4] andreasgal/dom.js/blob/master/src/impl/Node.js#L422

# Boris Zbarsky (13 years ago)

On 1/9/13 12:13 PM, David Bruant wrote:

There is some data associated with the object. Whether it's a (private) property or a map entry is an implementation detail ... Choosing symbols or a weakmap would make a huge difference in how proxy replacement would react to DOM algorithms.

Then it's not an implementation detail, now is it? I'm having a hard time understanding how you can reconcile those two sentences.

If the DOM in terms of accessing private properties, then proxies can replace DOM objects transparently. Their "unknownPrivateSymbol" trap will be called [2] and if they don't throw, the access to the private property will be transparently forwarded to the target without the private symbol ever leaking (it actually wouldn't need to exist in implementations).

That actually could work...

I'm not sure I see how yet, but I'm not as familiar with proxies as you are. I assume the link above was actually [1]? I'm having a hard time making sense of it, largely due to missing context, I think.

What happens if unknownPrivateSymbol throws? Would internal DOM algorithms like the serialization algorithm invoke unknownPrivateSymbol? If so, would unknownPrivateSymbol be allowed to modify the DOM tree?

That would be representing private state with private symbols properties.

OK, see above.

Dom.js started and is developed in a world where no engines has implemented symbols.

It started in a world with no WeakMap or Map either.

Also, last I checked it adds non-standard convieniences [2] and _properties for "private" state [3][4]. Am I looking at the wrong version?

I believe it adds those on the target object but the thing it hands out to script is actually a proxy for that object.

I'm not sure how it handles "expando" property sets that collide with its _properties, though. But again, it was developed in a world without WeakMap/Map.

Yet, assuming the private symbol idea would be the way to go, there is still a need for specs to define private state in terms of private symbols before implementations can start, no?

Indeed.

implementations would be non-interoperable only in how many times the unknownPrivateSymbol trap is called which I don't think is really a big deal.

Whether it's a big deal depends on when it's called and what it can do. If it can have side-effects, non-interop in how many times it's called is a big deal to me.

# David Bruant (13 years ago)

Le 09/01/2013 18:26, Boris Zbarsky a écrit :

On 1/9/13 12:13 PM, David Bruant wrote:

There is some data associated with the object. Whether it's a (private) property or a map entry is an implementation detail ... Choosing symbols or a weakmap would make a huge difference in how proxy replacement would react to DOM algorithms.

Then it's not an implementation detail, now is it? I'm having a hard time understanding how you can reconcile those two sentences.

I agree I'm confusing here. In the former part, I was trying to refer to implementation (C++) terminology of how you can associate data with an object (answering to the "bunch of state that's not stored in the objects themselves" part). In the latter part, I'm talking about ECMAScript representation of DOM objects and here, it makes a difference.

If the DOM in terms of accessing private properties, then proxies can replace DOM objects transparently. Their "unknownPrivateSymbol" trap will be called [2] and if they don't throw, the access to the private property will be transparently forwarded to the target without the private symbol ever leaking (it actually wouldn't need to exist in implementations).

That actually could work...

I'm not sure I see how yet, but I'm not as familiar with proxies as you are.

I don't think anyone (me including) really took the time to write an example of how unknownPrivateSymbol works either. Let's do that:

 var s1 = new Symbol(true); // true for "private" or something like 

that IIRC var s2 = new Symbol(true); var whitelist = new WeakSet(); whitelist.add(s1); // but we don't add s2!

 var p = new Proxy({}, {
     unknownPrivateSymbol: function(target){
         console.log('unknownPrivateSymbol');
     },
     get: function(target, name){
         console.log('get', target[name], name === s1);
         return target[name];
     },
     set: function(target, name, value){
         console.log('set', name === s1);
         return target[name] = value
     }
 }, whitelist)

 p[s1] = 12; // logs "set true" because s1 is in the whitelisst
 p[s1] // logs "get 12 true" for the same reason

 p[s2] = 37; // logs "unknownPrivateSymbol" and forward the operation
 // to the target because the unknownPrivateSymbol trap didn't throw
 // without calling the set trap.
 console.log(p[s2]);
 // logs "unknownPrivateSymbol", forwards without calling the get 

trap then logs "37"

If the trap always throws, then "p[s2] = 37" fails and anyone who knows both s2 and the target can verify that. If the unknownPrivateSymbol trap is not provided, the proxy just forwards to the target. As the trap signature suggests (only the target is passed), the decision of making the operation succeed or fail needs to be made with very few information. On purpose, in the trap, you have no idea which private name is being accessed on the target, nor the other arguments (like the value in a [[Set]] operation). The only motivating use case is to be able to throw when you've decided to revoke access to the target.

If DOM methods are specced in terms of private symbols, then, replace a DOM object with a corresponding wrapping proxy and any private access can go through the proxy. If the proxy is revoked or for whatever other reason, the proxy can decide to throw anytime (I answer your question about this below).

I assume the link above was actually [1]?

I was refering to [1] indeed.

I'm having a hard time making sense of it, largely due to missing context, I think.

What happens if unknownPrivateSymbol throws?

I'm not sure yet. My guess is that the error should be propagated, but maybe in some cases it would make sense to swallow it. It would be up to spec writers of each sort of object to decide. Maybe WebIDL can settle the case for every object of the web platform. Maybe diffferent objects can have different needs (I don't see a case, but the area is so vast I don't want to close the door)

Would internal DOM algorithms like the serialization algorithm invoke unknownPrivateSymbol?

If the serialization algorithm is represented as a private symbol'ed methods on objects, then, doing a [[Get]] with this symbol as argument would call the unknownPrivateSymbol trap. The result of this trap (throw or return (return value is ignored)) determines whether the algorithm is actually called.

If so, would unknownPrivateSymbol be allowed to modify the DOM tree?

unknownPrivateSymbol is a trap, so I'm not sure I understand your question. Hopefully the above explanation will have clarified things. The trap in itself cannot do anything. It's just a sink that's called when a proxy trap would be called, but a private symbol not in the whitelist is the property name. Best case, it forwards the operation to the target, worst case, it cancels the operation entirely.

implementations would be non-interoperable only in how many times the unknownPrivateSymbol trap is called which I don't think is really a big deal.

Whether it's a big deal depends on when it's called and what it can do. If it can have side-effects, non-interop in how many times it's called is a big deal to me.

The trap in itself can't do anything. The code in it is a function which can't do anything more than what a function can do today. It has access to the target (the actual DOM object). It can have side-effects, the only important case is whether a public method call result in 0 or 1+ calls on a given object. What I meant above (but didn't say) is that whether it's called (0 or 1) is important, but if it's 1 or 5 times for a given public method call, it doesn't matter much.

David

[1] strawman:proxies_names

# Boris Zbarsky (13 years ago)

On 1/9/13 1:23 PM, David Bruant wrote:

I don't think anyone (me including) really took the time to write an example of how unknownPrivateSymbol works either. Let's do that:

Thank you, that helps a lot.

What happens if unknownPrivateSymbol throws? I'm not sure yet. My guess is that the error should be propagated

So just to make sure we're on the same page here... Say I have a proxy for a <div> and I put it in the DOM. Say my page has:

<style> section > div { color: green; } </style>

Should matching that selector against the div call unknownPrivateSymbol when getting the parent of the div? If so, what should it do if it throws? Note, by the way, that UAs are working on doing off-main-thread selector matching and that the exact timing/ordering of selector matching is not defined and won't be (because it's a state thing, not a procedural algorithm), so doing anything script-observable anywhere here is pretty weird.

On the other hand, selector matching allows one to exfiltrate things like which attributes are set and to what values, as well as information about the ancestors and often descendants of the element.

It would be up to spec writers of each sort of object to decide.

OK.

If the serialization algorithm is represented as a private symbol'ed methods on objects, then, doing a [[Get]] with this symbol as argument would call the unknownPrivateSymbol trap. The result of this trap (throw or return (return value is ignored)) determines whether the algorithm is actually called.

That wasn't my point. My point was what happens to the tree traversal the serialization algorithm does if the firstChild member (not the getter, the actual internal state that stores the first child) is defined to be a private symbol?

unknownPrivateSymbol is a trap, so I'm not sure I understand your question.

My question boils down to this: are we talking about introducing things that would be able to modify a DOM tree while it's being iterated by internal browser algorithms? Because doing that is not acceptable.

It sounds like so far the answer is "maybe, depending on how those traversals are defined in the specs".

The trap in itself cannot do anything.

On the contrary, it can do anything any JS function on the page can do.

Best case, it forwards the operation to the target, worst case, it cancels the operation entirely.

No, worst case it calls document.open() or window.close() or spins the event loop via showModalDialog or any of a number of other interesting things script can do.

The trap in itself can't do anything. The code in it is a function which can't do anything more than what a function can do today.

That's a really interesting definition of "can't do anything".

Functions today can totally destroy the world (as in, the entire web page they're running in). The only saving grace is that they can only be invoked at particular safe points when you allow the world to be destroyed.

Any proposal that would allow random functions to be invoked in too many cases is dead in the water from a security and performance standpoint.

It can have side-effects, the only important case is whether a public method call result in 0 or 1+ calls on a given object.

Uh... no. How can that be the only important case???

What I meant above (but didn't say) is that whether it's called (0 or 1) is important, but if it's 1 or 5 times for a given public method call, it doesn't matter much.

For a function with side-effects this seems blatantly false to me, so I must be missing something. What am I mising?

# David Bruant (13 years ago)

Le 09/01/2013 19:43, Boris Zbarsky a écrit :

On 1/9/13 1:23 PM, David Bruant wrote:

What happens if unknownPrivateSymbol throws? I'm not sure yet. My guess is that the error should be propagated

So just to make sure we're on the same page here... Say I have a proxy for a <div> and I put it in the DOM. Say my page has:

<style> section > div { color: green; } </style>

Should matching that selector against the div call unknownPrivateSymbol when getting the parent of the div?

Debattable. Here, there is no need to work with private state. The nodeType and tagName and parentNode (all public) are enough to do the matching I think. So the unknownPrivateSymbol trap wouldn't be called, but the get trap would. But the public properties could also be reflecting the state of private properties.

If so, what should it do if it throws?

I guess swallow in that case. But maybe forward for qS/qSA... or swallow and consider the element as non-matching. I don't know what's the most useful.

Note, by the way, that UAs are working on doing off-main-thread selector matching and that the exact timing/ordering of selector matching is not defined and won't be (because it's a state thing, not a procedural algorithm), so doing anything script-observable anywhere here is pretty weird.

Agreed. Maybe this point settles the argument. That's what I was referring to when I was talking about "exposing guts" of DOM objects. The downside is that it forces to define a lot of things as algorithms at the expense of optimizations like the one you describe.

If the serialization algorithm is represented as a private symbol'ed methods on objects, then, doing a [[Get]] with this symbol as argument would call the unknownPrivateSymbol trap. The result of this trap (throw or return (return value is ignored)) determines whether the algorithm is actually called.

That wasn't my point. My point was what happens to the tree traversal the serialization algorithm does if the firstChild member (not the getter, the actual internal state that stores the first child) is defined to be a private symbol?

oh ok, I'm not familiar with this algorithm. If the firstChild is a private symbol, then the unknownPrivateSymbol trap would be called. If the public firstChild is called, the get trap is.

unknownPrivateSymbol is a trap, so I'm not sure I understand your question.

My question boils down to this: are we talking about introducing things that would be able to modify a DOM tree while it's being iterated by internal browser algorithms? Because doing that is not acceptable.

It sounds like so far the answer is "maybe, depending on how those traversals are defined in the specs"

Yes, depending on how they are defined, but pretty much anytime you touch a proxy, it calls a trap either the unknownPrivateSymbol or the get trap. Imagine a proxy for which the unknownPrivateSymbol and get traps would add a new element anywhere randomly to the DOM tree. I agree it'd be attrocious! You've convinced me against proxies for DOM Nodes. It could still make sense to wrap a DOM Node with a proxy to perform [[Get]] and [[Set]], etc. but definitely not put it in the DOM tree.

Now, the web platform defines a lot of other objects for which wrapping them with a proxy could make sense. I guess it would need to be on a case-by-case basis.

It can have side-effects, the only important case is whether a public method call result in 0 or 1+ calls on a given object.

Uh... no. How can that be the only important case???

What I meant above (but didn't say) is that whether it's called (0 or 1) is important, but if it's 1 or 5 times for a given public method call, it doesn't matter much.

For a function with side-effects this seems blatantly false to me, so I must be missing something. What am I mising?

I hadn't thought of cases like selector matching. I was thinking of function calls like appendChild that could be considered as "atomic" you call it once and whatever happens in the middle (the number of trap calls), in the end, the operation happened or not and trap call side-effects probably don't affect the appendChild algorithm. It actually depends on how it's exactly specified.

# Boris Zbarsky (13 years ago)

On 1/9/13 2:45 PM, David Bruant wrote:

Debattable. Here, there is no need to work with private state. The nodeType and tagName and parentNode (all public) are enough to do the matching I think.

No, because script can override them but matching needs to not depend on that, right?

So the unknownPrivateSymbol trap wouldn't be called, but the get trap would. But the public properties could also be reflecting the state of private properties.

I'm confused again. The "public" properties can do anything they want, since script can redefien them.

If so, what should it do if it throws? I guess swallow in that case. But maybe forward for qS/qSA... or swallow and consider the element as non-matching. I don't know what's the most useful.

What's most useful is not invoking any script at all for selector matching. Note that the main consumer of selector matching is not qS/qSA but CSS rendering.

That wasn't my point. My point was what happens to the tree traversal the serialization algorithm does if the firstChild member (not the getter, the actual internal state that stores the first child) is defined to be a private symbol? oh ok, I'm not familiar with this algorithm. If the firstChild is a private symbol, then the unknownPrivateSymbol trap would be called. If the public firstChild is called, the get trap is.

What happens right now is that private state is consulted that cannot be changed by script directly and which can be accessed with no side-effects.

Yes, depending on how they are defined, but pretty much anytime you touch a proxy, it calls a trap either the unknownPrivateSymbol or the get trap.

OK. I doubt that's acceptable for internal algorithms like serialization, fwiw.

Imagine a proxy for which the unknownPrivateSymbol and get traps would add a new element anywhere randomly to the DOM tree.

Yes, exactly. Done that already. ;)

Now, the web platform defines a lot of other objects for which wrapping them with a proxy could make sense. I guess it would need to be on a case-by-case basis.

OK. That might make sense; we'd have to look at specific cases.

# Andrea Giammarchi (13 years ago)

David ? You said: "It could still make sense to wrap a DOM Node with a proxy to perform [[Get]] and [[Set]], etc. but definitely not put it in the DOM tree."

so this is the current scenario ... now, can you explain me a single case where you would need that? If you can't use the DOM node then why would create a proxy of one of them ? I thought you agreed on the fact new Proxy() should throw instantly if target cannot be proxified ... in any case, here a counter example of what you guys are discussing:

var o = {}, p = new Proxy(o, { get: function (target, name) { console.log(name); return target[name]; } }) ;

p.test = 123; p.test; // log test o.test; // does nothing

At this point would be more like deciding if DOM should threat internally the "o" or the "p"

Having "special privileges" it could use o directly and pass back p when it comes to JS world. This will preserve performance. At the same time this will make the usage of proxies in the DOM world less useful because developers will be able to intercept only user defined interactions with these nodes but hey, it's already better than now where developers can create DOM proxies and use them only in RAM for who knows which reason 'cause in the DOM, where these identities belong, these fail.

As summary, as it is now, this from Francois is my favorite outcome from the discussion:

I would certainly understand if the ECMAScript group settled up not to work on Proxied native elements and specify that it should throw on creation. However, I would advise to create an Object.hasIdentity(...) method that returns true if the given object has a native identity

br

# Andrea Giammarchi (13 years ago)

Last, but not least, even offline DOM proxies are pointless, here another example without putting them in the DOM

var p = new Proxy( document.createElement("p"), {} ); try { p.appendChild( document.createElement("span") ); } catch(o_O) { console.log(o_O.message); } try { p.appendChild( new Proxy( document.createElement("span"), {} ) ); } catch(o_O) { console.log(o_O.message); }

So, as it is right now, there is not a single reason to make them valid as new Proxy target, IMHO

br

# David Bruant (13 years ago)

Le 09/01/2013 21:30, Andrea Giammarchi a écrit :

David ? You said: "It could still make sense to wrap a DOM Node with a proxy to perform [[Get]] and [[Set]], etc. but definitely not put it in the DOM tree."

so this is the current scenario ... now, can you explain me a single case where you would need that? If you can't use the DOM node then why would create a proxy of one of them ?

If you wrap DOM Nodes in a proxy, you can do a membrane around them and make things like wrappedNode1.appendChild(wrappedNode2) work without needing shadow targets (just unwrapped in the traps, perform the native action and return a wrapped result) It sounds like a worthwhile use case.

I thought you agreed on the fact new Proxy() should throw instantly if target cannot be proxified ...

For the short term I did (but now that the scope is reduced, I'm not sure). For the long term, I expected DOM objects could be proxified. Boris convinced me otherwise for DOM Nodes at least.

in any case, here a counter example of what you guys are discussing: var o = {}, p = new Proxy(o, { get: function (target, name) { console.log(name); return target[name]; } }) ;

p.test = 123; p.test; // log test

o.test; // does nothing At this point would be more like deciding if DOM should threat internally the "o" or the "p" Having "special privileges" it could use o directly and pass back p when it comes to JS world. This will preserve performance. At the same time this will make the usage of proxies in the DOM world less useful because developers will be able to intercept only user defined interactions with these nodes but hey, it's already better than now where developers can create DOM proxies and use them only in RAM for who knows which reason 'cause in the DOM, where these identities belong, these fail.

I don't understand that part. Especially given that you're dealing with normal objects. What is it a counter-example of (we've been discussing about a lot of things)? How is it a counter-example?

As summary, as it is now, this from Francois is my favorite outcome from the discussion:

I would certainly understand if the ECMAScript group settled up not to work on Proxied native elements and specify that it should throw on creation. However, I would advise to create an Object.hasIdentity(...) method that returns true if the given object has a native identity

I missed that part. What's a "native identity"? a non-proxy object? It's been decided early that a Proxy.isProxy method should be avoided, because proxies should be indetectable from the objects they try to emulate from the ECMAScript perspective (internal [[Get]]/[[DefineOwnProperty]]/[[Keys]]...). Boris has exposed a case (selector-matching) where the DOM API is complex enough that a proxy wrapping a DOM object could be discriminated against a native DOM object. This is a good trade-off I think. Allowing proxies to give the impression that the DOM object acts correctly from the ECMAScript internal properties perspective, but doesn't from the DOM perspective.

# David Bruant (13 years ago)

Le 09/01/2013 21:57, Andrea Giammarchi a écrit :

Last, but not least, even offline DOM proxies are pointless, here another example without putting them in the DOM

What's an "offline DOM Proxy"?

var p = new Proxy( document.createElement("p"),

{} ); try { p.appendChild(

That's what you call "without putting them in the DOM"? ;-) What are you going to do when your proxy node has a child? Probably put it on the DOM eventually, I guess, no?

 document.createElement("span")

); } catch(o_O) { console.log(o_O.message); } try { p.appendChild( new Proxy( document.createElement("span"),

   {}
 )

); } catch(o_O) { console.log(o_O.message); } So, as it is right now, there is not a single reason to make them valid as new Proxy target, IMHO

Membrane and avoiding the cost of the shadow target sounds like a good enough.

# Andrea Giammarchi (13 years ago)

do you have any example of what you are saying? all my examples fail and I don't understand other use cases.

As you said, if a Proxy should be undetectable a broken Proxy that cannot be used is a pointless object full of inconsistency in the environment, IMHO.

Is this the decision then? Let the new Proxy accept any target even if the target cannot be "proxied" ?

# Andrea Giammarchi (13 years ago)

forgto this: yes, offline DOM node means a DOM that is not part of the live DOM tree ... that is, indeed, disconnected, offline, no CSS, no repaint, no reflow, nothing ... is offline. Isn't this term good enough? I thought that makes sense

# David Bruant (13 years ago)

Le 09/01/2013 23:57, Andrea Giammarchi a écrit :

do you have any example of what you are saying? all my examples fail and I don't understand other use cases.

I don't. You seem to be relying a lot on the Firefox implementation, but there are 2 things to be aware of as far as I know (sorry I didn't bring that up earlier):

  1. its use with non-ECMAScript standard objects hasn't been thought out too much, so shouldn't be taken as authoritative
  2. the implementation is not complete [1]. The "depends on" list of the bug contains bug regarding missing traps, misbehaviors regarding invariants and a couple of other things.

Given 1), all what I've been talking about is a discussion on how proxies should behave and more importantly, what different specs should be saying about this. I haven't even tried anything I suggested because I was aware the current Firefox implementation cannot be considered up to spec.

Before we start any conversation on whether Firefox is making a good decision of shipping proxies in the arguably incomplete state that they are, I'd like to remind this warning that can be read on the MDN page almost since day 1 of the doc page [2]: "Warning: The SpiderMonkey Proxy implementation is a prototype and the Proxy API and semantics specifications are unstable. The SpiderMonkey implementation may not reflect the latest specification draft. It is subject to change anytime. It is provided as an experimental feature. Do not rely on it for production code."

I've just added a note in the Firefox 18 for developers page in that direction too.

As you said, if a Proxy should be undetectable

I wrote "undetectable from the objects they try to emulate from the ECMAScript perspective (internal [[Get]]/[[DefineOwnProperty]]/[[Keys]]...)." Whether an non-standard API decides to accept proxies in its internal methods is a different story.

a broken Proxy that cannot be used is a pointless object full of inconsistency in the environment, IMHO. Is this the decision then? Let the new Proxy accept any target even if the target cannot be "proxied" ?

I guess it all depends on what one means by "cannot be used" and "cannot be proxied". If what you expect is things like wrappedNode1.appendChild(wrappedNode2) to work if the wrapped nodes are part of the same membrane, that could be made functional (unwrap under the hood, perform the native action and return wrapped objects when applicable). If you want to use the Reflect API on them (what I call "from the ECMAScript perspective"), that could work too. And I think that makes a self-consistent useful set of things. If you want something that can be appended to the DOM (or used with the tree manipulation methods), we've clarified that it's not possible. But I don't think it's a good enough reason to prevent proxying altogether.

Now, one major point you're revealing is the lack of specification. Maybe either WebIDL or all spec writers should be made aware of this issue so that they start thinking of whether the algorithms applied on the objects they define would work as well on proxied versions of their objects. Depending on the answer choosing to throw when this |this| or an argument is a proxied version of the object they're defining.

If specs explain this choice, you may still be frustrated, but at least reading the spec will cut the surprise.

David

[1] bugzilla.mozilla.org/show_bug.cgi?id=703537 [2] developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Proxy

# Nathan Wall (13 years ago)

David Bruant wrote:

For the long term, I expected DOM objects could be proxified.  Boris convinced me otherwise for DOM Nodes at least.

Are you so convinced that the unknownPrivateSymbol trap is necessary?

It's been decided early that a Proxy.isProxy method should be avoided,  because proxies should be indetectable from the objects they try to  emulate from the ECMAScript perspective

I thought part of the goal in ES6 was striving to make the DOM emulable (dare I say implementable?) in pure ES. If that's the case, the inability of DOM objects to be proxied sounds like a big deal.

(A) If a proxy is allowed to have a DOM node as its target, but element.appendChild(proxiedNode) throws, then you can detect if an object is a proxied DOM node based on whether it throws when you call appendChild:

function isNodeProxy(node) {         try {             return node instanceof Node &&                 document.body.appendChild(node) &&                 !!document.body.removeChild(node);         } catch(x) {             return false;         }     }

(B) If a proxy isn't allowed to have a DOM node as its target and ES objects have no way to refuse being proxied, then the DOM is allowed to do something ES can't, widening the gap.

A consistent approach might be having some way for an ES object to refuse being proxied, but It still sounds to me like the real problem is the unknownPrivateSymbol trap.

If you take this trap away, wouldn't that let DOM objects be proxied? If you leave this trap there, library authors who want the same level of integrity as the DOM developers want will avoid symbols and try to come up with a WeakMap solution (which will require a bit of legging to work with inheritance I now realize, but I think it can be done).  Who knows, if a good enough WeakMap micro-library is worked out that can come close to simulating symbols without the unknownPrivateSymbol problem, private symbols may even become an anti-pattern. Or security-minded developers might just retreat back into closure-classes. Are there good enough reasons to leave the unknownPrivateSymbol trap at such an expense?

What does unknownPrivateSymbol gain? Someone can always write around it with WeakMaps and closures.  One thing I have read is it could help in the case of a read-only proxy which would throw anytime unknownPrivateSymbol is called with a "set" invocation, but that doesn't really ensure an object is any more read-only than without unknownPrivateSymbol if the object just avoids symbols. If you really want to ensure an object's properties are read-only, I think you need to have the proxy store the first call to the get-related traps to store the value of each property the first time the trap is invoked and ensure that that same value is returned for every subsequent call to the trap for the same property.

In addition, trying to make an object read-only in this way seems to be in conflict with the fact that private symbols are immune to freezing. I assume private symbols are immune to freezing because they represent internal state in the same way that variables inside closures can represent internal state. Why then are we allowing proxies to cancel writes to private symbols it doesn't know about?

Nathan

# Nathan Wall (13 years ago)

function isNodeProxy(node) {        try {            return node instanceof Node &&                document.body.appendChild(node) &&                !!document.body.removeChild(node);        } catch(x) {            return false;        }    }

Excuse me, I got the logic wrong:

function isNodeProxy(node) {         try {             return node instanceof Node &&                 document.body.appendChild(node) &&                 !document.body.removeChild(node);         } catch(x) {             return true;         }     }

# Brendan Eich (13 years ago)

Jason Orendorff wrote:

On Tue, Jan 8, 2013 at 11:54 PM, Brendan Eich <brendan at mozilla.com <mailto:brendan at mozilla.com>> wrote:

Boris and I talked more 1:1 -- it is not clear when a direct proxy
can be safely "cast" to its target. The internal proxies Gecko
uses are known implementations where this is safe (with a security
check). An arbitrary scripted direct proxy? Not safe in general,
and stripping the proxy to its target may break the abstraction of
that certain scripted proxy.

Hard-earned wisdom:

  1. Proxies that form membranes always have wrap/unwrap operations that can be sensibly applied in every situation—except access to an object's implementation details, like internal properties; then it’s unclear.

  2. Proxies that exist only to give a particular object magical behavior should never be "cast to its target".

What 1 and 2 have in common is that only the handler ever sees both the proxy and the target. The main lesson I've drawn from Mozilla's experience with proxies is that this property is crucial.

I buy this analysis.

Still doesn't mean direct proxies are a net loss from the original target-free API. Direct proxies solved the non-configurable issue and simplified by eliminating function proxies as distinct at create-time. And one can use direct proxies as if they were indirect, with a dummy target and some work.

My old bones are aching in ways that make me think we could use two kinds of APIs. Notification proxies were suggested last fall. We have limited time for ES6 to call this, though.

Without it, you get a mess that's impossible to reason about. The symptoms are a proliferation of special cases where something must be wrapped or unwrapped with a lengthy comment explaining why; code where a particular variable could be either a target or a proxy; and related bugs due to the code naively assuming one or the other.

Beyond the wrap/unwrap special casing, it sounds like proxied abstractions are leaking. Is this accurate and useful? Abstractions leak, utopia not an option....

The default direct proxy you get with an empty handler does not have this property; my hard-earned lesson predicts that we would therefore have a lot of trouble figuring out exactly what that could usefully do, and in fact we have had trouble. (It's not the only source of trouble; access to object implementation details are trouble too.) (Implementation details => abstraction leaks for sure, no?)

# David Bruant (13 years ago)

Le 11/01/2013 06:46, Nathan Wall a écrit :

David Bruant wrote:

For the long term, I expected DOM objects could be proxified. Boris convinced me otherwise for DOM Nodes at least. Are you so convinced that the unknownPrivateSymbol trap is necessary?

I would think so, for instance, when one wants to revoke access to a given object, one needs to be able to throw and for that a trap must be called also when the private symbol is unknown, this is a defensive scenario too. Imagine the following scenario: Untrusted code A is given access to a graph of objects through a membrane. Likewise for B with a different membrane. If both A and B have access to the same target, each through a proxy of their respective membrane. Now, you want to revoke access to all their membrane (including the shared target) to both A and B, because you don't want A and B to be able to communicate anymore.

If there is no unknownPrivateSymbol trap, A and B can continue to communicate through the private property on the shared target (through their respective proxy to it) and you have no way to prevent that. Even freezing the object wouldn't work apparently.

It's been decided early that a Proxy.isProxy method should be avoided, because proxies should be indetectable from the objects they try to emulate from the ECMAScript perspective I thought part of the goal in ES6 was striving to make the DOM emulable (dare I say implementable?) in pure ES.

I'm working for this to happen and it seems to be shared by TC39 (which I'm not part of)

If that's the case, the inability of DOM objects to be proxied sounds like a big deal.

(A) If a proxy is allowed to have a DOM node as its target, but element.appendChild(proxiedNode) throws, then you can detect if an object is a proxied DOM node based on whether it throws when you call appendChild:

 function isNodeProxy(node) {
     try {
         return node instanceof Node &&
             document.body.appendChild(node) &&
             !!document.body.removeChild(node);
     } catch(x) {
         return false;
     }
 }

Leaking the information that the node is actually a proxy is due to a DOM API issue (which is way way too late to fix obviously). If you want proxied node to go in the tree, you'll need to find an answer to the issue Boris Zbarsky brought up about selector matching [1]. I've given up [2], but I'm curious to know if there is a decent solution.

You're however not saying how this leak may be a problem for self-hosting (emulating in pure ES) the DOM. I'm addressing that after answering to your B.

(B) If a proxy isn't allowed to have a DOM node as its target and ES objects have no way to refuse being proxied, then the DOM is allowed to do something ES can't, widening the gap.

I'm in favor of allowing nodes to be used as proxy targets and having the Reflect API [3] work on them as expected, yet forbidding their use in DOM tree manipulation methods.

Assuming nodes can be proxied and proxied node are refused to be appended. How can this be implemented in pure ES? All non-proxy nodes are created internally (via HTML parsing) or through document.createElement (are there others?), so, the DOM can keep a list of the exact objects belonging to the document and refuse appending any other object (discriminated with object identity) including proxied nodes.

A consistent approach might be having some way for an ES object to refuse being proxied, but It still sounds to me like the real problem is the unknownPrivateSymbol trap.

If you take this trap away, wouldn't that let DOM objects be proxied? If you leave this trap there, library authors who want the same level of integrity as the DOM developers want will avoid symbols and try to come up with a WeakMap solution (which will require a bit of legging to work with inheritance I now realize, but I think it can be done). Who knows, if a good enough WeakMap micro-library is worked out that can come close to simulating symbols without the unknownPrivateSymbol problem, private symbols may even become an anti-pattern. Or security-minded developers might just retreat back into closure-classes. Are there good enough reasons to leave the unknownPrivateSymbol trap at such an expense?

I set up a use case above. If you have an alternative, I'm listening.

What does unknownPrivateSymbol gain? Someone can always write around it with WeakMaps and closures.

In the above setup, if A and B shared a weakmap, the weakmap had to go through the membrane before being shared and at least one of the 2 has a membraned version, so its access will be cut when the membrane is revoked.

Thinking a bit more about how A and B can share things, I'm realizing that anytime they want to share a private symbol, they have to do it through a "capturable" channel (set trap value argument, etc.). So if A and B are in touch, the membrane could capture all private symbols going from one side to the other and add it to the membrane whitelist. This way, anytime there is a communication attempt via private symbols, the private symbol is known and the non-unknownPrivateSymbol trap is called. In theory, it makes the unknownPrivateSymbol not absolutely necessary for this use case. In practice, to capture all communication, anytime the membranes intercept a new symbol, it needs to make sure it wasn't used on any other object previously shared between both parties and that means traversing entire graphs of objects which may be an enormous amount of work degrading performance. I'm also not a big fan of retroffiting control afterwards.

One thing I have read is it could help in the case of a read-only proxy which would throw anytime unknownPrivateSymbol is called with a "set" invocation, but that doesn't really ensure an object is any more read-only than without unknownPrivateSymbol if the object just avoids symbols.

I think I suggested that to promote the idea of passing the original operation as a second argument of the unknownPrivateSymbol trap. It's not been agreed on. One main use case for proxies is using them as intermediary. If there is a circumstance (like private symbol properties) where the proxy can be entirely bypassed, then, that use case fails to be fulfilled.

David

[1] esdiscuss/2013-January/027952 [2] esdiscuss/2013-January/027955 [3] harmony:reflect_api

# Tom Van Cutsem (13 years ago)

2013/1/11 Nathan Wall <nathan.wall at live.com>

I thought part of the goal in ES6 was striving to make the DOM emulable (dare I say implementable?) in pure ES. If that's the case, the inability of DOM objects to be proxied sounds like a big deal.

I think we are missing an important point in this whole "Proxy the DOM" discussion.

The way I have always thought of how "emulating the DOM using proxies" would work is as follows:

  1. a DOM-in-JS library (like dom.js) exports an API that is an exact mirror copy of the DOM API.
  2. some of these exported methods may create and return proxies that emulate particular DOM objects.
  3. application code that uses this library only ever interacts with these emulated DOM objects via the DOM emulation library's API. In other words, it never would try to pass an emulated DOM node into the actual DOM. Doing that is just a plain type error.

The cases where I think libraries like dom.js make sense is:

  • in an environment that doesn't have a real DOM at all (e.g. NodeJS)
  • in a sandboxed environment (like SES) where the sandboxed code cannot ever access the real DOM.

In neither of these cases, the problem comes up.

In general, when you're emulating a system, it is almost never the case that emulated objects must run on the real system directly. The real system doesn't know about the emulation's implementation layer, so it can't "interpret" the emulated object.

Choosing to automatically unwrap the proxy and pass in the wrapped DOM node is dangerous. As Brendan mentioned, it breaks the proxy's abstraction. In that regard, I agree with Jason that it's best to think of a proxy's "target" as an encapsulated, internal piece of proxy state that should never leak. At best, external code can use the target to derive some simple information (like typeof does on direct proxies).

The suggestion of allowing proxies to hook into the deeper DOM protocols and intercept inner calls (via symbols or however else you want to do that) seems to me a terrible idea. It seems at least some people here agree.

To summarize, to me, emulating the DOM is as much about proxying DOM objects as it is about wrapping the actual DOM functions (like appendChild). It's the job of the wrapped functions to recognize their proxies, unwrap them, and pass their unwrapped counterpart into the real DOM.

# Andrea Giammarchi (13 years ago)

All good except it's impossible/not meant to recognize Proxies in JS world?

# Allen Wirfs-Brock (13 years ago)

On Jan 12, 2013, at 9:36 AM, Andrea Giammarchi wrote:

All good except it's impossible/not meant to recognize Proxies in JS world?

Note that Tom said: "recognize their proxies" (emphasis added). If it needs to, a membrane or any other proxy issuing subsystem should be able to recognize its own proxies. It can, for example, tag them using a private symbol or keep them in a weak map. You don't want to recognize other subsystems proxies as proxies. They're just objects to you.

Allemn

# Brendan Eich (13 years ago)

Allen Wirfs-Brock wrote:

All good except it's impossible/not meant to recognize Proxies in JS world?

Note that Tom said: "recognize /their/ proxies" (emphasis added). If it needs to, a membrane or any other proxy issuing subsystem should be able to recognize its own proxies. It can, for example, tag them using a private symbol or keep them in a weak map. You don't want to recognize other subsystems proxies as proxies. They're just objects to you.

Critical point -- same as if we ignore proxies and have two separate systems whose objects must satisfy certain constraints that are internal to each system. Yes, APIs for these systems should be more structurally typed, IMHO -- but sometimes you need high integrity and performance. In such cases, what amounts to nominal types or brands come into play. There may be no universal domain of discourse for objects in a given super-system.

Wishing the DOM were entirely self-hosted and "open-box" is just not realistic. As Boris points out, CSS selector matching must not trigger traps or getters, and must be parallelizable.

# Nathan Wall (13 years ago)

Hey Tom, I think you missed my point. The point was that in David and Boris' discussion they agreed that the DOM should throw if it is given a Proxy in appendChild. That sounded to me like it presumed that the DOM had a magical ability to distinguish between DOM objects and their proxies. That seemed to contradict the point David made that Proxies should not be distinguishable from their targets (e.g. the no Proxy.isProxy decision). If real DOM had a way to determine if something was a proxy (with a target of a real DOM node) and throw but there was no way for an emulated DOM (in a non-DOM environment) to determine if an object was a proxy with a target to an emulated DOM node or just a plain emulated DOM node, then the DOM has magical abilities that the emulated DOM can't have. That was my concern.

David's reply works, though, I think. He stated that since you have to create DOM nodes using a method of the DOM itself (document.createElement, document.createTextNode), then emulated DOM could track all objects created and compare them against objects passed into appendChild, and since a proxy would have a different object identity from its target, the emulated DOM would have a way to distinguish proxies from their targets.

Nathan

# Nathan Wall (13 years ago)

Nathan wrote:

Hey Tom, I think you missed my point. The point was that in David and Boris' discussion they agreed that the DOM should throw if it is given a Proxy in appendChild. That sounded to me like it presumed that the DOM had a magical ability to distinguish between DOM objects and their proxies. That seemed to contradict the point David made that Proxies should not be distinguishable from their targets (e.g. the no Proxy.isProxy decision). If real DOM had a way to determine if something was a proxy (with a target of a real DOM node) and throw but there was no way for an emulated DOM (in a non-DOM environment) to determine if an object was a proxy with a target to an emulated DOM node or just a plain emulated DOM node, then the DOM has magical abilities that the emulated DOM can't have. That was my concern.

David's reply works, though, I think. He stated that since you have to create DOM nodes using a method of the DOM itself (document.createElement, document.createTextNode), then emulated DOM could track all objects created and compare them against objects passed into appendChild, and since a proxy would have a different object identity from its target, the emulated DOM would have a way to distinguish proxies from their targets.

Nathan

In short, if real DOM can throw when given a proxy of a real DOM node, fake DOM should also be able to throw when given a proxy of a fake DOM node.

# Andrea Giammarchi (13 years ago)

and in both cases you would have ...

Proxy.isDOMProxy = function (node) { try { document.documentElement.appendChild(node); document.documentElement.removeChild(node); return false; } catch(gotcha) { return true; } };

that's why I have talked about inconsistencies all over at the very beginning of this thread :)

br

# Brendan Eich (13 years ago)

This is silly.

I can use the closure pattern to vend objects that do not satisfy the internal constraints the DOM imposes on some of its objects. No proxies in sight.

So why is the method you wrote named "isDOMProxy" and whatever in the world does it have to do with Proxy?

# Andrea Giammarchi (13 years ago)

that is a (quick/imperfect/asexample) way to detect a proxy while I understand these should not be detectable.

Consider that as conditional check after node instanceof Element in order to know if that node is usable or not as DOM element.

There, a way to simulate an isProxy method and it does not matter if that's silly ... is possible

# David Bruant (13 years ago)

Le 12/01/2013 21:58, Andrea Giammarchi a écrit :

that is a (quick/imperfect/asexample) way to detect a proxy while I understand these should not be detectable. {} and [] also return true to your test, but they're not proxies. You cannot use an imperfect test to prove a point. As I (and others now) said, the DOM, in order to be self-consistent (and have performance in operations like selector matching) can only use objects it created through HTML parsing or methods like document.createElement. It will refuse any other object, proxy or not. With tree manipulation methods (like appendChild), the only relevant test you can be making is "is this object a DOM created genuine node?". This rules out any object. This includes proxies to node

Consider that as conditional check after node instanceof Element in order to know if that node is usable or not as DOM element.

Object.create(Element.prototype)?

There, a way to simulate an isProxy method and it does not matter if that's silly ... is possible

The possibility to use proxies in the DOM tree was ruled out because of the selector matching use case. If you have a solution to put proxies in the DOM tree without ruining selector matching perf, you will be listened.

# Andrea Giammarchi (13 years ago)

did you read anything I have been written in this thread or you have a short memory ?

I am strongly recommending to throw an error the moment an object cannot be proxied . I don't want to detect node and proxied node, I don't want DOM node to be allowed to be the first argument of the Proxy.

I don't want create 50 line sof code that demonstrates isDOMProxy is possible because you all know it is right now so, I am just waiting for the verdict about this issue because if this is meant ant won't change, the fact we can do new Proxy(HTMLElement), I want write about this problem and inform developers and library authors we gonna have bad time if a proxy leaks anywhere in a program.

thanks for any outcome

br

# Nathan Wall (13 years ago)

Thank you for the thoughtful reply. You sufficiently answered many of my concerns related to DOM, but I want to focus on the need for the unknownPrivateSymbol trap.

David Bruant wrote:

Imagine the following scenario: Untrusted code A is given access to a graph of objects through a membrane. Likewise for B with a different membrane. If both A and B have access to the same target, each through a proxy of their respective membrane. Now, you want to revoke access to all their membrane (including the shared target) to both A and B, because you don't want A and B to be able to communicate anymore.

If there is no unknownPrivateSymbol trap, A and B can continue to communicate through the private property on the shared target (through their respective proxy to it) and you have no way to prevent that. Even freezing the object wouldn't work apparently.

My reply concerning this is below.

Leaking the information that the node is actually a proxy is due to a DOM API issue (which is way way too late to fix obviously). If you want proxied node to go in the tree, you'll need to find an answer to the issue Boris Zbarsky brought up about selector matching [1]. I've given up [2], but I'm curious to know if there is a decent solution.

I have an idea, but I'm going to think through it some more and possibly post it as a separate reply later to keep this reply more focused.

Assuming nodes can be proxied and proxied node are refused to be appended. How can this be implemented in pure ES? All non-proxy nodes are created internally (via HTML parsing) or through document.createElement (are there others?), so, the DOM can keep a list of the exact objects belonging to the document and refuse appending any other object (discriminated with object identity) including proxied nodes.

That makes sense. Conceded.

Thinking a bit more about how A and B can share things, I'm realizing that anytime they want to share a private symbol, they have to do it through a "capturable" channel (set trap value argument, etc.). So if A and B are in touch, the membrane could capture all private symbols going from one side to the other and add it to the membrane whitelist. This way, anytime there is a communication attempt via private symbols, the private symbol is known and the non-unknownPrivateSymbol trap is called. In theory, it makes the unknownPrivateSymbol not absolutely necessary for this use case. In practice, to capture all communication, anytime the membranes intercept a new symbol, it needs to make sure it wasn't used on any other object previously shared between both parties and that means traversing entire graphs of objects which may be an enormous amount of work degrading performance. I'm also not a big fan of retroffiting control afterwards.

Ok, I think I understand now.

Here's how I'm imagining it: So if A shares object O with B, the membrane may wrap O in proxy P. O may have a private symbol properties M and N, where the property for symbol M actually points to symbol N:

let O = { },         M = new Symbol(true/* private /),         N = new Symbol(true/ private */);     O[M] = N;     O[N] = null;

When A shares O with B, the membrane will not be able to see either one of these symbols.

Let's assume no unknownPrivateSymbol trap.

The objects cannot yet communicate without the membrane knowing because B does not know O's private symbols.

Later if A shares private symbol M with B, the membrane will intercept the symbol and add it to the whitelist before passing it along. Now the membrane knows about M but it can only learn about N by searching all previous communications for M. The membrane could in theory discover N by searching previous communications for M and finding N in O.  I agree with you though that this is a pretty huge overhead for the membrane to have to do, so let's look for another way.

If the membrane fails to search O for M, then B can discover N under-the-hood -- that is, B gets access to a private symbol that the membrane doesn't have on its whitelist.

However, what if the membrane, instead of passing along M, creates a new private symbol M2, and this is the actual symbol shared with B. Now if B checks O for property M2 it will not be able to uncover the symbol N.  The membrane adds M2 to its whitelist and anytime B tries to send M2 back to A, the membrane converts it to M (and vice versa). Therefore, A always sees only symbol M and B always sees only symbol M2.

Does this work and allow us to do without unknownPrivateSymbol?

(An even easier alternative is that the membrane could prevent sharing private symbols.)

Nathan

# David Bruant (13 years ago)

Le 12/01/2013 23:28, Andrea Giammarchi a écrit :

I don't want to detect node and proxied node

Why would you have to? Currently, before calling .appendChild, do you write code that detects that you're not trying to append a normal object or a date or an array? I have personally never done such a test, because anytime I .appendChild, I've created the node before or picked it up from the DOM tree. Why would it be different when proxies are out?

# Nathan Wall (13 years ago)

hmm.. I just realized that B doesn't have direct access to O. So even if B does have access to M, trying to get N from P[M] would expose N to the membrane.

So perhaps I don't understand your original dilemma?

(Forgive me if I'm making you repeat past discussions for my sake. It's not my intention to just make you explain things to me that the rest of the list already understands.)

Nathan

# David Bruant (13 years ago)

Le 12/01/2013 23:54, Nathan Wall a écrit :

David Bruant wrote:

Leaking the information that the node is actually a proxy is due to a DOM API issue (which is way way too late to fix obviously). If you want proxied node to go in the tree, you'll need to find an answer to the issue Boris Zbarsky brought up about selector matching [1]. I've given up [2], but I'm curious to know if there is a decent solution. I have an idea, but I'm going to think through it some more and possibly post it as a separate reply later to keep this reply more focused.

I'm looking forward to it :-)

Thinking a bit more about how A and B can share things, I'm realizing that anytime they want to share a private symbol, they have to do it through a "capturable" channel (set trap value argument, etc.). So if A and B are in touch, the membrane could capture all private symbols going from one side to the other and add it to the membrane whitelist. This way, anytime there is a communication attempt via private symbols, the private symbol is known and the non-unknownPrivateSymbol trap is called. In theory, it makes the unknownPrivateSymbol not absolutely necessary for this use case. In practice, to capture all communication, anytime the membranes intercept a new symbol, it needs to make sure it wasn't used on any other object previously shared between both parties and that means traversing entire graphs of objects which may be an enormous amount of work degrading performance. I'm also not a big fan of retroffiting control afterwards. Ok, I think I understand now.

Here's how I'm imagining it: So if A shares object O with B, the membrane may wrap O in proxy P. O may have a private symbol properties M and N, where the property for symbol M actually points to symbol N:

 let O = { },
     M = new Symbol(true/* private */),
     N = new Symbol(true/* private */);
 O[M] = N;
 O[N] = null;

When A shares O with B, the membrane will not be able to see either one of these symbols.

Exactly.

Let's assume no unknownPrivateSymbol trap.

The objects cannot yet communicate without the membrane knowing because B does not know O's private symbols.

Later if A shares private symbol M with B, the membrane will intercept the symbol and add it to the whitelist before passing it along. Now the membrane knows about M but it can only learn about N by searching all previous communications for M. The membrane could in theory discover N by searching previous communications for M and finding N in O.

That's the scenario I had in mind.

I agree with you though that this is a pretty huge overhead for the membrane to have to do, so let's look for another way.

If the membrane fails to search O for M, then B can discover N under-the-hood -- that is, B gets access to a private symbol that the membrane doesn't have on its whitelist.

Yes. And suddenly A and B can communicate together in ways they aren't supposed to.

However, what if the membrane, instead of passing along M, creates a new private symbol M2, and this is the actual symbol shared with B. Now if B checks O for property M2 it will not be able to uncover the symbol N. The membrane adds M2 to its whitelist and anytime B tries to send M2 back to A, the membrane converts it to M (and vice versa). Therefore, A always sees only symbol M and B always sees only symbol M2.

Does this work and allow us to do without unknownPrivateSymbol?

I don't think this work. It's important to share the same private symbol, very much like it's important that if O has a "foo" property, it has a "foo" property in both A and B contexts and not "fooa" in one and "foob" in the other.

(An even easier alternative is that the membrane could prevent sharing private symbols.)

I don't think that's an option because there are legitimate use cases of private symbols with proxies.

# Andrea Giammarchi (13 years ago)

David if you think about the most used library out there and the fact it would like to threat not only DOM nodes but objects or arrays too, you realize we might have a problem with a proxy in the wild that is wrapping a DOM node, isn't it?

$(whatIsThis)

So this unable to recognize but able to create could be a problem and/or a security issue. A proxy that lands inside jQuery closure, or any other similar library, might have the ability to do many things silently and behind the scene.

If this is meant, we should all know and eventually deal, if necessary, with this.

# Brandon Benvie (13 years ago)

This isn't exactly a direct parallel because all the types that are included as part of ES6 will be proxyable in such a way that the proxy can be transparently interchanged with its target. The problem with DOM objects is that this isn't true. I think it would be a good practice for the providers of unproxyable platform APIs to be up front about their inability to be proxied successfully.

# Andrea Giammarchi (13 years ago)

also because I believe it is straight forward ... "is this a hosted thingy" ? throw "this object cannot be proxied"

So the source of all evil disappear until Proxy is eventually able to wrap DOM instances too.

# David Bruant (13 years ago)

Le 13/01/2013 00:18, Brandon Benvie a écrit :

This isn't exactly a direct parallel because all the types that are included as part of ES6 will be proxyable in such a way that the proxy can be transparently interchanged with its target. The problem with DOM objects is that this isn't true. I think it would be a good practice for the providers of unproxyable platform APIs to be up front about their inability to be proxied successfully.

Andrea and I disagree on what "unproxyable" and "be up front about their inability to be proxied successfully" mean. I'll try to summarize my and Andrea's position the best I can (please Andrea, correct me if I'm mistaken about your position):

"unproxyable" Me: proxyability is about the Reflect API, so every object is proxyable (but other types like numbers, booleans, etc. aren't) Andrea: if a proxy to a DOM node cannot be substituted to a genuine DOM node, then DOM nodes are unproxyable (can be applied to any sor of objects obviously)

"be up front about their inability to be proxied successfully" Me: it's enough if the specs are upfront (corollary: web specs need to start talking about how they deal with proxies) Andrea: the runtime needs to be upfront

Anyone wants to provide different definitions?

# Brandon Benvie (13 years ago)

I agree with that. This isn't something that belongs in ECMAScript, it's out of scope. As you said, this is a conversation that needs to be had in the space of DOM specifications/implementations.

# David Bruant (13 years ago)

Le 13/01/2013 00:09, Nathan Wall a écrit :

hmm.. I just realized that B doesn't have direct access to O. So even if B does have access to M, trying to get N from P[M] would expose N to the membrane.

Yes and once the membrane has captured N, it has to search again if some object use in A<--->B communication has an N property... and restart all

over if an object does and has another private name attached. Theoretically, the membrane can always be one step ahead of the private communication. Practically, it costs a lot without the unknownPrivateSymbol trap.

So perhaps I don't understand your original dilemma?

For this scenario, we've proven that unknownPrivateSymbol is not absolutely necessary, but not having it would result in a huge runtime cost and there is a (minor) risk of bug in the private property search that could compromise the security. So, I think that even if it's not absolutely necessary for this scenario, the unknownPrivateSymbol trap is a good thing.

I have described one scenario where the unknownPrivateSymbol isn't strictly necessary. Maybe there are other where it is (I can't think of any now, but I don't know if there is none either)

(Forgive me if I'm making you repeat past discussions for my sake. It's not my intention to just make you explain things to me that the rest of the list already understands.)

Proxies and private symbols are both new features. I don't recall this kind of questions happened on the list too much, so I think it's beneficial for everyone to have this discussion on the list.

# Andrea Giammarchi (13 years ago)

my concern is about security problems too but at least having this spec'ed somewhere as known gotcha that cannot be solved not in the ECMAScript domain would be better than nothing.

br

# Andrea Giammarchi (13 years ago)

double negation ... that cannot be solved in the ECMAScript domain .... yeah!

# Nathan Wall (13 years ago)

David Bruant wrote:

Yes and once the membrane has captured N, it has to search again if some object use in A<--->B communication has an N property... and restart all over if an object does and has another private name attached. Theoretically, the membrane can always be one step ahead of the private communication. Practically, it costs a lot without the unknownPrivateSymbol trap.

I was under the impression that all objects that were passed from A to B were wrapped in a proxy by the membrane. If this is the case, then the membrane simply needs to add N to the whitelist and N can't be used in B on an object from A without the membrane knowing about it.

So there's no need to search previous communications. The membrane can learn about any private symbols B can because the membrane is always watching B's access to objects from A through proxies.

Nathan

# David Bruant (13 years ago)

Le dim. 13 janv. 2013 00:43:29 CET, Nathan Wall a écrit :

David Bruant wrote:

Yes and once the membrane has captured N, it has to search again if some object use in A<--->B communication has an N property... and restart all over if an object does and has another private name attached. Theoretically, the membrane can always be one step ahead of the private communication. Practically, it costs a lot without the unknownPrivateSymbol trap.

I was under the impression that all objects that were passed from A to B were wrapped in a proxy by the membrane. If this is the case, then the membrane simply needs to add N to the whitelist and N can't be used in B on an object from A without the membrane knowing about it.

True. I guess it simplifies things a lot :-p

So there's no need to search previous communications. The membrane can learn about any private symbols B can because the membrane is always watching B's access to objects from A through proxies.

Ok, I'm convinced. This scenario doesn't need the unknownPrivateSymbol trap after all apparently. Thanks for not giving up Nathan :-)

I'm still reluctant to give up on the unknownPrivateSymbol trap, because doing so means giving up on mediation for one case. In this case, we were able to capture every single private symbol passed back and forth, but in another situation, maybe we didn't create A and B. Someone else created them and just shared with each the same private symbol and then handed off access to A and B so you can work with them. You share an object through a membrane, they cooperate to do what you ask them to (without using their shared secret), then, you'd like to cut the communication? You revoke the membranes. But sharing a common object (or a least different proxies to a common target) is all they need to continue communicate since they have a private symbol you're oblivious to, so they can still communicate. In that scenario, the only way to actually cut the communication is the unknownPrivateSymbol trap (admittedly, the scenario has been crafted to that end, but is realistic anyway)

# Nathan Wall (13 years ago)

I'm still reluctant to give up on the unknownPrivateSymbol trap, because doing so means giving up on mediation for one case. In this case, we were able to capture every single private symbol passed back and forth, but in another situation, maybe we didn't create A and B. Someone else created them and just shared with each the same private symbol and then handed off access to A and B so you can work with them. You share an object through a membrane, they cooperate to do what you ask them to (without using their shared secret), then, you'd like to cut the communication? You revoke the membranes. But sharing a common object (or a least different proxies to a common target) is all they need to continue communicate since they have a private symbol you're oblivious to, so they can still communicate. In that scenario, the only way to actually cut the communication is the unknownPrivateSymbol trap (admittedly, the scenario has been crafted to that end, but is realistic anyway)

Is this scenario unique to private symbols? Can't I replace the word "private symbol" in your scenario above with "WeakMap" (or really just any object) and you have the same problem?

If A and B are created by someone else, and both A and B are given a shared WeakMap and then they are passed off to a membrane which tries to cut communication, can't A and B continue to communicate behind the scenes through their shared WeakMap?

I think if A and B are known directly to anyone before the membrane, they can build a backdoor to communicate, unknownPrivateSymbol trap or not.

Nathan

# David Bruant (13 years ago)

Le 13/01/2013 01:27, Nathan Wall a écrit :

I'm still reluctant to give up on the unknownPrivateSymbol trap, because doing so means giving up on mediation for one case. In this case, we were able to capture every single private symbol passed back and forth, but in another situation, maybe we didn't create A and B. Someone else created them and just shared with each the same private symbol and then handed off access to A and B so you can work with them. You share an object through a membrane, they cooperate to do what you ask them to (without using their shared secret), then, you'd like to cut the communication? You revoke the membranes. But sharing a common object (or a least different proxies to a common target) is all they need to continue communicate since they have a private symbol you're oblivious to, so they can still communicate. In that scenario, the only way to actually cut the communication is the unknownPrivateSymbol trap (admittedly, the scenario has been crafted to that end, but is realistic anyway) Is this scenario unique to private symbols? Can't I replace the word "private symbol" in your scenario above with "WeakMap" (or really just any object) and you have the same problem?

Yes or no. That's what we're debating. Private symbols and Weakmaps have a lot in common. One major difference is how they are thought and used. In how they are being used, they should be associated with strings (when used in the context or property name). Also, if the creator of A and B cares about how and whether they can communicate, it can send a wrapped weakmap and revoke the access to the shared weakmap anytime it wants. That's not an option with private symbols without the unknownPrivateSymbol trap.

If A and B are created by someone else, and both A and B are given a shared WeakMap and then they are passed off to a membrane which tries to cut communication, can't A and B continue to communicate behind the scenes through their shared WeakMap?

The contract of whoever created A and B may be just that it shared a private symbol with them (maybe because it's using this symbol with them). Maybe even specifically so that it can cut the communication latter.

I think if A and B are known directly to anyone before the membrane, they can build a backdoor to communicate, unknownPrivateSymbol trap or not.

If A and B are known directly to one another, it's pointless to try to separate them indeed. However, it can be in the contract of any other party that they can make communicate A and B but only in a revokable manner. The problem with private symbols without the unknownPrivateSymbol trap is that it put in the language a way to provide an unrevokable ability to communicate, making private symbols inadequate for a lot of situations situations where you need to revoke it. Obviously, you can work around with wrapped WeakMap, but suddenly, you've reduced what's allowed in the language for untrusted code, elsewhere known as feature starvation. I think feature starvation is acceptable when you try to retrofit proxies into existing APIs that weren't designed with proxies in mind (DOM as an example). However I don't think it's acceptable for features being worked on.

# Andreas Rossberg (13 years ago)

On 8 January 2013 22:33, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Tue, Jan 8, 2013 at 1:30 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:

so you are saying that Object.observe() does not suffer these problems ? Or is just much simpler than Proxies ? ( yeah, I know, I guess both ... )

I believe it just wouldn't suffer the same problems - it needs to observe the JS-visible stuff, which DOM objects expose normally, so it can just hook those. Alternately, it can be specialized to handle DOM stuff correctly even with their craziness. I'm not familiar with the implementations of it.

Object.observe isn't simpler than proxies, but the complexity is along somewhat different axes.

In any case, WebIDL actually specs attributes as accessor properties, which means that Object.observe simply ignores them. So there isn't much interference between Object.observe and the DOM.