> >> 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.         
                                  
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to