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

Move the check for HTMLElement and its subclass from customElements.define to construction timing #541

Closed
rniwa opened this issue Aug 11, 2016 · 9 comments
Assignees

Comments

@rniwa
Copy link
Collaborator

rniwa commented Aug 11, 2016

Right now, CustomElementsRegistry.define is spec'ed to check whether the interface object inherits from HTMLElement interface or not.

This check is hard to implement in WebKit as is because we don't have a mechanism to check the inheritance hierarchy of interface constructor objects (we can check the inheritance hierarchy of constructed objects via branding).

It's also weird to specifically filter out HTMLElement and their sub-interfaces when we throw the same error for things like Range, Text, and Set.

My preference would be moving this check to the construction time. We can check that new.target is not HTMLElement or one of its subclasses instead.

@rniwa
Copy link
Collaborator Author

rniwa commented Aug 11, 2016

@domenic domenic self-assigned this Aug 11, 2016
@domenic
Copy link
Collaborator

domenic commented Aug 12, 2016

Hmm. So let me try to understand your proposed alternate fix. Consider the following:

<!DOCTYPE html>
<bad-1></bad-1> <!-- (1) -->
<script>
  customElements.define("bad-1", HTMLElement);
  document.createElement("bad-1"); // (2)
  new HTMLElement(); // (3)
</script>
<bad-1></bad-1> <!-- (4) -->

So with your proposal of checking new.target, clearly (3) will throw.

The other cases are less clear, but I think they work... at some point they all call Construct(HTMLElement), which throws:

  • For (1), that causes the upgrade to fail; the exception will be send to window.onerror
  • For (2), that causes createElement to synchronously re-throw the exception
  • For (4), that will cause the parser to fail, thus the parser will instead create a HTMLUnknownElement.

So I guess it works.


Can you also help me understand why this check would be easier to make at construction time, instead of at definition time? In both cases you need to compare a constructor (either C, or new.target) against the list of all constructors. In neither case can you check a constructed object, since no object has been constructed yet. It seems a bit more costly to do the check on every element creation too... That's part of why I think I came up with the currently-specced approach, of just prohibiting these "bad-1" -> HTMLElement associations from ever getting into the custom element registry in the first place.

@Mr0grog
Copy link

Mr0grog commented Aug 12, 2016

Seeing the above example, it also occurs to me that: a) any given custom element is likely to be used more than once on a page and b) its definition will often be loaded asynchronously, so when @domenic's case (1) occurs, it'll probably occur many times.

That is, if an author passes an invalid constructor to customElements.define() and it doesn’t throw until construction time, they might suddenly see 10 or 20 error events on window show up at once. That doesn’t seem so great.

@rniwa
Copy link
Collaborator Author

rniwa commented Aug 14, 2016

Can you also help me understand why this check would be easier to make at construction time, instead of at definition time? In both cases you need to compare a constructor (either C, or new.target) against the list of all constructors.

You don't because you can just check that you're constructing yourself during the construction. e.g. let's say you're trying to define a custom element with interface HTMLInputElement. Then in your implementation, either HTMLInputElement has a constructor or it doesn't.

  1. HTMLInputElement has a constructor. In this case, you can simply check that new.target isn't HTMLInputElement in the constructor.
  2. HTMLInputElement doesn't have a constructor. In this case, the attempt to Construct the element immediately fails.

So in either scenario, one doesn't have to enumerate the list of all builtin HTML*Element constructors.

@rniwa
Copy link
Collaborator Author

rniwa commented Aug 14, 2016

Also, I'm not sure how your check works against Proxy objects. What happens if you defined a Proxy to HTMLElement? Proxy could change its behavior anytime it wants so it seems any check we do during the definition time won't be sufficient.

@kojiishi
Copy link

+1 to @rniwa, turns out that the current Blink impl skips this check.

@domenic
Copy link
Collaborator

domenic commented Aug 15, 2016

You don't because you can just check that you're constructing yourself during the construction.

Ah, great, that makes sense! OK, I can change this then...

Also, I'm not sure how your check works against Proxy objects.

I think the new.target would still be the original constructor (when the proxy delegates construction). So it would still fail. But I guess we don't need to worry about it any more.

+1 to @rniwa, turns out that the current Blink impl skips this check.

I hope someone is writing web platform tests for this!! (And the Proxy case is a good one too.) I thought I even saw some from Firefox land in WPT...

@rniwa
Copy link
Collaborator Author

rniwa commented Aug 15, 2016

Also, I'm not sure how your check works against Proxy objects.

