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

Custom elements created with "new" are not :defined #1297

Closed
dominiccooney opened this issue May 21, 2016 · 11 comments · Fixed by #1309
Closed

Custom elements created with "new" are not :defined #1297

dominiccooney opened this issue May 21, 2016 · 11 comments · Fixed by #1309
Assignees
Labels
topic: custom elements Relates to custom elements (as defined in DOM and HTML)

Comments

@dominiccooney
Copy link

Compare the steps for creating a custom element is created with 'new' using the HTMLElement constructor, versus creating one with create element or upgrading one.

Both create element and upgrade set custom element state; the HTML constructor doesn't. This means custom elements created with new will not match :defined.

On the other hand, if you were to tell me that the HTMLElement constructor step 4.1 implies doing create element, I would tell you I think that implies running the author's constructor a second time. I assume step 4.1 does not mean create element specifically.

@domenic domenic added the topic: custom elements Relates to custom elements (as defined in DOM and HTML) label May 21, 2016
@domenic domenic self-assigned this May 21, 2016
@domenic
Copy link
Member

domenic commented May 21, 2016

Tentatively I think the fix is that HTMLElement() step 4 should get a new substep that sets the element's state to "custom".

Step 4.1 definitely does not imply "create an element". It's meant to be the lower-level Web IDL-ish operation for creating an instance of a given interface. (Via magic, without ever calling its constructor.)

@domenic
Copy link
Member

domenic commented May 21, 2016

This would also mean we could remove https://dom.spec.whatwg.org/#concept-create-element step 6.1.11 since it would happen automatically in 6.1.2.

@dominiccooney
Copy link
Author

This has the nice side effect that :defined coincides with the prototype being set; there's fewer observable states an element can be in.

@kojiishi
Copy link

The change looks reasonable to me. We need @rniwa's attention here as he wrote a test for this.matches(':defined') to be false in the custom constructor.

@rniwa
Copy link

rniwa commented May 23, 2016

I don't think :defined should apply to a custom element while it's still being constructed.

@domenic
Copy link
Member

domenic commented May 23, 2016

@rniwa I don't think we have a choice. Given new XTag(), the only point at which the UA gets to run code is during the super() call to HTMLElement. That's the only time we can flip the flag...

@rniwa
Copy link

rniwa commented May 24, 2016

Oh I see, that's a good point.

domenic added a commit that referenced this issue May 24, 2016
As discussed in #1297, the spec previously did not correctly set custom
element state to "custom" for the case of new CustomElement(). The
correct place to do this is in the HTMLElement constructor (invoked by
the super() call), since that is the last place user agent code is
invoked, and so our last chance to change the state. In practice, this
affects the behavior of the :defined pseudo-class.

Fixes #1297. Also fixes a typo in the same algorithms, where the
variable names "element" and "interface" were accidentally both used.
@dominiccooney
Copy link
Author

Yay!

domenic added a commit that referenced this issue May 31, 2016
As discussed in #1297, the spec previously did not correctly set custom
element state to "custom" for the case of new CustomElement(). The
correct place to do this is in the HTMLElement constructor (invoked by
the super() call), since that is the last place user agent code is
invoked, and so our last chance to change the state. In practice, this
affects the behavior of the :defined pseudo-class.

Fixes #1297. Also fixes a typo in the same algorithms, where the
variable names "element" and "interface" were accidentally both used.
@dominiccooney
Copy link
Author

Ah, I think there may be a wrinkle with this we failed to anticipate.

First, I think it's 100% right to set "custom" in the HTMLElement constructor. It's the universal, consistent point to do that.

However this has the effect that attributeChangedCallbacks and so on start happening at that point. This means that if the constructor sets an attribute, two attributeChanged callbacks will be enqueued—one by the CEReactions tickled by the attribute setting, and a second as the upgrade steps are finishing. The same goes for connectedCallback.

I think upgrade should queue those notifications before running the constructor. That way the author gets a consistent sequence of notifications.

The side effect of attribute setting in the constructor causing reactions will continue to exist though. I don't see any way out of that, given we can't hook when the author's constructor or Reflect.construct call returns without changing ES6 semantics.

@domenic domenic reopened this Jun 1, 2016
@domenic
Copy link
Member

domenic commented Jun 1, 2016

So to make sure I understand, the proposal would be to move the attributeChanged and connected calls in steps 8 and 9 of https://html.spec.whatwg.org/multipage/scripting.html#concept-upgrade-an-element to before step 3?

That seems to work OK:

  • For constructors that do nothing that triggers [CEReactions], the result will be equivalent.
  • For constructors that trigger CE reactions (in any way), the callbacks will fire at that time
    • For an element with no existing attributes whose constructor appends an attribute, attributeChangedCallback will fire a single time immediately after the setAttribute call.
    • For an element with an existing attribute A whose constructor appends an attribute B, attributeChangedCallback will fire twice, in sequence A then B, immediately after the setAttribute call.

I briefly considered the idea of saving the current set of attributes before step 3, but still firing in step 8. But then you get B then A in the last example, which is bad.

I think this is the best we can do if people do things they're explicitly advised not to do (viz. adding attributes, or in general touching the DOM in a way that would trigger CEReactions, inside constructors).

domenic added a commit that referenced this issue Jun 1, 2016
As pointed out in
#1297 (comment), the
setup introduced in #1309 allows custom element reactions to happen
during custom element constructors. This is unavoidable, but the spec
did not account for its consequences, leading to cases where a custom
element reaction would be called twice for the same attribute (or the
same element being connected). By moving these steps to the top of the
algorithm, we ensure that reactions are only enqueued for the state of
the world before the constructor runs, and that any reactions caused by
actions inside the constructor are then taken care of by the usual
mechanisms.

Closes #1297 again.
@dominiccooney
Copy link
Author

Yes, the effects you describe are correct! I think the situation for connected is similar.

annevk pushed a commit that referenced this issue Jun 6, 2016
As pointed out in
#1297 (comment), the
setup introduced in #1309 allows custom element reactions to happen
during custom element constructors. This is unavoidable, but the spec
did not account for its consequences, leading to cases where a custom
element reaction would be called twice for the same attribute (or the
same element being connected). By moving these steps to the top of the
algorithm, we ensure that reactions are only enqueued for the state of
the world before the constructor runs, and that any reactions caused by
actions inside the constructor are then taken care of by the usual
mechanisms.

Closes #1297 again.
domenic added a commit to whatwg/dom that referenced this issue Jun 7, 2016
whatwg/html#1297 led to a series of changes
which cause the custom element state to be set to "defined" by the
HTMLElement constructor. This invalidates the super()-then-throw
example. We instead replace it with an example that fails to call
super().

Fixes whatwg/html#1396.
annevk pushed a commit to whatwg/dom that referenced this issue Jun 7, 2016
whatwg/html#1297 led to a series of changes
which cause the custom element state to be set to "defined" by the
HTMLElement constructor. This invalidates the super()-then-throw
example. We instead replace it with an example that fails to call
super().

Fixes whatwg/html#1396.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: custom elements Relates to custom elements (as defined in DOM and HTML)
Development

Successfully merging a pull request may close this issue.

4 participants