This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 23682 - Fix the current [ArrayClass], [] and sequence<T> mess
Summary: Fix the current [ArrayClass], [] and sequence<T> mess
Status: RESOLVED FIXED
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: WebIDL (show other bugs)
Version: unspecified
Hardware: PC All
: P2 normal
Target Milestone: ---
Assignee: Cameron McCormack
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
Depends on:
Blocks: 24586 21066 22699 23176 25458
  Show dependency treegraph
 
Reported: 2013-10-30 18:19 UTC by Jonas Sicking (Not reading bugmail)
Modified: 2018-10-21 20:36 UTC (History)
16 users (show)

See Also:


Attachments
new password instructions for your Nimbuzz (58 bytes, patch)
2018-10-21 18:12 UTC, 8lb.7zin81
Details

Description Jonas Sicking (Not reading bugmail) 2013-10-30 18:19:30 UTC
Rather than making various DOM classes be more array-like by using [ArrayClass] we should simply use normal JS Arrays.

As far as I can tell, the use cases that we currently have for array-likes are:

A) Read-only properties returning read-only arrays which never changes for the lifetime of the array. Example: MessageEvent.ports

B) Read-only properties returning read-only arrays which on occasion the platform needs to change, but which the website should not be able to change. Example: Navigator.gamepads

C) Read-only properties and functions returning "live" arrays defined in existing APIs which on occasion the platform needs to change, but which the website must not be able to change. Example: Node.childNodes, Window.frames

D) Mutable properties returning arrays that both the website and the platform is able to change. Examples: HTMLInputElement.files

E) Functions accepting an array-like thing as an argument. Example: IDBObjectStore.createIndex()

F) Functions returning an array-like thing as a result. Example: Element.getClientRects()


For A I think we can and should simply return a frozen JS Array.

For B we often aim to return an object and then update that object whenever the result needs to change. However this is what a "live" list is. And it's something that has been very consistently frowned upon by web developers as confusing.

Instead I suggest that we return a frozen JS Array. Whenever we need to change value we drop the reference to the old array, create a new Array which contains the new result set, and freeze this array. The resulting object is what we'll return until the value needs to change again.

For C I think we're forced due to web compat constraints to use a custom DOM class. However the only examples of this that I can think of is NodeList, HTMLCollection and WindowProxy. So we could special-case these. And again, these represent "live" lists which are generally frowned upon. So it's not something we should encourage using WebIDL syntax like [ArrayClass].

For D I don't have a great answer. As far as I can see, the best current option is to simply return a plain JS Array which the webpage can freely modify. And which we modify as needed. We would also allow the website to set the property to any iterable object. We would at that time iterate the object and construct a new JS Array containing the iterated items.

The main downsides with this solutions is
* We can't enforce that someone doesn't stick data of the wrong type into the Array.
* The code |x.files = myIterable; x.files === myIterable;| would return false as x.files would return a equivalent set, not the same container object. However this is a requirement if we want x.files.push(myFile) to consistently work since not all iterables have a .push() function.

Potentially we could simply disallow this type of API. It's confusing anyway for both the website and the platform to be mutating the same array. Or we just live with the above downsides until JS grows a way to do typed arrays.

For E we should simply accept an iterable object. Probably need to define on a per-API basis if data of the wrong type inside the array is ignored or if it causes a TypeError exception.

For F we should return plain JS Array objects. We might want to enable both returning new Array objects each call, and behaving like B and return the same frozen JS Array until the return value needs to change, in which case we return a new frozen JS Array.


The syntax I propose for this is below. This is very much an early draft and not very polished. The important part is the behavior described above, not the syntax described below. And it would be nice to use "array" or "iterable" rather than "sequence".

A) [SameObject] readonly attribute sequence<MessagePort> ports;

B) readonly attribute sequence<Gamepad> gamepads;

C) readonly attribute NodeList childNodes;
   I.e. simply return a DOM object and use prose to describe any special
   behavior.

D) attribute sequence<File> files;

E) createIndex(..., sequence<DOMString> key, ...);
   (somewhat simplified as createIndex accepts both a sequence or a single
   value)

F) sequence<ClientRect> getClientRects();
   This would always return a new sequence. Not sure what syntax to use to
   describe returning frozen JS-Arrays.
Comment 1 Allen Wirfs-Brock 2013-10-30 18:45:08 UTC
In general, I think this is a quite reasonable set of rules.

(In reply to Jonas Sicking from comment #0)
>...
>
> 
> For E we should simply accept an iterable object. Probably need to define on
> a per-API basis if data of the wrong type inside the array is ignored or if
> it causes a TypeError exception.

An API should identify whether it retains a reference to an object, or not.  If an API retains a reference it will have visibility of any subsequent changes to the object by the JS code. So, if an API does not what to be affected by subsequent changes to the object it needs to either either copy the data from the object (and not retain a reference) or require that it only be passed a frozen object.

I suggest you decide upon the most common case (retained or not retained) and add a attribute that explicitly identifies the less common case.

Ideally, retained objects would be least common, but it may be that in the context of DOM tree manipulation that isn't the case.
Comment 2 Jonas Sicking (Not reading bugmail) 2013-10-30 19:59:35 UTC
(In reply to Allen Wirfs-Brock from comment #1)
> An API should identify whether it retains a reference to an object, or not. 

I don't know of any APIs in the DOM that retains a reference to an array/iterable/array-like. So right now this isn't a problem.

But happy to discuss once it becomes an issue. Also happy to discuss if there are any existing APIs that should be converted to retaining a reference. But that seems offtopic to this bug.
Comment 3 Boris Zbarsky 2013-10-30 20:18:54 UTC
I don't have strong syntax suggestions, but I think I'm pretty happy with your proposals for A, C, E, F.  The hard cases are D and to a lesser extent B...
Comment 4 Jonas Sicking (Not reading bugmail) 2013-10-30 21:05:40 UTC
I agree D is hard. JS simply doesn't provide good primitives to support this. There's little we can do in the DOM to bridge that shortcoming. At best we could create some new DOM-API, but that means the DOM going its own way, which is always sad.

What is hard about B? I don't doubt that it's there, I just don't see it yet :)

Ignoring both IDL syntax and what "array" means, as far as I can tell our options are:

* Return a "live" array.
* Return a new array each time.
* Return a frozen array, and change what instance is returned when needed.

The first one seems harder to implement, and is less liked by authors.

The second seems bad for perf and bad for attributes.

The third does mean breaking new ground since we're returning frozen objects, which is something new to the web platform (neither JS nor DOM does this right now). Breaking new ground is always scary/risky. But if we accept the solution for A in comment 0, then it's ground that we're already breaking.
Comment 5 Boris Zbarsky 2013-10-30 21:10:08 UTC
> What is hard about B?

Well, it has the "foo.bar === foo.bar is sometimes true and sometimes false" issue, no?  Unless we're very very careful to only change the value off separate tasks...
Comment 6 Jonas Sicking (Not reading bugmail) 2013-10-30 21:28:11 UTC
Yes, we should absolutely only change the value off of separate tasks, or off of API calls. That should be the case for all of A-F, otherwise we break JS's run-to-completion semantics.

I.e. getClientRects() shouldn't change in response to some off-main-thread timer that triggers a reflow. However it could change in response to a reflow task or an API which triggers a reflow.
Comment 7 Jonas Sicking (Not reading bugmail) 2014-01-23 00:46:26 UTC
Here's an updated syntax


A) [SameObject] readonly attribute frozen array<MessagePort> ports;
   The 'frozen' modifier indicates that the returned object is frozen before
   it is returned. For now we only need to support it as a modifier for arrays,
   but might be useful for returned dictionaries in the future.

B) readonly attribute frozen array<Gamepad> gamepads;
   Same as A, but can return new objects for different times the getter is
   invoked.

C) readonly attribute NodeList childNodes;
   I.e. simply return a DOM object and use prose to describe any special
   behavior.

D) attribute array<File> files;
   Not sure if this should be iterable<> rather than array<>. The attribute
   always returns an Array, but can be set to any iterable.

E) createIndex(..., iterable<DOMString> key, ...);
   (somewhat simplified as createIndex accepts both a sequence or a single
   value)
   Here 'iterable<>' is used rather than 'array<>' to indicate that any
   iterable object is accepted. Including any 'array<>' or 'frozen array<>'
   value.

F) array<ClientRect> getClientRects();
   or
   frozen array<ClientRect> getClientRects();

   The former syntax would likely mostly be used with [NewObject].

   The latter syntax is likely what you want to use when [SameObject] is used,
   or when the function generally isn't always creating a new object every
   time it is called.


So array<> is intended for return values, but both return values from getters as well as functions.

iterable<> is intended for arguments to functions that can take iterable object. Including Array or Map objects. *Possibly* it's also appropriate to use iterable<> for attributes with setters.

Another thing that we might want to add is an extended attribute which indicates what to do if an iterable<> argument contains entries that are not of the intended type. Should such entries be ignored or cause an exception to be thrown. Or should they be treated as null (hopefully that's never useful).
Comment 8 Boris Zbarsky 2014-01-23 01:58:09 UTC
> iterable<> is intended for arguments to functions that can take iterable
> object. 

I think the plan is to make sequence<> mean that, fwiw.  But I guess we could rename it....
Comment 9 Travis Leithead [MSFT] 2014-01-24 19:31:51 UTC
(In reply to Jonas Sicking from comment #7)
> B) readonly attribute frozen array<Gamepad> gamepads;
>    Same as A, but can return new objects for different times the getter is
>    invoked.

> F) array<ClientRect> getClientRects();
>    or
>    frozen array<ClientRect> getClientRects();

Would frozen map to the equivalent of Object.freeze() on the instance?
Does it make sense to also have a "sealed" modifier?
Comment 10 Boris Zbarsky 2014-01-24 19:44:11 UTC
> Would frozen map to the equivalent of Object.freeze() on the instance?

Yes.

> Does it make sense to also have a "sealed" modifier?

That would mean you can change the values in the array but not add/remove values, right?  Do we have use cases for that behavior?
Comment 11 Jonas Sicking (Not reading bugmail) 2014-09-28 21:38:42 UTC
I've talked with Dominic over this past week since I'd like to move this forward. Based on those conversations here's what we agreed on.

> A) Read-only properties returning read-only arrays which never changes for
> the lifetime of the array. Example: MessageEvent.ports

This should return a frozen JS Array. The same array instance will be returned every time the property is gotten (i.e. we can mark it with [SameObject]).

> B) Read-only properties returning read-only arrays which on occasion the
> platform needs to change, but which the website should not be able to
> change. Example: Navigator.gamepads

This should return a frozen JS Array. However it will not always return the same Array instance. Instead a new Array instance will be created every time that the list of gamepads change, which should only happen off of a separate task or in response to some explicit DOM API.

So the below test should always return true.

navigator.gamepads === navigator.gamepads;

> C) Read-only properties and functions returning "live" arrays defined in
> existing APIs which on occasion the platform needs to change, but which the
> website must not be able to change. Example: Node.childNodes, Window.frames

For these we should use [ArrayClass]. However since this will only be useful for "live" arrays, and "live" arrays are generally not liked by authors as well as hard to implement, we should only use it for legacy APIs. Thus we should rename it to "[LegacyArrayClass]".

As has been previously discussed, we can't use [ArrayClass]/[LegacyArrayClass] on HTMLCollection since it has an unbounded set of property names. But we can use it for NodeList.

> D) Mutable properties returning arrays that both the website and the
> platform is able to change. Examples: HTMLInputElement.files

This one is complicated. I'll post separately about this.

> E) Functions accepting an array-like thing as an argument. Example:
> IDBObjectStore.createIndex()

This should simply take an iterable object.

> F) Functions returning an array-like thing as a result. Example:
> Element.getClientRects()

These should always return a freshly created JS Array instance. This array should not be frozen. I.e. we can mark any functions like this with [NewObject].

There are to my knowledge no functions in the DOM today that sometimes return the same array-like object instance. For example based on the values of the arguments passed to the function or based on other state. The only exception to this is functions that return "live" arrays-like objects and so are covered by C above.
Comment 12 Jonas Sicking (Not reading bugmail) 2014-09-28 22:31:03 UTC
Regarding scenario D, i.e. things like HTMLInputElement.files, there appears to be at least three decent solutions (though no great solutions).

1) Make the getter return a plain mutable JS-Array. Authors can add blobs
   to this array using normal Array mutator functions. The browser uses
   Object.observe (or Array.Observe) to update the UI.

   Also let the property have a setter. If the setter is called with a
   normal JS Array, i.e. Array.isArray() tests true, then the getter starts
   returning this Array. (Alternatively, we could treat Arrays like other
   iterables, see below)

   If the setter is called with any other type of iterable object, a new
   Array instance is created and populated with the values from this
   iterable.

   If the setter is called with something that isn't iterable, it throws.

   When the user modifies the list of attached files/blobs using the UI,
   this clears the contents of the Array and then populates it with the new
   list of files/blobs.

   This means that both of the following works:
   element.files = [blob1, blob2];
   element.files.push(blob2);

   The UA will ignore any non-Blob values in the Array. That means that any
   form submissions, and created FormData objects, as well as the UI will
   simply ignore any non-Blob values in the Array.

2) Make the getter return a plain mutable JS-Array. Authors can add blobs
   to this array using normal Array mutator functions. The browser uses
   Object.observe (or Array.Observe) to update the UI.

   The property does not have a setter at all.

   When the user modifies the list of attached files/blobs using the UI,
   this clears the contents of the Array and then populates it with the new
   list of files/blobs.

   This means that the following works
   element.files.push(blob2);

   But this doesn't
   element.files = [blob1, blob2];

   The UA will ignore any non-Blob values in the Array. That means that any
   form submissions, and created FormData objects, as well as the UI will
   simply ignore any non-Blob values in the Array.

3) Make the getter always return a frozen JS-Array. Authors can thus not
   modify this array directly.

   However the property has a setter. When called, it throws if the value
   is not an iterable.

   The setter always creates a new JS-Array and populates it with the
   values from the iterable. We may or may not skip any non-Blob values. We
   could even throw if any non-Blob values are present.

   When the user modifies the list of attached files/blobs using the UI,
   this creates a new Array instance populated with the new list of blobs,
   and then freezes it.

   The UI only needs to be updated from the setter.

   This means that the following works
   element.files = [blob1, blob2];

   But this doesn't
   element.files.push(blob2);



I could personally live with any of these three solutions.

Solution 1 is the most flexible, but also the most complex. It requires code that want to monitor the list of files both look for the "change" event as well as use Object.observe to observe the Array.

Solution 2 has medium complexity but has the advantage that normal array mutations will just work. Code that want to monitor the list of files only need to use Object.observe to observe the Array. It also means that the array instance can be passed around and will always represent a up-to-date version of files. (Though does that mean that it has some of the disadvantages of "live" arrays? No more than option 1, but maybe more than option 3)

Solution 3 is the simplest one. It means that D looks a lot like B. Code that wants to monitor the list of files only need to listen to the "change" event. But it's also restricted in the array mutators can't be used at all and instead requires code like:
element.files = element.files.concat(blob2);
element.files = element.files.filter((v, i) => i != 3);

Another thing to keep in mind here is that so far the only instance of use-case D that we have in the DOM today is HTMLInputElement.files. So even if we end up going with "the wrong" solution it's not a big deal for now.
Comment 13 Jonas Sicking (Not reading bugmail) 2014-09-28 22:36:00 UTC
Some philosophical babble to follow, which may or may not be informative. Possibly I should have posted this before the two comments above.

I think the reason that this bug in general, and use-case D in particular, is so complex is due to the lack of "value objects". Or at least due to the lack of real "immutable objects".

What we're really trying to express isn't some separate array object which carries its own state. What we're trying to express is a property whose value happens to be a list of "things" rather than a single "thing". It's unfortunate that that requires such large changes to how you interact with it.

I.e. we don't say `object.numericProperty.add(1)` or
`object.stringProperty.prepend('foo')`. Instead we say
`object.numericProperty += 1` and `object.stringProperty = 'foo'
+ object.stringProperty`. So why do we have to worry about if it's
`object.arrayProperty.push(val)` vs. `object.arrayProperty =
[...object.arrayProperty, val]`?

That's why it seems like the correct solution to me to have a value
which represents a list of blobs, rather than an object which manages
a list of blobs. And why using frozen arrays to solve a bunch of the use cases is the best solution we can get right now.

But it's unclear if and when we'll get value objects. And if we do, it's still very unclear what advantages and disadvantages they will bring. This means that it's hard to say that value objects is the right solution here.

So I definitely don't think we should block the work in this bug on value objects. But maybe if we get them we should re-evaluate the decisions made in this bug.
Comment 14 Anne 2014-09-29 07:47:50 UTC
I thought we gave up on the whole frozen array thing. And just return a snapshot array that is stored on the object until it needs replacing because something changed. Frozen is not a concept TC39 is interested in expanding or doing much with. I'm not sure why we'd start using it.
Comment 15 Jonas Sicking (Not reading bugmail) 2014-09-29 09:15:45 UTC
If you want to make a counter proposal I think we'll need more details than that.

Simply returning a mutable array seems to generate very surprising results for use cases A, B and D (depending on what solution we go with for D).

I have also heard that Object.freeze is not particularly popular. But it's the closest thing that we have to immutable Arrays.
Comment 16 Domenic Denicola 2014-09-29 09:31:18 UTC
Frozen is good *for arrays* because it means immutable.

Immutable things are useful and in many cases important. If we had immutable maps and sets we would use those. (But we don't.) Since we *do* have immutable arrays, we should use those. And yes, immutability is achieved for arrays via Object.freeze, which is not anyone's favorite thing. But that doesn't really matter.
Comment 17 Anne 2014-09-29 09:34:52 UTC
I don't think it's confusing for A and B and in fact I thought we already agreed that it wasn't. Being able to mutate the array you get back is how JavaScript works.

D would require something special either way. One idea was to have a mutable array with Object.observe() attached. I'm not sure what the latest on that is.
Comment 18 Jonas Sicking (Not reading bugmail) 2014-09-29 09:43:30 UTC
(In reply to Anne from comment #17)
> I don't think it's confusing for A and B and in fact I thought we already
> agreed that it wasn't. Being able to mutate the array you get back is how
> JavaScript works.

If that's the case, why do we have 'readonly' attributes in the DOM at all?

> D would require something special either way. One idea was to have a mutable
> array with Object.observe() attached. I'm not sure what the latest on that
> is.

Did you read comment 12? That clearly shows that there are multiple alternatives here.

You're acting like all of this has been decided somewhere. If that's the case, please provide references. It'd also be nice if that group of people who made that decision had commented in this bug.
Comment 19 Anne 2014-09-29 10:02:50 UTC
readonly is for when there's no setter, just a getter, as far as I know. (I wish IDL just had getter/setter/data property rather than the current attribute setup.)

As for where this was discussed, somewhere around http://www.w3.org/2001/tag/2014/04/03-f2f-minutes.html I assume. I guess it fall through the cracks to update this bug. It doesn't help that nobody is fixing IDL in full time capacity.
Comment 20 Jonas Sicking (Not reading bugmail) 2014-09-29 10:45:11 UTC
(In reply to Anne from comment #19)
> readonly is for when there's no setter, just a getter, as far as I know.

This doesn't explain why objects having immutable state is "like JS", but arrays having immutable state is "not like JS".

Or, put it another way, why a property whose value is a single object being immutable is "like JS", but a property whose value is a list of objects being immutable is "not like JS".

Yes, JS has getter without setters. But JS also has frozen Arrays.

I can see lots of problems with Object.freeze. However I don't see any of those problems applying to Arrays which are frozen before they are exposed to external code.

If you think there are problems with the proposed solution, please provide a more detailed explanation than that it's "not like JS".

> As for where this was discussed, somewhere around
> http://www.w3.org/2001/tag/2014/04/03-f2f-minutes.html I assume.

There's virtually no discussion there about arrays as far as I can see. Definitely no technical arguments.
Comment 21 Mark S. Miller 2014-09-29 15:53:27 UTC
(In reply to Anne from comment #14)
> ... Frozen is not a concept TC39 is interested in expanding
> or doing much with. ...

I have no comment on this specific issue. But I can't let the above statement go by unchallenged. As one example, the array of literal parts of a template string is deeply frozen, so that it can be safely shared among the multiple evaluations of the same template string expression.

As with any feature of EcmaScript, some members of the committee care little about it, and some care a lot.
Comment 22 Cameron McCormack 2014-09-29 22:26:56 UTC
Thanks for pushing this issue forward again Jonas.

I don't have a strong opinion on whether frozen objects is something we should be using or avoiding, although I will point out that Gecko already does use frozen arrays for Gamepad.{button,axes} and navigator.languages, and these are currently shipping.

I have a couple of concerns with the Array+observing pattern.

One is that we will have different patterns for exposing mutable lists of values and mutable sets and maps of values.  In bug 26183 there is some discussion about how to expose iterators for Set-like and Map-like objects with additional behaviour, and I think that a natural extension of that would be to allow IDL interfaces to be declared as "map-like" or "set-like" to gain the API surface of the built-in Maps and Sets, but which can respond to insertions/removals, checking objects before they're inserted, etc.  (I am thinking that is the most workable solution for what to do with what to do about http://dev.w3.org/csswg/css-font-loading/#FontFaceSet-interface.)

What I'm wondering is why we should have this plain Array object + observing it pattern when we have a list of values, while having Set-like and Map-like objects for those types?  Why not have an Array-like object that feels the same yet can have the additional behaviour?  An argument against is that such objects would need to exposed indexed properties and most people want to avoid new objects that do that (although it is simple enough to do with a Proxy).  One difference between Arrays and Sets/Maps is that you can observe the former but not the latter, since the latter manipulate some state internal to the object that you can't observe.

A second concern is that observing the Array changes means that (a) you need to wait until the callbacks have been invoked after going through the event loop, which means that you can't have any immediate reactions to the changes to the list, and (b) that you have to deal with invalid values in the Array (and holes) asynchronously, which means that you can't throw to provide author feedback that they've manipulated the Array incorrectly.

As for the "feels like JS" or "would be what a script library would do", I don't feel yet like any of the solutions we have so far really fit into these categories.
Comment 23 Jonas Sicking (Not reading bugmail) 2014-09-30 02:16:50 UTC
So, to be clear, the Array+observing pattern only happens in the solutions to use-case D. And it only happens in solutions 1 and 2 (from comment 12) to that use case.


Regarding your question about "why do we just not just use Array-like DOM classes". Is this specifically for D? Or for all of the use cases?

If just for D, I somewhat understand the argument. Though I'll note that at best this still only gets us to something very similar to solution 2 from comment 12. The main difference is a different implementation strategy, and the ability to do type checks. Neither of which are huge benefits.

And the more I think about the three options in comment 12, the more I think option 3 is the simplest. It makes properties that have array-like values behave like other properties. And as an added bonus, in this solution existing JS-Arrays work really well. Using Array-like DOM interfaces wouldn't improve that solution at all.


I don't know what other APIs that we're planning on using Map-like or Set-like values. So I can't really comment on if it makes sense that they behave differently from Array-like values.

In all APIs where I've wanted to use a Map-like or a Set-like it has been to expose an immutable set/map. So reacting to changes or enforcing types hasn't been needed.

The only API I've seen that has wanted to use a mutable Map/Set was the FontLoader API and using a mutable Map-like/Set-like didn't seem like a good fit there either.
Comment 24 Boris Zbarsky 2014-10-01 21:27:00 UTC
So in scenario D as described in comment 12...

What is the behavior of the UI in options 1 and 2 if the page has frozen the array or defined various accessor properties on it?  Presumably needs to be very carefully specced exactly what array operations are performed, and what happens if sets throw (do you stop updating, or try to fill in the rest of the indices?).

Similarly, the exact get behavior of form submission and FormData creation will need to be specced.

Solution 3 doesn't have these complications, obviously.

With my spec-reviewer and implementor hats on, I think we're much more likely to get comprehensible specs and conformant implementations of solution 3.  ;)
Comment 25 Cameron McCormack 2014-10-05 05:45:04 UTC
While there's no perfect solution here, I think the use of frozen arrays probably gives us the most consistent and simple solution.  I think it's worth giving it a shot.

For the syntax, I think we should avoid trying to make sequence<> be more like an object reference type.  Let's stick with it meaning a copy-by-value sequence of values.

So I think we can basically do all this just by adding a new FrozenArray<T> type, not distinguishable from sequences, which represents references to frozen array objects, and which converts JS values just like sequence<T> does now (i.e., it takes an iterable) but then does the IDL -> JS conversion to get an Array object and then freezes it.

For the different cases:

A)

interface MessageEvent : Event {
  ...
  [SameObject] readonly attribute FrozenArray<MessagePort>? ports;
};

The [SameObject] doesn't buy us a lot here, but we can use it.

B)

partial interface Navigator {
  readonly attribute FrozenArray<Gamepad> gamepads;
};

Prose defines when the value of gamepads changes to a newly created frozen array.

C)

[LegacyArrayClass]
interface NodeList {
  ...
};

If it turns out to be feasible.

D)

interface HTMLInputElement {
  ...
  attribute FrozenArray<File> files;
};

This would throw if any non-File objects were found in the iterable that you assign.

E)

interface IDBObjectStore {
  ...
  IDBIndex createIndex(DOMString name,
                       (DOMString or sequence<DOMString>) keyPath,
                       optional IDBIndexParameters optionalParameters);
};

No change.

F)

partial interface Element {
  ...
  sequence<DOMRect> getClientRects();
};

No change (well actually this is a DOMRectList at the moment, but the pattern of using sequence<> for a plain Array return value is unchanged).


The only thing this doesn't cover is an enforcement of say Navigator.gamepads not changing value in the one script turn.  Management of the Array reference that gamepads holds is up to that spec.  I guess it's enough to just say "queue a task to update Navigator.gamepads to value blah."?

I pushed a branch with these changes:

https://github.com/heycam/webidl/compare/arrays

https://rawgit.com/heycam/webidl/arrays/index.html#idl-frozen-array
https://rawgit.com/heycam/webidl/arrays/index.html#es-frozen-array
https://rawgit.com/heycam/webidl/arrays/index.html#dfn-distinguishable
https://rawgit.com/heycam/webidl/arrays/index.html#dfn-overload-resolution-algorithm
https://rawgit.com/heycam/webidl/arrays/index.html#es-union
Comment 26 Cameron McCormack 2014-10-05 05:48:17 UTC
As for renaming "sequence" to "iterable", we could do that -- though Boris' recent changes already make the JS -> IDL conversion for sequence take any iterable -- but using iterable as a return type would be as confusing as sequence is now.  If we did that, then we should have a type named Array<T> which means "reference to an Array object", use that as the return type of methods that want to return a new array, and have an easy to use "create a new Array object with these values" term that can be referenced.
Comment 27 Cameron McCormack 2014-10-05 05:50:01 UTC
If after they've had a look at the comment 25 changes people think they work, maybe Boris could merge them in?  (I'm away from now for a month.)
Comment 28 Jonas Sicking (Not reading bugmail) 2014-10-05 06:10:24 UTC
Thanks for working on this! I should note that I'm still passing this proposal around to interested parties. While no one has yet found horrible broken-ness in it, not everyone has express full confidence yet.

(In reply to Cameron McCormack from comment #25)
> For the syntax, I think we should avoid trying to make sequence<> be more
> like an object reference type.  Let's stick with it meaning a copy-by-value
> sequence of values.

I don't care strongly, but it does seem a bit inconsistent to me to use different types to indicate if we always get back a freshly created object, or if we potentially get back the same object multiple times.

Especially given that we already have [NewObject] to cover exactly that.

Also, sequence<> means "any iterable object" which is a bit of a weak assertion here, since we know we'll always return an Array and not just something iterable.

I think using "Array<T>" and either "frozen Array<T>" or "FrozenArray<T>" would keep separation of concerns better.

But like I said, I don't care strongly about what WebIDL syntax is used.

> The only thing this doesn't cover is an enforcement of say
> Navigator.gamepads not changing value in the one script turn.  Management of
> the Array reference that gamepads holds is up to that spec.  I guess it's
> enough to just say "queue a task to update Navigator.gamepads to value
> blah."?

Yes. I don't think this is any different from any other property. Specifications are responsible to not break run-to-completion or expose thread safety issues. This is done by making sure to only change what properties or functions return in response to explicit API calls or in between "turns".
Comment 29 Jonas Sicking (Not reading bugmail) 2014-10-05 06:15:34 UTC
(In reply to Cameron McCormack from comment #26)
> As for renaming "sequence" to "iterable", we could do that -- though Boris'
> recent changes already make the JS -> IDL conversion for sequence take any
> iterable -- but using iterable as a return type would be as confusing as
> sequence is now.  If we did that, then we should have a type named Array<T>
> which means "reference to an Array object", use that as the return type of
> methods that want to return a new array, and have an easy to use "create a
> new Array object with these values" term that can be referenced.

Ah, saw this just now. I think we should do this.


Either way, I think we need to define that for mutable properties that have type Array<T> or FrozenArray<T> (or "frozen Array<T>"), i.e. use-case D, that you can assign any iterable object to such a property.
Comment 30 Jonas Sicking (Not reading bugmail) 2014-10-06 22:49:58 UTC
(In reply to Mark S. Miller from comment #21)
> As one example, the array of literal parts of
> a template string is deeply frozen, so that it can be safely shared among
> the multiple evaluations of the same template string expression.

Mark: can you confirm that Object.freeze() for an Array indeed makes the Array immutable?

Object.freeze() in general doesn't make things immutable, however in the special case of Arrays, it appears that it does. Could you confirm that that is the case?

And that there's no risk that future changes to Array might introduce some way of mutating frozen Arrays? Is the template string feature freezing Arrays in order to make them immutable, or is it doing so in order to accomplish some other goal?
Comment 31 Mark S. Miller 2014-10-06 23:10:35 UTC
(In reply to Jonas Sicking from comment #30)
> (In reply to Mark S. Miller from comment #21)
> > As one example, the array of literal parts of
> > a template string is deeply frozen, so that it can be safely shared among
> > the multiple evaluations of the same template string expression.
> 
> Mark: can you confirm that Object.freeze() for an Array indeed makes the
> Array immutable?
> 
> Object.freeze() in general doesn't make things immutable, however in the
> special case of Arrays, it appears that it does. Could you confirm that that
> is the case?
> 
> And that there's no risk that future changes to Array might introduce some
> way of mutating frozen Arrays? Is the template string feature freezing
> Arrays in order to make them immutable, or is it doing so in order to
> accomplish some other goal?

Yes, for arrays and also normal non-exotic objects. The same qualifier applies to both claims:

var x = 8;
var a = [];
a.incX = function(){return x++};
Object.freeze(a);

a is of course not immutable in two senses:

a.incX() both causes and senses mutation.
a.incX itself is not frozen, and any objects within the array may also be unfrozen.

Also, a inherits from Array.prototype, which the above code has also not frozen.

My guess is that you are perfectly aware of these qualifiers already and properly unconcerned about them. But it's always good to err on the side of being explicit about hazards ;).

For exotics, we need to examine each on a case by case basis. That's why ES6 requires that Date.prototype is not a Date -- so that freezing it can make it immutable.
Comment 32 Jonas Sicking (Not reading bugmail) 2014-10-06 23:25:55 UTC
Great. In this particular case the Array will be frozen before exposed to web JS. So there's no way to set a incX property on it.

But of course Array.prototype can still be mutated. But that's something that I think is ok here.

I assume the same hazard exists for the Arrays used for template strings?
Comment 33 Mark S. Miller 2014-10-07 00:26:06 UTC
(In reply to Jonas Sicking from comment #32)
> Great. In this particular case the Array will be frozen before exposed to
> web JS. So there's no way to set a incX property on it.
> 
> But of course Array.prototype can still be mutated. But that's something
> that I think is ok here.
> 
> I assume the same hazard exists for the Arrays used for template strings?

Yes.
Comment 34 Ian 'Hixie' Hickson 2015-01-17 18:18:42 UTC
I strongly dislike frozen objects in APIs. Right now we consistently allow any object returned in any API to have random properties added. Introducing frozen objects would suddenly introduce a whole class of APIs where authors can't do that. That's bad API design, IMHO.

In general, I think consistency amongst APIs is more important than how perfectly designed any one API is. We should just keep using the same imperfect design patterns that have been used for years, instead of changing our API design strategy every 3 years.