I think the new.target would still be the original constructor (when the proxy delegates construction). So it would still fail. But I guess we don't need to worry about it any more.

My point was that this case is hard/impossible to catch at definition time. e.g. constructor = new Proxy(HTMLElement, {}); Then constructor !== HTMLElement yet it would still construct HTMLElement. So the only timing at which we can catch case like this is at construction time when, as you said, new.target would tell us exactly which object we're trying to construct.

domenic added a commit to whatwg/html that referenced this issue Aug 15, 2016
Previously, this would be prevented at definition time. However, that
required extra work to know all of the possible element interfaces, and
was inconsistent with other failure cases. This moves the necessary
check to construction time, which allows it to be simplified. Fixes
WICG/webcomponents#541.
domenic added a commit to whatwg/html that referenced this issue Aug 15, 2016
Previously, this would be prevented at definition time. However, that
required extra work to know all of the possible element interfaces, and
was inconsistent with other failure cases. This moves the necessary
check to construction time, which allows it to be simplified. Fixes
WICG/webcomponents#541.
domenic added a commit to web-platform-tests/wpt that referenced this issue Aug 15, 2016
@dominiccooney
Copy link
Contributor

I support this proposal, specifically in HTMLConstructor stepts, if new.target === the active function object then throw a TypeError. Given HTMLConstructor step 4.2, which ties together the definition's local name and the active function object's allowable local names, this prevents mayhem like minting elements with unrelated local names or effectively using built-in constructors directly to create elements.

kisg pushed a commit to paul99/webkit-mips that referenced this issue Aug 31, 2016
https://bugs.webkit.org/show_bug.cgi?id=161430

Reviewed by Antti Koivisto.

Source/WebCore:

Per WICG/webcomponents#541, we must throw a TypeError when the HTMLElement constructor
is called with new.target set to itself (i.e. new HTMLElement after registering it with a custom element).

Note that we can't check this at the time of customElements.define because it could be a Proxy object.

Also see: https://html.spec.whatwg.org/#html-element-constructors

Tests: fast/custom-elements/CustomElementRegistry.html
       fast/custom-elements/HTMLElement-constructor.html

* bindings/js/JSHTMLElementCustom.cpp:
(WebCore::constructJSHTMLElement): Throw a TypeError when NewTarget is HTMLElement constructor itself.

LayoutTests:

Added test cases for defining a custom element with the HTMLElement constructor,
and making sure the HTMLElement constructor throws an exception when newTarget is itself.

* fast/custom-elements/CustomElementRegistry-expected.txt:
* fast/custom-elements/CustomElementRegistry.html:
* fast/custom-elements/HTMLElement-constructor-expected.txt:
* fast/custom-elements/HTMLElement-constructor.html:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@205263 268f45cc-cd09-0410-ab3c-d52691b4dbfc
alice pushed a commit to alice/html that referenced this issue Jan 8, 2019
Previously, this would be prevented at definition time. However, that
required extra work to know all of the possible element interfaces, and
was inconsistent with other failure cases. This moves the necessary
check to construction time, which allows it to be simplified. Fixes
WICG/webcomponents#541.
ryanhaddad pushed a commit to WebKit/WebKit that referenced this issue Dec 22, 2020
https://bugs.webkit.org/show_bug.cgi?id=161430

Reviewed by Antti Koivisto.

Source/WebCore:

Per WICG/webcomponents#541, we must throw a TypeError when the HTMLElement constructor
is called with new.target set to itself (i.e. new HTMLElement after registering it with a custom element).

Note that we can't check this at the time of customElements.define because it could be a Proxy object.

Also see: https://html.spec.whatwg.org/#html-element-constructors

Tests: fast/custom-elements/CustomElementRegistry.html
       fast/custom-elements/HTMLElement-constructor.html

* bindings/js/JSHTMLElementCustom.cpp:
(WebCore::constructJSHTMLElement): Throw a TypeError when NewTarget is HTMLElement constructor itself.

LayoutTests:

Added test cases for defining a custom element with the HTMLElement constructor,
and making sure the HTMLElement constructor throws an exception when newTarget is itself.

* fast/custom-elements/CustomElementRegistry-expected.txt:
* fast/custom-elements/CustomElementRegistry.html:
* fast/custom-elements/HTMLElement-constructor-expected.txt:
* fast/custom-elements/HTMLElement-constructor.html:


Canonical link: https://commits.webkit.org/179603@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@205263 268f45cc-cd09-0410-ab3c-d52691b4dbfc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants