-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
non-readonly [FrozenArray] attributes are footguns as currently specced #810
Comments
Alternately, we could eliminate FrozenArray altogether and do the "freeze and cache" thing via extended attributes on the type or attribute, I suppose. I'm not sure that's better than having a dedicated type, but it would make it clear that the underlying implementation sees a sequence. |
I like the extended attribute idea. Something like (not entirely sure about placement of extended attributes, forgive me): [Cached] attribute [FrozenArray] sequence<Item> arr; (With [Cached] from #212. And [FrozenArray] can only be used if [Cached] is used or if it's a method, and the type is a sequence.) |
Eliminating FrozenArray would syntactically allow |
For what it's worth, Gecko's parser allows |
Would [FrozenArray] and [Cached] extend to this?
The desired behavior of [Cached] here is that the same ECMAScript Array is produced on sequence-to-ES conversion when the same sequence is passed for |
I don't think that's currently being proposed, especially because there is no concept of "same sequence", really: sequences are passed by value, so the best you could do is define that two sequences are "the same" if the lengths are the same and all the items are "the same", but then you have to define what "the same" means for |
Thanks. I can see the problem if sequences do not have identifying handles. I'm trying to understand in "a FrozenArray-to-ES conversion takes a sequence as input, creates a frozen JS Array from it, and caches it" where the result would be cached. Is there a proposed description of [Cached]? Does [Cached] indicate that the language binding for an attribute (for example, or only attributes even) should cache? I can see how that would not extend well to callback parameters. Currently,
would pass by reference to an ECMAScript callback.
That is arguably more informative, because it is clearly an Object, and less informative, because the nature of the Object would need more description in prose. |
Sure. I would define it as follows, informally, for the ES binding:
In spec terms, https://heycam.github.io/webidl/#internally-create-a-new-object-implementing-the-interface step 4 would add more slots to the list as needed. https://heycam.github.io/webidl/#dfn-attribute-getter would have extra steps to check the slot as needed and to store the gotten value into the slot after the https://heycam.github.io/webidl/#get-the-underlying-value step and doing the converting to an ES value. https://heycam.github.io/webidl/#dfn-attribute-setter would have extra steps to clear the slot, probably after the "Perform the actions" step, and probably only if that step does not throw. I think that's about all that's needed, plus possibly some language about being able to clear the slot manually from spec algorithms as needed if the value changes other than through the setter.
That is my proposal, yes.
If we got rid of the FrozenArray type altogether, you mean? It would depend on what type got used instead. If we kept FrozenArray but just changes what ES-to-IDL conversion means for it, there would be no difference. Are there specific APIs you're thinking of that use that pattern? I'm not aware of any offhand; https://wicg.github.io/CSS-Parser-API/ has
Indeed. |
If we kept FrozenArray but changed its IDL-to-ES conversion as initially proposed, then it would be pass by copy for ECMAScript callbacks. If the ES-to-IDL conversion is changed to produce a sequence, then the IDL-to-ES conversion will also need to change. To be clear, I'm not advocating either way here, just watching for how this might impact direction in WebAudio/web-audio-api#1933 |
I agree this is a problem. I think one route toward fixing this is using the classes in #796 to completely replace all My initial impulse was toward just "fixing" Another factor that makes me consider Regardless, I think it's worth thinking through what we want the spec authoring experience to be. Here's a stab:
This seems reasonable, although a bit fragile. Is it possible to improve? |
Did you see #212? We can use [Cached] for lots of things. |
So in that world, things that currently have readonly
This seems pretty reasonable to me, with the additional note that the "create and freeze" step is not observable, and could be done lazily on get, as far as I can tell, as long as the global used for the creation can't change over time. In either case that global should be defined, presumably to the relevant global of the object involved. We could consider having mutations to the backing infra list automagically invoke "reset the frozen array value" to the new value, so spec authors can mutate it directly. That does involve some slightly weird action at a distance that might be non-obvious when reading and implementing a spec... I don't have any great ideas for improving that so far. |
Yes. There are potential compat issues (most notably breaking
I think the attribute setters could still work. I guess specs would use the same "reaction" spec text to update their internal data when setter is called as when the ReactToAbleArray is mutated. |
I'm not clear on the status of this issue or those it's related to in e.g. CSS, but with developer and ultimately user interests in mind, I'd like to call attention to analogous but simpler discussions in TC39, where It's worth noting that the discussion included a suggestion to keep the getter but have it return cached frozen objects, which was rejected for what seemed in part to be performance concerns that I find surprising in light of similar behavior required in the same software to implement this other set of specifications. TC39 haven't yet visited up the setter case, but it would be great to establish some consistency for just reading object-valued data before that comes up. Where is the best place to see what has already been firmly committed to in this part of the web (and how firm those commitments are)? |
@gibson042 if you grep for [FrozenArray] in browser IDL there's a number of APIs. Most notably element-reflection APIs in HTML. I don't think it's much-used though and in general I think folks follow the pattern of |
Note: currently webref lists the following interfaces with non-readonly
I'm not sure what's up with all the permission results needing to be mutable; the base class So I would suggest the following plan for this issue:
|
I've brought this up before, but I can't find any relevant issues, so trying to put it all down in one place.
Consider this IDL:
What happens when the setter is called? Per spec we land in https://heycam.github.io/webidl/#es-to-frozen-array which converts the value passed to the setter to a sequence, then converts that sequence to a JS
Array
object, freezes it, and passes that off to the underlying spec-defined setter.OK, but now the spec-defined setter is getting a JS
Array
, and spec authors have no idea how to deal with those. A typical example of a spec using this stuff (not linking, because I am not trying to call out anyone in particular) looks like this:Both of those statements are problematic in this case. "For each entry in input" is ambiguous when input is a JS
Array
. Does that mean callinginput.forEach
? Does it mean calling the original value ofArray.prototype.forEach
? Does it mean doing the equivalent offor (let entry of input)
? Does it mean doingfor (var i = 0; i < input.length; ++i) { let entry = input[i]; // etc
? All of those produce observably different behaviors. Spec authors probably (1) don't realize those options exist, (2) don't realize they do different things. Implementors are left to make up something."Set image's sizes to entry's sizes" is similarly problematic: input is a JS
Array
, so entry is a JS value. In this case, because the FrozenArray type parameter is a dictionary type we know thatType(entry)
is Object, but that says nothing about how "entry's sizes" is computed. The principled thing would be to convert the object to the relevant dictionary type, but that is a little silly because we already had that dictionary in our sequence before we created the JSArray
, and dictionaries don't actually roundtrip through to-JS-and-back conversions if someone has messed withObject.prototype
...OK, so what do implementations do?
Gecko doesn't implement
FrozenArray
at all, for some of the above reasons; the thing we do implement has the semantics of a sequence-typed attribute with automatic caching at the binding layer on get and automatic cache invalidation on set.Blink claims to implement
FrozenArray
, but doesn't follow the spec and passes a sequence instead of a JSArray
to the underlying setter, as far as I can get. Getters are expected to return a JSArray
; the typical Blink implementation of aFrozenArray
API returns a new JSArray
on each get, which is also problematic.WebKit seems to pass a sequence-like thing, not a JS
Array
for the setter and likewise expect one for the getter; I didn't dig enough into whether they cache on get or not. There are no non-test writable FrozenArray-typed attributes in WebKit, as far as I can see.I would like to propose that we more or less reify the Gecko semantics into the IDL spec: an ES-to-FrozenArray conversion creates a sequence, while a FrozenArray-to-ES conversion takes a sequence as input, creates a frozen JS Array from it, and caches it. At least for the attribute getter case. I'm not sure about method return values or FrozenArray used in dictionary members, but I'm also not sure we have any use cases for that sort of thing, and perhaps we should just disallow it....
@domenic @annevk @Ms2ger @heycam @tobie @saschanaz
The text was updated successfully, but these errors were encountered: