Skip to content
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

Add HTMLAllCollection [[Call]], remove legacycaller special operation #2932

Merged
merged 10 commits into from
Sep 8, 2017

Conversation

tobie
Copy link
Contributor

@tobie tobie commented Aug 16, 2017

Depends on whatwg/webidl#412.

@domenic domenic added the do not merge yet Pull request must not be merged per rationale in comment label Aug 18, 2017
source Outdated
@@ -7091,6 +7092,11 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
different (ab)uses of its methods to all end up returning something, and that it can be called as
a function as an alternative to property access.</p>

<p>Objects that implement the <code>HTMLAllCollection</code> are <span data-x="legacy platform
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing word "interface"

source Outdated
<p>Objects that implement the <code>HTMLAllCollection</code> are <span data-x="legacy platform
object">legacy platform objects</span> with an additonal <a
href="#HTMLAllCollection-call">[[Call]]</a> internal method described in the subsection
below.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather the <a> be "the subsection below". Also, probably "section" instead of "subsection"; who knows how deep it is nested (and thus how many "sub"s are necessary).

source Outdated
@@ -7163,7 +7169,8 @@ interface <dfn>HTMLAllCollection</dfn> {
element(s)</span> from this <code>HTMLAllCollection</code> given <var>name</var>.</p>

<p>The <dfn><code data-x="dom-HTMLAllCollection-item">item(<var>nameOrIndex</var>)</code></dfn>
method (and the <code data-x="">legacycaller</code> behavior) must run the following steps:</p>
method (and the <a href="#HTMLAllCollection-call">[[Call]]</a> internal method) must run the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't include this here as it makes there be two normative definitions of [[Call]]

source Outdated
<ol>
<li><p>Let <var>result</var> be the result of performing the steps listed in the description of
the <code data-x="dom-HTMLAllCollection-item">item</code> method on <var>thisArgument</var> with
<var>argumentsList</var>[0] as argument.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should really refactor these steps into a separate named algorithm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, though I'm not sure of the name.

@annevk
Copy link
Member

annevk commented Aug 19, 2017

I think you have to rebase on master. Sorry about that.

Copy link
Member

@TimothyGu TimothyGu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing is that I'd like to address whatwg/webidl#407 if possible and convenient. In other words, deciding if document.all is a constructor. Chrome says yes, Firefox says no.

@@ -7101,7 +7106,7 @@ interface <dfn>HTMLAllCollection</dfn> {
readonly attribute unsigned long <span data-x="dom-HTMLAllCollection-length">length</span>;
getter <span>Element</span>? (unsigned long index);
getter (<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-namedItem">namedItem</span>(DOMString name);
legacycaller (<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-item">item</span>(optional DOMString nameOrIndex);
(<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-item">item</span>(optional DOMString nameOrIndex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add a comment in IDL about the fact that HTMLAllCollection instances implement a [[Call]]. Even better if it's linked like for "intentionally" in HTMLUnknownElement's IDL.

source Outdated
<ol>
<li><p>Let <var>result</var> be the result of <span
data-x="concept-get-all-named-or-indexed">getting the "all"-named or indexed element(s)</span>
from this <code>HTMLAllCollection</code> given <var>argumentsList</var>[0].</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If argumentsList's length is 0, it might be undefined behavior. Infra says, "The index cannot be out-of-bounds, except when used with exists." We don't use "exists" here, but there is a "was not provided" check in the algorithm. Either way I'd say it'll save us some headache if we just say "argumentsList[0] if argumentsList[0] exists".

OTOH, if argumentsList[0] is provided, it must first be converted to a IDL DOMString.

Another point of interest/headache is if argumentsList[0] is the ES value undefined. Per Web IDL right now it must be ignored as the parameter is optional. But on both Chrome and Firefox undefined is treated as 'undefined'. Additionally Firefox throws if no argument is passed to document.all or document.all.item, against the spec; i.e. it's not treating the argument as optional at all.

A piece of good news is that branding check for thisArgument as a Document seems unnecessary though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way I'd say it'll save us some headache if we just say "argumentsList[0] if argumentsList[0] exists".

That's a good point. I thought I'd somehow get away with being hand-wavy here, but looks like I didn't. :-P

Another point of interest/headache is if argumentsList[0] is the ES value undefined. Per Web IDL right now it must be ignored as the parameter is optional. But on both Chrome and Firefox undefined is treated as 'undefined'. Additionally Firefox throws if no argument is passed to document.all or document.all.item, against the spec; i.e. it's not treating the argument as optional at all.

So Firefox's behavior is actually implementing legacycaller (HTMLCollection or Element)? item(DOMString nameOrIndex);, right (i.e. without the optional argument)? Safari seems to be doing the same thing as Chrome, with a little twist: document.all.item() is the same as document.all.item(undefined) i.e. it treats it as "undefined", but document.all() always returns undefined.

Also worth noting that Chrome and Safari return undefined instead of null when they can't find a node.

Do we know what IE does?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A piece of good news is that branding check for thisArgument as a Document seems unnecessary though.

Probably worth testing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know what IE does?

I wrote a test file to be run on Edge. Seems like it does the exact same thing as Chrome.

A piece of good news is that branding check for thisArgument as a Document seems unnecessary though.

Probably worth testing.

Yep, the same behavior is present on all popular browsers regarding document.all()'s branding check.

The full results are available at: https://github.com/TimothyGu/document-all-undefined/blob/master/index.html. There are multiple instances of convergence we can spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple instances of convergence we can spec.

Do you want to just go-ahead and push those to this PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tobie No commit access to this repo 😕

Copy link
Contributor Author

@tobie tobie Aug 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TimothyGu oh, right. I assumed checking "Allow edits from maintainers" was enough. Let me fix that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tobie Thanks! @annevk has also offered write access to whatwg/html so that should be enough.

Unfortunately I'll be going on vacation until next Wednesday :( I will assign this to myself, but do not hesitate to make more improvements during this time!

@TimothyGu TimothyGu assigned bzbarsky and TimothyGu and unassigned bzbarsky Aug 25, 2017
@bzbarsky
Copy link
Contributor

Is the goal here just to keep someone else from putting legacycaller on some interface?

It's not clear to me why the spec (and possibly implementation) churn here is that desirable. In practice, at least for Gecko, we'd just leave legacycaller in our IDL, because the thing that needs to spit out the [[Call]] uses the IDL as input anyway...

@domenic
Copy link
Member

domenic commented Aug 29, 2017

Is the goal here just to keep someone else from putting legacycaller on some interface?

That, plus perhaps a general principle that constructs used for a single object on the platform should not get dedicated IDL syntax.

@bzbarsky
Copy link
Contributor

And if the worry is that people will misuse legacycaller, then note that the misuse potential of random specs defining arbitrary internal methods however they want is much greater. Having that precedent is pretty suboptimal, even if we do kind of have it for Location....

@tobie
Copy link
Contributor Author

tobie commented Aug 29, 2017

@bzbarsky this was a proposed solution to whatwg/webidl#408, based on the fact that HTMLAllCollection is the only legacycaller with [[Call]].

@bzbarsky
Copy link
Contributor

Yes, I understand that.

source Outdated

<ol>
<li><p>Let <var>result</var> be the result of <span
data-x="concept-get-all-named-or-indexed">getting the "all"-named or indexed element(s)</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argumentsList is a list of ES Values. concept-get-all-named-or-indexed takes as input an IDL value. This is the main problem with trying to hand-define this instead of using IDL: you end up having to carefully reinvent parts of the IDL spec...

@tobie
Copy link
Contributor Author

tobie commented Aug 29, 2017

I don't mind backing out of this. We just need to address whatwg/webidl#408 differently.

@bzbarsky
Copy link
Contributor

I guess we can do this on the principle that one-offs don't belong in IDL. We do need to make the actual algorithm make sense in terms of ES vs IDL values, though.

@TimothyGu
Copy link
Member

Having that precedent is pretty suboptimal

I think document.all (and Location for that matter) is understood to be legacy enough to not be a good precedent for... anything.

We do need to make the actual algorithm make sense in terms of ES vs IDL values, though.

Agreed. I'll try to fix this while tackling #2932 (comment).

@TimothyGu
Copy link
Member

I have addressed the issues @bzbarsky and I noted earlier. I have also made the change to return undefined instead of null for document.all() and document.all.item(), as is the behavior in Chrome, Edge, and released Safari. The change to the latter isn't real clean because of the fact that IDL doesn't work well with undefined, but it's there for review.

However, I also found even more complications to Web compatibility regarding this excrescence.


It seems at least Chrome supports a weird two-argument form of document.all() legacy caller:

  // If there is a second argument it is the index of the item we want.

The good news is that this feature is used very very rarely (0.000002% according to Chrome's UseCounter), and this special treatment does not extend to document.all.item(). I will just ignore this new development.


Another thing I noticed is that relatively recently WebKit changed its behavior regarding HTMLAllCollection to be spec-compliant, and therefore not consistent with any other browsers except for Firefox (mostly). 😕

CC'ing @samweinig who made that change.

@domenic
Copy link
Member

domenic commented Sep 2, 2017

@TimothyGu I think we should stick with the existing spec/what was in IDL for null vs. undefined. It's been in the spec a long time, and we shouldn't be punishing Firefox and WebKit for following the spec. We need some WPTs for it, to be sure, but we shouldn't change it.

@domenic domenic added the needs tests Moving the issue forward requires someone to write tests label Sep 2, 2017
source Outdated
@@ -7247,9 +7250,15 @@ interface <dfn>HTMLAllCollection</dfn> {
<h6 id="HTMLAllCollection-call">[[Call]] ( <var>thisArgument</var>, <var>argumentsList</var> )</h6>

<ol>
<li><p>If <var>argumentsList</var>'s length is zero, return null.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lists have sizes, not lengths, per Infra.

@domenic
Copy link
Member

domenic commented Sep 2, 2017

So things to test so far:

what else?

@TimothyGu
Copy link
Member

TimothyGu commented Sep 2, 2017

@domenic

Sounds fair.

Note that even Firefox isn't 100% compliant right now, as it throws when there's no argument. I'm unable to test latest Safari due to its unavailability on Sauce Labs and my not having a Mac.

Some more test cases I thought of:

  • (from @domenic) no [[Construct]]
  • undefined as lone argument
  • null as lone argument
  • empty string
  • not an "all"-named element but has a name
  • (from @annevk) no brand check for [[Call]]
  • (optionally) second argument doesn't make a difference

The following tests I thought of were already tested:

  • no arguments behavior for [[Call]] and item()
  • array index out of bounds
  • integer index but not array index argument (treated as ID/name; for both when ID/name exists and doesn't exist)
  • fake array index (e.g. '01', '0xff', '-1', treated as an ID/name)
  • nonexistent name/ID

@annevk
Copy link
Member

annevk commented Sep 2, 2017

I think another thing is this being incorrect. I'd imagine the current specified behavior for that is to throw as it is for other IDL-defined members. Tested by https://github.com/TimothyGu/document-all-undefined/blob/master/index.html.

@TimothyGu
Copy link
Member

@annevk Yes, that would be something to test as well. However, current Web IDL spec doesn't do such a check, nor does any of the browsers I tested.

@TimothyGu
Copy link
Member

All, a new batch of stuff.

@TimothyGu TimothyGu removed the needs tests Moving the issue forward requires someone to write tests label Sep 5, 2017
@TimothyGu
Copy link
Member

Tests are now available at web-platform-tests/wpt#7254!

source Outdated
@@ -7101,7 +7106,11 @@ interface <dfn>HTMLAllCollection</dfn> {
readonly attribute unsigned long <span data-x="dom-HTMLAllCollection-length">length</span>;
getter <span>Element</span>? (unsigned long index);
getter (<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-namedItem">namedItem</span>(DOMString name);
legacycaller (<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-item">item</span>(optional DOMString nameOrIndex);
(<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-item-noargs">item</span>();
(<span>HTMLCollection</span> or <span>Element</span>)? <span data-x="dom-HTMLAllCollection-item">item</span>(DOMString nameOrIndex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the browser incompatibility here, I think we can align with Safari (1/4) and do optional arguments. I will push a commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, Safari doesn't do optional arguments, it coerces undefined to a string. So... let's align with Firefox then.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, wait, Safari tech preview does optional arguments.

Basically given the non-interop here let's converge on Safari tech preview, which has done the best job staying up to date with the spec. Still working on it.

Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I changed this to align with the existing spec and with Safari. I think that's better than changing the spec out from under them, as they've been the ones that kept most up to date.

I'll work on the tests next.

data-x="">Function.prototype.call.call(document.all, null, "x")</code> will still search for
elements. (<code data-x="">document.all.call</code> does not exist, since <code
data-x="">document.all</code> does not inherit from <code
data-x="">Function.prototype</code>.)</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean HTMLAllCollection does not inherit from Function.prototype? How is HTMLAllCollection different from other interfaces in that regard? Can't be due to the custom [[Call]] or is it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's it.

@TimothyGu
Copy link
Member

So I changed this to align with the existing spec and with Safari. I think that's better than changing the spec out from under them, as they've been the ones that kept most up to date.

Sure, but note: all browsers were consistently against the spec regarding undefined -- Chrome, Edge, Firefox, Safari -- before WebKit became spec-compliant. It kind of sucks, but I'm of the opinion that changing WebKit back to what it was before for this particular case is easier than changing all the other browsers.

This is different from #2932 (comment), where Safari TP and Firefox act the same way. The undefined behavior is only Safari TP.

@domenic
Copy link
Member

domenic commented Sep 8, 2017

I think it's more important to play nicely with browsers that have taken pains to be spec-compliant, than it is to conform to the majority of browsers which haven't been paying attention---at least in cases where we don't believe there to be any compat concerns.

Would either of you mind reviewing this and the tests, now that I've stuck my fingers in them and so it'd be good to get a review of my work?

@TimothyGu
Copy link
Member

@domenic Your changes LGTM.

@domenic domenic merged commit 0679448 into whatwg:master Sep 8, 2017
@domenic
Copy link
Member

domenic commented Sep 8, 2017

This ended up being editorial since we preserved the spec behavior (which was last touched in 93cc395). But I'll take this opportunity to file browser bugs now that @TimothyGu has contributed a great comprehensive test suite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
do not merge yet Pull request must not be merged per rationale in comment
Development

Successfully merging this pull request may close these issues.

5 participants