05:53
<Mathieu Hofman>
also the "just" part of "just created yourself" can be dropped if you're willing to have it throw if the object has a .then property that the user put on it, which I think is a perfectly fine restriction; I updated my snippet to do that. as long as you know the object is one you created, even if you've since handed it out to a user (as in the Animation CVE in the repo) then you can rely on it not being a proxy and so the hasOwn check not triggering user code
Not true, the user may have set the prototype to a proxy which would trap when not finding an own then
05:55
<nicolo-ribaudo>
At which step of that code would it trap?
05:58
<Mathieu Hofman>
In the animation case, the spec does/did `promiseCapability.resolve(this)`, with `this` being the animation instance that had previously been exposed to userland. Resolve does synchronously look up a `then` property, and knowing that the animation object isn't a proxy (having been brand checked) doesn't mean it can't trap a `x.Get('then')`
05:59
<Mathieu Hofman>
I don't see why `hasOwn` is relevant in this case
06:00
<nicolo-ribaudo>
The idea is that instead of .resolve you would use @bakkot:matrix.org's implementation above
06:00
<nicolo-ribaudo>
function safePromiseCapability() {
  let { promise, resolve, reject } = Promise.withResolvers();
  function safeResolve(val) {
    if (Object.hasOwn(val, 'then')) throw new TypeError('you are not supposed to use this with thenables');
    Object.defineProperty(val, 'then', { configurable: true, value: void 0 });
    resolve(val);
    delete val.then;
  }
  return { promise, safeResolve, reject };
}

let evilProto = { get then() { throw 'boom'; } };

let { promise, safeResolve } = safePromiseCapability();
safeResolve({ __proto__: evilProto, key: 'value' });
console.log((await promise).key); // no boom

turns out we already have the "safe promise resolve" capability with no changes to the language

This one
06:00
<Mathieu Hofman>
But you can't rely on that object being extensible anymore
06:02
<nicolo-ribaudo>
Mh ok
06:03
<Mathieu Hofman>
Unless throwing on a frozen animation instance is acceptable ?
13:00
<bakkot>
yeah seems fine
13:02
<bakkot>

options:

  • throw on a non-extensible animation instance. this has probably literally never come up so I doubt anyone will even notice
  • only use this functionality for newborn objects. doesn't solve the Animation CVE but does still address most of them
  • decide that since the above snippet gets us 99% of the way to a "resolveNonThennable", and the "must be extensible" restriction makes no sense from a user's perspective given that the object is not observably-to-them gaining a new property, we might as well just provide an actual "resolveNonThennable" function which works in the non-extensible case as well
16:05
<Mathieu Hofman>
If we're gonna introduce a new resolve function, why can't we simply delay the resolution in those cases by a tick instead of doing things that are weird and / or not 100% compatible.
16:12
<bakkot>
I would prefer not to make users of the web pay a cost, even a small one, just for the sake of avoiding things which are in some sense "weird" but which no developer much less user would ever even notice.
17:06
<Mathieu Hofman>
But unless the developer does something weird there may not be a cost!
17:07
<Mathieu Hofman>
Again my suggestion for a safe resolve spec op is to delay by a tick when it would trigger user code
17:12
<bakkot>
As I understood it, you didn't like that the "would trigger use code" check would let you to build a pretty straightforward gadget to detect proxies, so rather than "would trigger use code" the check would have to be "is an object", which is ~all of the cases in question
17:12
<bakkot>
so the extra tick would be incurred in ~all cases
17:14
<bakkot>
if that understanding is wrong then sure I'm open to going in that direction instead
17:14
<bakkot>
but if our choices are "do something which is theoretically kinda weird but is allowed by the language already, and which no one will ever notice, or make all web specs which resolve a promise with an object slower", I'm going to advocate for the former