Re: Object.unfreeze, or similar API
> If the engine were to read these all in advance, this would cause a problem. No, my handler always provides the same function for a given trap name, so even if the traps were cached when the proxy is constructed, the code would still work. > Are we sure that all engines don't read the handler spec in advance? Yes, the spec does not allow this. But there was a proposal about doing that in some cases, in order to improve performance. -- Oriol ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
I see, so you're relying on the engine reading the handler object during the moment that the user of my `obj` will try to read `obj`, so if the engine tries to read something in the handler (that we haven't whitelisted) due to what the user is doing with `obj` then we throw the error. If the engine were to read these all in advance, this would cause a problem. Are we sure that all engines don't read the handler spec in advance? */#!/*JoePea On Sat, Mar 24, 2018 at 2:59 PM, Oriol _ wrote: > Hi Joe, I used a 2nd proxy as the handler in order to only allow the > desired traps. > > Sure, I could have defined all blacklisted traps in an ordinary object, > and make them throw an error. > > But this wouldn't be future-proof in case new traps are eventually added. > Whitelisting is safer. > > -- Oriol > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
Hi Joe, I used a 2nd proxy as the handler in order to only allow the desired traps. Sure, I could have defined all blacklisted traps in an ordinary object, and make them throw an error. But this wouldn't be future-proof in case new traps are eventually added. Whitelisting is safer. -- Oriol ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
Hello Oriol, why did you make two Proxies there? Does it serve some purpose not achieved with only one Proxy? - Joe */#!/*JoePea On Mon, Feb 19, 2018 at 12:35 PM, Oriol _ wrote: > > So, what if there was a way to unfreeze an object in the scope in which > the object was frozen? > > I don't think the behavior of object operations should depend on the scope > in which they are used. > > And I could understand undoing [[PreventExtensions]], just switch > [[Extensible]] back to true (for ordinary objects). But unfreezing makes no > sense. How is ES supposed to know which properties became non-configurable > because of `Object.freeze` and which ones were manually defined as > non-configurable? Do you want all non-configurable properties to become > configurable when "unfreezing"? I think that would be bad. > > > // recommend all listeners to be synchronous, and not modify the payload > later > > You can recommend, but what if they are not synchronous? They will have a > reference to the unfrozen object! If you trust them, you can avoid freezing > in the first place. If you can't trust them, unfreezing is a big problem! > > Frankly I don't see the point, if you don't want to clone just use a proxy > that only allows restricted access. > > ```js > let error = () => { throw new Error(); }; > let allowed = ["get", "ownKeys", "has", "getOwnPropertyDescriptor"]; > let handler = new Proxy(Object.create(null), { > get(_, trap, receiver) { > if (!allowed.includes(trap)) return error; > } > }); > this.emit('some:event', new Proxy(obj, handler)); > ``` > > -- Oriol > > ___ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
The entire purpose of Object.freeze is that the frozen object *can never be altered again* (in the ways that freeze restricts, at least). Allowing an object to be unfrozen would violate that very critical security property. The same is true with seal and preventExtensions. Once locked down, an object *must not* be unlockable. On Mon, Feb 19, 2018 at 12:35 PM, Oriol _ wrote: > > So, what if there was a way to unfreeze an object in the scope in which > the object was frozen? > > I don't think the behavior of object operations should depend on the scope > in which they are used. > > And I could understand undoing [[PreventExtensions]], just switch > [[Extensible]] back to true (for ordinary objects). But unfreezing makes no > sense. How is ES supposed to know which properties became non-configurable > because of `Object.freeze` and which ones were manually defined as > non-configurable? Do you want all non-configurable properties to become > configurable when "unfreezing"? I think that would be bad. > > > // recommend all listeners to be synchronous, and not modify the payload > later > > You can recommend, but what if they are not synchronous? They will have a > reference to the unfrozen object! If you trust them, you can avoid freezing > in the first place. If you can't trust them, unfreezing is a big problem! > > Frankly I don't see the point, if you don't want to clone just use a proxy > that only allows restricted access. > > ```js > let error = () => { throw new Error(); }; > let allowed = ["get", "ownKeys", "has", "getOwnPropertyDescriptor"]; > let handler = new Proxy(Object.create(null), { > get(_, trap, receiver) { > if (!allowed.includes(trap)) return error; > } > }); > this.emit('some:event', new Proxy(obj, handler)); > ``` > > -- Oriol > > ___ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Object.unfreeze, or similar API
Note that if you want to use the Proxy that way you will probably want to implement the setPrototypeOf and preventExtensions traps as well since, together with set, defineProperty and deleteProperty, they are the traps whose associated internal methods can have side effects on the target when the traps are not implemented. ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
> So, what if there was a way to unfreeze an object in the scope in which the > object was frozen? I don't think the behavior of object operations should depend on the scope in which they are used. And I could understand undoing [[PreventExtensions]], just switch [[Extensible]] back to true (for ordinary objects). But unfreezing makes no sense. How is ES supposed to know which properties became non-configurable because of `Object.freeze` and which ones were manually defined as non-configurable? Do you want all non-configurable properties to become configurable when "unfreezing"? I think that would be bad. > // recommend all listeners to be synchronous, and not modify the payload later You can recommend, but what if they are not synchronous? They will have a reference to the unfrozen object! If you trust them, you can avoid freezing in the first place. If you can't trust them, unfreezing is a big problem! Frankly I don't see the point, if you don't want to clone just use a proxy that only allows restricted access. ```js let error = () => { throw new Error(); }; let allowed = ["get", "ownKeys", "has", "getOwnPropertyDescriptor"]; let handler = new Proxy(Object.create(null), { get(_, trap, receiver) { if (!allowed.includes(trap)) return error; } }); this.emit('some:event', new Proxy(obj, handler)); ``` -- Oriol ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
That's actually very useful, thanks! I have plenty of cases where I wished I could pass an object without cloning it to ensure it isn't mutated from outside. And instead of creating the Proxy right when I will emit an event (for example), I could just store the proxy somewhere so it is long-lived and to avoid GC. */#!/*JoePea On Mon, Feb 19, 2018 at 12:08 PM, Michał Wadas wrote: > You can just do: > > const proxy = new Proxy(obj, { >set() { throw new Error(); }, >defineProperty() { throw new Error();}, >deleteProperty() { throw new Error(); } > }) > this.emit('some:event', proxy) > > > Though, it seems like an exotic use case. > > On Mon, Feb 19, 2018 at 8:56 PM, /#!/JoePea wrote: > >> >> I think the ability to unfreeze an object can be useful. >> >> For example, suppose we have a library that emits events: >> >> ```js >> this.emit('some:event', this.obj) >> ``` >> >> Maybe we want to try to prevent external listeners from modifying the >> event payload without having to clone `this.obj`, otherwise cloning can >> cause "jank" from GC during animations that should be smooth. >> >> So, what if there was a way to unfreeze an object in the scope in which >> the object was frozen? >> >> ```js >> Object.freeze(obj) // instead of cloning >> this.emit('some:event', this.obj) // recommend all listeners to be >> synchronous, and not modify the payload later >> Object.unfreeze(this.obj) // in the same scope >> ``` >> >> or maybe it return an unfreeze function: >> >> ```js >> const unfreeze = Object.freeze(obj) // instead of cloning >> this.emit('some:event', this.obj) // recommend all listeners to be >> synchronous, and not modify the payload later >> unfreeze(this.obj) // only code that is given the unfreeze function can >> call it, similar to resolve/reject functions with Promises >> ``` >> >> or maybe it is more similar to setTimeout and setInterval: >> >> ```js >> const frozenKey = Object.freeze(obj) // instead of cloning >> this.emit('some:event', this.obj) // recommend all listeners to be >> synchronous, and not modify the payload later >> Object.unfreeze(frozenKey) // only code that is given the key can >> unfreeze the object associated with the key >> ``` >> >> It seems like there can be opportunity for this to be optimized, so that >> it can be faster than cloning. For example, this uses more memory and >> causes GC: >> >> ```js >> this.emit('some:event', { ...this.obj }) // creates a new object >> ``` >> >> Could the performance benefit make it worth it to have a way to unfreeze >> objects? >> >> Another use case is unlocking things only when used in certain places, as >> a "guard". In the following example, the only way to set the X rotation >> value is with the setter, which enforces that certain logic will fire, >> rather than someone from the outside modifying the readable state not using >> the setter: >> >> ```js >> >> import Privates from 'somewhere' >> >> // a private cache, using WeakMap internally >> const _ = new Privates >> >> class ThingThatRotates { >> constructor() { >> this.rotation = { x: 0, y: 0, z: 0 } >> _(this).frozen = Object.freeze(this.rotation) >> // ... >> } >> >> get rotateX() { return this.rotation.x } >> set rotateX(val) { >> this.doSomethingImportantWithTheValue(val) >> >> // unlock here, so that the value can only be set with this >> setter. >> Object.unfreeze(_(this).frozen) >> this.rotation.x = val >> _(this).frozen = Object.freeze(this.rotation) >> >> this.emitAnEventWithTheValue(val) >> this.etc(val) >> } >> >> // ... >> } >> >> ``` >> >> I know, I can probably just keep `rotation` in the private cache, but >> then it isn't readable. Maybe I want that to be readable as a convenient >> shortcut. F.e. >> >> ```js >> // outside code >> const r = new ThingThatRotates >> >> // X rotation might be animated by some other tool >> >> const {x, y, z} = r.rotation // for convenience, no cloning involved. >> >> ``` >> >> This is a completely contrived example I just made up right now. I can >> probably think of more. It seems like it could be good for performance in >> cases where we don't want modification from the outside... >> >> >> /#!/JoePea >> >> ___ >> es-discuss mailing list >> es-discuss@mozilla.org >> https://mail.mozilla.org/listinfo/es-discuss >> >> > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Re: Object.unfreeze, or similar API
You can just do: const proxy = new Proxy(obj, { set() { throw new Error(); }, defineProperty() { throw new Error();}, deleteProperty() { throw new Error(); } }) this.emit('some:event', proxy) Though, it seems like an exotic use case. On Mon, Feb 19, 2018 at 8:56 PM, /#!/JoePea wrote: > > I think the ability to unfreeze an object can be useful. > > For example, suppose we have a library that emits events: > > ```js > this.emit('some:event', this.obj) > ``` > > Maybe we want to try to prevent external listeners from modifying the > event payload without having to clone `this.obj`, otherwise cloning can > cause "jank" from GC during animations that should be smooth. > > So, what if there was a way to unfreeze an object in the scope in which > the object was frozen? > > ```js > Object.freeze(obj) // instead of cloning > this.emit('some:event', this.obj) // recommend all listeners to be > synchronous, and not modify the payload later > Object.unfreeze(this.obj) // in the same scope > ``` > > or maybe it return an unfreeze function: > > ```js > const unfreeze = Object.freeze(obj) // instead of cloning > this.emit('some:event', this.obj) // recommend all listeners to be > synchronous, and not modify the payload later > unfreeze(this.obj) // only code that is given the unfreeze function can > call it, similar to resolve/reject functions with Promises > ``` > > or maybe it is more similar to setTimeout and setInterval: > > ```js > const frozenKey = Object.freeze(obj) // instead of cloning > this.emit('some:event', this.obj) // recommend all listeners to be > synchronous, and not modify the payload later > Object.unfreeze(frozenKey) // only code that is given the key can unfreeze > the object associated with the key > ``` > > It seems like there can be opportunity for this to be optimized, so that > it can be faster than cloning. For example, this uses more memory and > causes GC: > > ```js > this.emit('some:event', { ...this.obj }) // creates a new object > ``` > > Could the performance benefit make it worth it to have a way to unfreeze > objects? > > Another use case is unlocking things only when used in certain places, as > a "guard". In the following example, the only way to set the X rotation > value is with the setter, which enforces that certain logic will fire, > rather than someone from the outside modifying the readable state not using > the setter: > > ```js > > import Privates from 'somewhere' > > // a private cache, using WeakMap internally > const _ = new Privates > > class ThingThatRotates { > constructor() { > this.rotation = { x: 0, y: 0, z: 0 } > _(this).frozen = Object.freeze(this.rotation) > // ... > } > > get rotateX() { return this.rotation.x } > set rotateX(val) { > this.doSomethingImportantWithTheValue(val) > > // unlock here, so that the value can only be set with this setter. > Object.unfreeze(_(this).frozen) > this.rotation.x = val > _(this).frozen = Object.freeze(this.rotation) > > this.emitAnEventWithTheValue(val) > this.etc(val) > } > > // ... > } > > ``` > > I know, I can probably just keep `rotation` in the private cache, but then > it isn't readable. Maybe I want that to be readable as a convenient > shortcut. F.e. > > ```js > // outside code > const r = new ThingThatRotates > > // X rotation might be animated by some other tool > > const {x, y, z} = r.rotation // for convenience, no cloning involved. > > ``` > > This is a completely contrived example I just made up right now. I can > probably think of more. It seems like it could be good for performance in > cases where we don't want modification from the outside... > > > /#!/JoePea > > ___ > es-discuss mailing list > es-discuss@mozilla.org > https://mail.mozilla.org/listinfo/es-discuss > > ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss
Object.unfreeze, or similar API
I think the ability to unfreeze an object can be useful. For example, suppose we have a library that emits events: ```js this.emit('some:event', this.obj) ``` Maybe we want to try to prevent external listeners from modifying the event payload without having to clone `this.obj`, otherwise cloning can cause "jank" from GC during animations that should be smooth. So, what if there was a way to unfreeze an object in the scope in which the object was frozen? ```js Object.freeze(obj) // instead of cloning this.emit('some:event', this.obj) // recommend all listeners to be synchronous, and not modify the payload later Object.unfreeze(this.obj) // in the same scope ``` or maybe it return an unfreeze function: ```js const unfreeze = Object.freeze(obj) // instead of cloning this.emit('some:event', this.obj) // recommend all listeners to be synchronous, and not modify the payload later unfreeze(this.obj) // only code that is given the unfreeze function can call it, similar to resolve/reject functions with Promises ``` or maybe it is more similar to setTimeout and setInterval: ```js const frozenKey = Object.freeze(obj) // instead of cloning this.emit('some:event', this.obj) // recommend all listeners to be synchronous, and not modify the payload later Object.unfreeze(frozenKey) // only code that is given the key can unfreeze the object associated with the key ``` It seems like there can be opportunity for this to be optimized, so that it can be faster than cloning. For example, this uses more memory and causes GC: ```js this.emit('some:event', { ...this.obj }) // creates a new object ``` Could the performance benefit make it worth it to have a way to unfreeze objects? Another use case is unlocking things only when used in certain places, as a "guard". In the following example, the only way to set the X rotation value is with the setter, which enforces that certain logic will fire, rather than someone from the outside modifying the readable state not using the setter: ```js import Privates from 'somewhere' // a private cache, using WeakMap internally const _ = new Privates class ThingThatRotates { constructor() { this.rotation = { x: 0, y: 0, z: 0 } _(this).frozen = Object.freeze(this.rotation) // ... } get rotateX() { return this.rotation.x } set rotateX(val) { this.doSomethingImportantWithTheValue(val) // unlock here, so that the value can only be set with this setter. Object.unfreeze(_(this).frozen) this.rotation.x = val _(this).frozen = Object.freeze(this.rotation) this.emitAnEventWithTheValue(val) this.etc(val) } // ... } ``` I know, I can probably just keep `rotation` in the private cache, but then it isn't readable. Maybe I want that to be readable as a convenient shortcut. F.e. ```js // outside code const r = new ThingThatRotates // X rotation might be animated by some other tool const {x, y, z} = r.rotation // for convenience, no cloning involved. ``` This is a completely contrived example I just made up right now. I can probably think of more. It seems like it could be good for performance in cases where we don't want modification from the outside... /#!/JoePea ___ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss