Re: Object.unfreeze, or similar API

2018-03-24 Thread Oriol _
> 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

2018-03-24 Thread /#!/JoePea
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

2018-03-24 Thread Oriol _
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

2018-03-24 Thread /#!/JoePea
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

2018-02-19 Thread Jordan Harband
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

2018-02-19 Thread Raul-Sebastian Mihăilă
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

2018-02-19 Thread Oriol _
> 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

2018-02-19 Thread /#!/JoePea
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

2018-02-19 Thread Michał Wadas
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

2018-02-19 Thread /#!/JoePea
​
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