Re: Defining a constructor for Element and friends
On Fri, 16 Jan 2015 20:08:30 +0100, Domenic Denicola d...@domenic.me wrote: From: Anne van Kesteren [mailto:ann...@annevk.nl] How can that work if the custom element constructor needs to look in the registry to find its name? Pick a name at random? Nah, it just automatically starts acting like HTMLQuoteElement: the localName option becomes required. See https://github.com/domenic/element-constructors/blob/5e6e00bb2bb525f04c8c796e467f103c8aa0bcf7/element-constructors.js#L229-L233 https://github.com/domenic/element-constructors/blob/5e6e00bb2bb525f04c8c796e467f103c8aa0bcf7/element-constructors.js#L51-L54 Consider if HTML adds a new element that uses the same interface as another element, let's say foobar, so that both foobar and data use HTMLDataElement. When this happens, new HTMLDataElement() starts throwing? Similarly, if HTML is changed such that an element changes to use a more specific interface, e.g. ruby changes from HTMLElement to HTMLRubyElement, then new HTMLElement('ruby') will start throwing? If so, it seems it removes some flexibility with how HTML uses interfaces. In particular many elements use HTMLElement and it should be possible to change them to use a more specific interface. How do you envision to solve this? Should we assign element-specific interfaces to all post-HTML4 elements now, just in case? Or make new HTMLElement('ruby') create an HTMLRubyElement? Something else? -- Simon Pieters Opera Software
Re: Defining a constructor for Element and friends
On Mon, Jan 19, 2015 at 11:01 AM, Simon Pieters sim...@opera.com wrote: If so, it seems it removes some flexibility with how HTML uses interfaces. In particular many elements use HTMLElement and it should be possible to change them to use a more specific interface. How do you envision to solve this? Should we assign element-specific interfaces to all post-HTML4 elements now, just in case? Or make new HTMLElement('ruby') create an HTMLRubyElement? Something else? You are correct that the forward compatibility aspects of Domenic's proposal are lacking. I think we should introduce classes on a one-per-element basis. And we should probably lock down some of the generic interfaces or at least restrict them to only allow elements with dashes in their name. -- https://annevankesteren.nl/
RE: Defining a constructor for Element and friends
From: Anne van Kesteren [mailto:ann...@annevk.nl] How can that work if the custom element constructor needs to look in the registry to find its name? Pick a name at random? Nah, it just automatically starts acting like HTMLQuoteElement: the localName option becomes required. See https://github.com/domenic/element-constructors/blob/5e6e00bb2bb525f04c8c796e467f103c8aa0bcf7/element-constructors.js#L229-L233 https://github.com/domenic/element-constructors/blob/5e6e00bb2bb525f04c8c796e467f103c8aa0bcf7/element-constructors.js#L51-L54
RE: Defining a constructor for Element and friends
I've updated my element constructors sketch at https://github.com/domenic/element-constructors/blob/master/element-constructors.js with a design that means no subclasses of HTMLElement (including the built-in elements) need to override their constructor or [Symbol.species](). It also uses an options argument for the constructors so it is more extensible in the future (e.g. Yehuda's attributes (or was it properties?) argument). It mostly doesn't delve into custom elements, but does contain a small sample to illustrate how their don't need to override the constructor or [Symbol.species] either, and how their constructor works exactly the same as that of e.g. HTMLParagraphElement. One interesting thing that falls out of the design is that it's trivial to allow a custom element class to be registered for multiple names; it requires no more work on the part of the class author than writing a class that corresponds to a single name, and is painless to use for consumers. -Original Message- From: Domenic Denicola [mailto:d...@domenic.me] Sent: Friday, January 9, 2015 20:01 To: Anne van Kesteren; WebApps WG; www-...@w3.org Subject: RE: Defining a constructor for Element and friends OK, so I've thought about this a lot, and there was some discussion on an unfortunately-TC39-private thread that I want to put out in the open. In [1] I outlined some initial thoughts, but that was actually a thread on a different topic, and my thinking has evolved. [1]: http://lists.w3.org/Archives/Public/public-webapps/2015JanMar/0035.html I was writing up my ideas in an email but it kind of snowballed into something bigger so now it's a repo: https://github.com/domenic/element-constructors One primary concern of mine is the one you mention: whether it is acceptable to have an element whose name is a, namespace is the HTML namespace, and interface is Element I do not really think this is acceptable, and furthermore I think it is avoidable. In the private thread Boris suggested a design where you can do `new Element(localName, namespace, prefix)`. This seems necessary to explain how `createElementNS` works, so we do want that. He also suggested the following invariants: 1. The localName and namespace of an element determine its set of internal slots. 2. The return value of `new Foo` has `Foo.prototype` as the prototype. I agree we should preserve these invariants, but added a few more to do with keeping the existing (localName, namespace) - constructor links solid. I've outlined the added invariants in the readme of the above repo. Other points of interest: - Explainer for a very-recently-agreed-upon ES6 feature that helps support the design: https://github.com/domenic/element-constructors/blob/master/new-target-explainer.md - Jump straight to the code: https://github.com/domenic/element-constructors/blob/master/element-constructors.js - Jump straight to the examples of what works and what doesn't: https://github.com/domenic/element-constructors/blob/master/element-constructors.js#L194 One ugly point of my design is that the constructor signature is `new Element(localName, document, namespace, prefix)`, i.e. I require the document to be passed in. I am not sure this is necessary but am playing it safe until someone with better understanding tells me one way or the other. See https://github.com/domenic/element-constructors/issues/1 for that discussion. --- As for how this applies to custom elements, in the private thread Boris asked: what is the use case for producing something that extends HTMLImageElement (and presumably has its internal slots?) but doesn't have img as the tag name and hence will not have anything ever look at those internal slots? Elsehwere on this thread or some related one IIRC he pointed out code that looks at the local name, finds img, and casts to the C++ backing representation of HTMLImageElement. So from what I am gathering in his view the parts of the platform that treat img elements specially currently work by checking explicitly that something has local name img (and HTML namespace). From a naïve authoring point of view that seems suboptimal. I'd rather be able to do `class MyImg extends HTMLImageElement { constructor(document) { super(document); } }` and have MyImg instances treated specially by the platform in all the ways img currently is. Or, for an easier example, I'd like to be able to do `class MyQ extends HTMLQuoteElement { constructor(document) { super(document); } }` and have `(new MyQ()).cite` actually work, instead of throw a cite getter incompatible with MyQ error because I didn't get the HTMLQuoteElement internal slots. The logical extension of this, then, is that if after that `document.registerElement` call I do `document.body.innerHTML = my-q cite=fooblah/my-q` I'd really like to see `document.querySelector(my-q).cite` return `foo`. However this idea that we'd like custom elements which inherit from
Re: Defining a constructor for Element and friends
On Wed, Jan 14, 2015 at 5:26 AM, Domenic Denicola d...@domenic.me wrote: How do you propose having a private constructor API? Same as how we explain Window, Navigator, and friends, no? By requiring that UAs pass in a magic token web developers cannot get a hold of. This is one of those only makes sense to a C++ programmer things. I thought we got past that reasoning a while ago. -- https://annevankesteren.nl/
Re: Defining a constructor for Element and friends
* Domenic Denicola wrote: From: Bjoern Hoehrmann [mailto:derhoe...@gmx.net] I know that this a major concern to you, but my impression is that few if any other people regard that as anything more than nice to have, especially if you equate explaining with having a public API for it. How do you propose having a private constructor API? How do you propose instances of the objects even existing at all, if there is no constructor that creates them? This is one of those only makes sense to a C++ programmer things. I think it is misleading to describe something as a design goal if it is not widely accepted as a design goal, and my impression is that this is not widely accepted as a design goal. I also think it is entirely normal to deal with objects you have no way of creating on your own. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de D-10243 Berlin · PGP Pub. KeyID: 0xA4357E78 · http://www.bjoernsworld.de Available for hire in Berlin (early 2015) · http://www.websitedev.de/
RE: Defining a constructor for Element and friends
From: Bjoern Hoehrmann [mailto:derhoe...@gmx.net] I know that this a major concern to you, but my impression is that few if any other people regard that as anything more than nice to have, especially if you equate explaining with having a public API for it. How do you propose having a private constructor API? How do you propose instances of the objects even existing at all, if there is no constructor that creates them? This is one of those only makes sense to a C++ programmer things.
Re: Defining a constructor for Element and friends
* Domenic Denicola wrote: That kind of breaks the design goal that we be able to explain how everything you see in the DOM was constructed. How did the parser (or document.createElement(NS)) create a HTMLUnknownElement, if the constructor for HTMLUnknownElement doesn't work? I know that this a major concern to you, but my impression is that few if any other people regard that as anything more than nice to have, especially if you equate explaining with having a public API for it. -- Björn Höhrmann · mailto:bjo...@hoehrmann.de · http://bjoern.hoehrmann.de D-10243 Berlin · PGP Pub. KeyID: 0xA4357E78 · http://www.bjoernsworld.de Available for hire in Berlin (early 2015) · http://www.websitedev.de/
Re: Defining a constructor for Element and friends
On Jan 13, 2015, at 10:22 AM, Boris Zbarsky bzbar...@mit.edu wrote: On 1/13/15 1:18 PM, Ryosuke Niwa wrote: I agree. It's unusual for a constructor of a super class to automatically instantiate an arbitrary subclass based on its arguments. And we usually solve that convenience problem by introducing a factory class/function. While true, I do think there's a problem here. Consider this: var element = new HTMLElement(somename); OK, so the web author is not being very forward-compatible in that they're not using a tag name with a - in it. But then they put it in the DOM and it acts just like a span, and they're happy with that. Shouldn't we throw in this case because the concert type of somename is HTMLUnknownElement? Then we want to add a somename tag in the spec, and suddenly this JS throws. This is a different order of breakage than what you get from just having new semantics for the somename tag. ... In any case, it's a bit of a niggling worry for me because it can increase the chance that adding things to HTML breaks websites. I think if we threw an exception on every attempt to create an element with a name without - (as they're HTMLUnknownElement anyway), then we can probably mitigate this forward compatibility issue. Hopefully, authors won't be creating HTMLUnknownElement all that often... - R. Niwa
Re: Defining a constructor for Element and friends
On 1/13/15 12:05 PM, Domenic Denicola wrote: From: Boris Zbarsky [mailto:bzbar...@mit.edu] var x = new Element(a, http://www.w3.org/1999/xhtml;) The idea is that your above example throws, preserving the invariant. That sounds annoying Presumably this means that document.createElement(NS) looks up the registry constructor and calls it, instead of calling the Element constructor and having _that_ delegate to the registry. But it also means that user-space code that has to create an HTML element generically now has to go through document.createElement instead of being able to do |new HTMLElement(a)|, right? I agree that this does allow us to preserve the invariant in question, but I question whether it's worth it. I was assuming non-exotic [[HasInstance]] Ah, yeah, with Web IDL that's not a good assumption but I agree it's ambiguous given that. I meant prototype chain. Probably I also implicitly meant internal slots. Those aren't the same thing at all, right? The prototype chain has absolutely nothing to do with internal slots, unless we're assuming some sort of vanilla untouched tate of the world. The one piece of terminology that I think we have so far that I understand is what it means for an object to implement an interface. At least Web IDL has a normative requirement on such a thing being defined (e.g. see http://heycam.github.io/webidl/#es-operations step 4 of the behavior), presumably in terms of some sort of branding. Heh, I don't really understand what that means; I indeed noticed that Web IDL uses it without defining it. Right now the concept of what it means for a platform object to implement an interface is sort of up to the implementation. The Web IDL spec assumes that for each platform object there is a set of interfaces it implements, subject to some constraints described in http://heycam.github.io/webidl/#es-platform-objects, but even basic restrictions like if A inherits from B and the platform object implements A then it must implement B don't seem to be explicitly stated We should really fix that. I too would guess that it's branding-related. That's certainly how implementations treat it in practice. Note that in any sensible scheme I can think of, subclass instances (for a subclass that calls super() in the constructor) also get the brands of their superclass. Yes, absolutely. So it makes sense to me to talk about things implementing Element but not any interface that has Element as an inherited interface. That would correspond to is an Element but not any specific subclass of Element. Could use a shorter way of saying it, for sure. Don't think this attempt at pinning down terminology works for user-defined subclasses of Element. E.g. as far as I can tell, `new (class X extends Element {})()` has only the Element brand but no other brand (since X didn't install any brands itself). But I would say that it's an own-instance of X instead of an own-instance of Element. OK. That makes sense; I'm not trying to redefine the concept of own-instance. I'm just trying to figure out what the right concepts are to define. The concept of implements an interface has to do with branding and guaranteed existence of internal slots and whatnot. The concept of has Element as its primary interface also promises something about the initial value of __proto__. The concept of own-instance, if we define it as you did above (was created by) would also guarantee certain things about branding and internal slots. It would also make some guarantees about the prototype, assuming no one sets __proto__ after that... Really, this idea of primary interface and your idea of own-instance seem fairly similar, right? Except that primary interface can only refer to Web IDL interfaces, not user-defined subclasses... or something. -Boris
Re: Defining a constructor for Element and friends
On 1/13/15 1:18 PM, Ryosuke Niwa wrote: I agree. It's unusual for a constructor of a super class to automatically instantiate an arbitrary subclass based on its arguments. And we usually solve that convenience problem by introducing a factory class/function. While true, I do think there's a problem here. Consider this: var element = new HTMLElement(somename); OK, so the web author is not being very forward-compatible in that they're not using a tag name with a - in it. But then they put it in the DOM and it acts just like a span, and they're happy with that. Then we want to add a somename tag in the spec, and suddenly this JS throws. This is a different order of breakage than what you get from just having new semantics for the somename tag. In some cases, this is a problem no matter what; e.g. if somename is actually img or canvas then the layout is totally different too, not just the semantics. But there are other cases where the layout is not that different from a vanilla inline though maybe we don't really plan to add any more of those? In any case, it's a bit of a niggling worry for me because it can increase the chance that adding things to HTML breaks websites. -Boris
RE: Defining a constructor for Element and friends
From: Boris Zbarsky [mailto:bzbar...@mit.edu] Terminology: In what follows I use 'own-instances of X' to mean objects where obj.constructor === X, That doesn't make much sense to me as a useful test, since it's pretty simple to produce, say, an HTMLParagraphElement instance on the web that has whatever .constructor value you desire, right? Unless the intent is to talk about this in some setup where no one has messed with any of the objects or something. I guess the intent here is that we want obj to have been constructed via X in some sense? Modulo whatever the story is for the things that have NamedConstructors. Right, I was being imprecise. I am not sure how to make it precise. Maybe something like was created via `new X` assuming `X` doesn't use return-override and we buy in to the story where all instances are created via constructors (even those originating from the browser instead of the author). Anyway, modulo exactly what this definition should be, let's talk about the proposed the constructor of an element determines its set of internal slots invariant. I'm OK with that if we include constructor arguments. Otherwise, I don't see how it can make sense. In particular, say someone does: var x = new Element(a, http://www.w3.org/1999/xhtml;) or whatever argument order we do. Per invariant 1 in your document, this should get the internal slots of an HTMLAnchorElement, right? Per invariant 2, x.constructor == Element, and in particular x.__proto__ == Element.prototype. So suddenly we have an HTMLAnchorElement as an own-instance of Element, which I think violates your invariant 3. The idea is that your above example throws, preserving the invariant. Since there is already an entry in the registry for (a, HTML_NS), and it's not Element, the constructor fails. (Except when invoked as part of a super() chain from the actual HTMLAnchorElement constructor.) Details: - https://github.com/domenic/element-constructors/blob/88dbec40494aefc03825e00ff1bfc8d5e3f02f1e/element-constructors.js#L62-L66 - https://github.com/domenic/element-constructors/blob/88dbec40494aefc03825e00ff1bfc8d5e3f02f1e/element-constructors.js#L211-L215 Moving on to invariant 4, is that instances in terms on instanceof (which can be almost tautologically true, given what Web IDL says about [[HasInstance]] on interface objects), or in terms of what the proto chain looks like, or something else? In particular, the x defined above doesn't have HTMLElement.prototype on its proto chain, but is instanceof HTMLElement... I was assuming non-exotic [[HasInstance]], but I agree it's ambiguous given that. I meant prototype chain. Probably I also implicitly meant internal slots. The one piece of terminology that I think we have so far that I understand is what it means for an object to implement an interface. At least Web IDL has a normative requirement on such a thing being defined (e.g. see http://heycam.github.io/webidl/#es-operations step 4 of the behavior), presumably in terms of some sort of branding. Heh, I don't really understand what that means; I indeed noticed that Web IDL uses it without defining it. I too would guess that it's branding-related. Note that in any sensible scheme I can think of, subclass instances (for a subclass that calls super() in the constructor) also get the brands of their superclass. So it makes sense to me to talk about things implementing Element but not any interface that has Element as an inherited interface. That would correspond to is an Element but not any specific subclass of Element. Could use a shorter way of saying it, for sure. Don't think this attempt at pinning down terminology works for user-defined subclasses of Element. E.g. as far as I can tell, `new (class X extends Element {})()` has only the Element brand but no other brand (since X didn't install any brands itself). But I would say that it's an own-instance of X instead of an own-instance of Element.
RE: Defining a constructor for Element and friends
From: Boris Zbarsky [mailto:bzbar...@mit.edu] But it also means that user-space code that has to create an HTML element generically now has to go through document.createElement instead of being able to do |new HTMLElement(a)|, right? That seems totally fine to me though. The idea of a string-based factory for when you don't know what constructor you want to use has precedent all over software design. Those aren't the same thing at all, right? The prototype chain has absolutely nothing to do with internal slots, unless we're assuming some sort of vanilla untouched tate of the world. Agreed. However, in a normal situation---where all constructors in the chain call super() appropriately, and nobody __proto__-munges, and so on---they should be the same. That's why I'm saying that implicitly it was probably also part of what I was thinking when writing that. Really, this idea of primary interface and your idea of own-instance seem fairly similar, right? Except that primary interface can only refer to Web IDL interfaces, not user-defined subclasses... or something. Yeah, that sounds about right. Honestly, own-interface was just my attempt at capturing a JavaScript concept that I work with pretty often (this over here is a Foo; this over here is a Bar).
Re: Defining a constructor for Element and friends
On Jan 13, 2015, at 9:25 AM, Domenic Denicola d...@domenic.me wrote: From: Boris Zbarsky [mailto:bzbar...@mit.edu] But it also means that user-space code that has to create an HTML element generically now has to go through document.createElement instead of being able to do |new HTMLElement(a)|, right? That seems totally fine to me though. The idea of a string-based factory for when you don't know what constructor you want to use has precedent all over software design. I agree. It's unusual for a constructor of a super class to automatically instantiate an arbitrary subclass based on its arguments. And we usually solve that convenience problem by introducing a factory class/function. - R. Niwa
Re: Defining a constructor for Element and friends
On Jan 13, 2015, at 8:26 PM, Domenic Denicola d...@domenic.me wrote: From: Bjoern Hoehrmann [mailto:derhoe...@gmx.net] I know that this a major concern to you, but my impression is that few if any other people regard that as anything more than nice to have, especially if you equate explaining with having a public API for it. How do you propose having a private constructor API? I don't think we need to make the constructor of HTMLUnknownElement private. It certainly isn't today. We just need to throw whenever it's called. How do you propose instances of the objects even existing at all, if there is no constructor that creates them? This is one of those only makes sense to a C++ programmer things. We can model it as a constructor that takes a private symbol only the DOM implementation has access to, and throws whenever this symbol is not passed in as an argument. - R. Niwa
RE: Defining a constructor for Element and friends
From: Ryosuke Niwa [mailto:rn...@apple.com] Shouldn't we throw in this case because the concert type of somename is HTMLUnknownElement? Yes, that's exactly the current design. Hidden a bit: https://github.com/domenic/element-constructors/blob/master/element-constructors.js#L4 This still leaves the potential hazard of someone doing `new HTMLUnknownElement(somename)` and their code breaking later once someone becomes a real tag... hopefully the Unknown is a bit more of a deterrent though? (It'd be nice if HTMLElement weren't a global and you had to do `import HTMLElement from html/parser-internals` or something. Ah well.)
Re: Defining a constructor for Element and friends
On Jan 13, 2015, at 11:31 AM, Boris Zbarsky bzbar...@mit.edu wrote: On 1/13/15 1:33 PM, Ryosuke Niwa wrote: Shouldn't we throw in this case because the concert type of somename is HTMLUnknownElement? Oh, hmm. Yes, I guess so. That's very non-obvious to an author. Even less obvious because for some tag names using the HTMLElement constructor is in fact correct. The end result here is really something that ends up all self-consistent and preserving our invariants and the obvious reaction to it will be WAT?. Not least because the actual interface used by various HTML elements is pretty random. Want a basefont? HTMLElement. Want a bgsound? HTMLUnknownElement. Want a rb? HTMLUnknownElement. Want a big? HTMLElement… Indeed the developer ergonomics here is terrible :( I think if we threw an exception on every attempt to create an element with a name without - (as they're HTMLUnknownElement anyway) I'm not sure I follow. How do you propose an s be constructed via constructor, say? What about address? Sorry, I meant that only non-standard (i.e. unknown) HTML elements. If we're disallowing upgrades as we've been arguing, then we should probably disallow HTMLUnknownElements altogether regardless of whether they contain - in their names or not. - R. Niwa
Re: Defining a constructor for Element and friends
On 1/13/15 1:33 PM, Ryosuke Niwa wrote: Shouldn't we throw in this case because the concert type of somename is HTMLUnknownElement? Oh, hmm. Yes, I guess so. That's very non-obvious to an author. Even less obvious because for some tag names using the HTMLElement constructor is in fact correct. The end result here is really something that ends up all self-consistent and preserving our invariants and the obvious reaction to it will be WAT?. Not least because the actual interface used by various HTML elements is pretty random. Want a basefont? HTMLElement. Want a bgsound? HTMLUnknownElement. Want a rb? HTMLUnknownElement. Want a big? HTMLElement... I think if we threw an exception on every attempt to create an element with a name without - (as they're HTMLUnknownElement anyway) I'm not sure I follow. How do you propose an s be constructed via constructor, say? What about address? -Boris
Re: Defining a constructor for Element and friends
On Jan 13, 2015, at 10:45 AM, Domenic Denicola d...@domenic.me wrote: From: Ryosuke Niwa [mailto:rn...@apple.com] Shouldn't we throw in this case because the concert type of somename is HTMLUnknownElement? Yes, that's exactly the current design. Hidden a bit: https://github.com/domenic/element-constructors/blob/master/element-constructors.js#L4 This still leaves the potential hazard of someone doing `new HTMLUnknownElement(somename)` and their code breaking later once someone becomes a real tag... hopefully the Unknown is a bit more of a deterrent though? Or, we could always throw an exception in the constructor of HTMLUnknownElement so that nobody could do it. It would mean that libraries and frameworks that do support custom elements without - would have to use document.createElement but that might be a good thing since they wouldn't be doing that in the first place. - R. Niwa
RE: Defining a constructor for Element and friends
From: Ryosuke Niwa [mailto:rn...@apple.com] Or, we could always throw an exception in the constructor of HTMLUnknownElement so that nobody could do it. It would mean that libraries and frameworks that do support custom elements without - would have to use document.createElement but that might be a good thing since they wouldn't be doing that in the first place. That kind of breaks the design goal that we be able to explain how everything you see in the DOM was constructed. How did the parser (or document.createElement(NS)) create a HTMLUnknownElement, if the constructor for HTMLUnknownElement doesn't work?
Re: Defining a constructor for Element and friends
On Jan 13, 2015, at 10:53 AM, Domenic Denicola d...@domenic.me wrote: From: Ryosuke Niwa [mailto:rn...@apple.com] Or, we could always throw an exception in the constructor of HTMLUnknownElement so that nobody could do it. It would mean that libraries and frameworks that do support custom elements without - would have to use document.createElement but that might be a good thing since they wouldn't be doing that in the first place. That kind of breaks the design goal that we be able to explain how everything you see in the DOM was constructed. How did the parser (or document.createElement(NS)) create a HTMLUnknownElement, if the constructor for HTMLUnknownElement doesn't work? I didn't know that we had such a design goal. In general, backwards and forwards compatibilities are much more important than design purity. - R. Niwa
Re: Defining a constructor for Element and friends
On 1/11/15 2:33 PM, Domenic Denicola wrote: Terminology: In what follows I use 'own-instances of X' to mean objects where obj.constructor === X, That doesn't make much sense to me as a useful test, since it's pretty simple to produce, say, an HTMLParagraphElement instance on the web that has whatever .constructor value you desire, right? Unless the intent is to talk about this in some setup where no one has messed with any of the objects or something. I guess the intent here is that we want obj to have been constructed via X in some sense? Modulo whatever the story is for the things that have NamedConstructors. as distance from 'instances of X' which means objects for which obj instanceof X. OK. Anyway, modulo exactly what this definition should be, let's talk about the proposed the constructor of an element determines its set of internal slots invariant. I'm OK with that if we include constructor arguments. Otherwise, I don't see how it can make sense. In particular, say someone does: var x = new Element(a, http://www.w3.org/1999/xhtml;) or whatever argument order we do. Per invariant 1 in your document, this should get the internal slots of an HTMLAnchorElement, right? Per invariant 2, x.constructor == Element, and in particular x.__proto__ == Element.prototype. So suddenly we have an HTMLAnchorElement as an own-instance of Element, which I think violates your invariant 3. Moving on to invariant 4, is that instances in terms on instanceof (which can be almost tautologically true, given what Web IDL says about [[HasInstance]] on interface objects), or in terms of what the proto chain looks like, or something else? In particular, the x defined above doesn't have HTMLElement.prototype on its proto chain, but is instanceof HTMLElement... I'd like to understand what you mean by interface is Element here, exactly. I'm just quoting Anne :). My interpretation is that the (object representing the) element is an own-instance of Element. This _really_ requires pinning down what exactly own-instance means. Let's get our terminology in order. ;) The one piece of terminology that I think we have so far that I understand is what it means for an object to implement an interface. At least Web IDL has a normative requirement on such a thing being defined (e.g. see http://heycam.github.io/webidl/#es-operations step 4 of the behavior), presumably in terms of some sort of branding. So it makes sense to me to talk about things implementing Element but not any interface that has Element as an inherited interface. That would correspond to is an Element but not any specific subclass of Element. Could use a shorter way of saying it, for sure. -Boris
RE: Defining a constructor for Element and friends
From: Boris Zbarsky [mailto:bzbar...@mit.edu] That said, I do have one question already: what does the term own-instances mean in that document? Explained at the top: Terminology: In what follows I use 'own-instances of X' to mean objects where obj.constructor === X, as distance from 'instances of X' which means objects for which obj instanceof X. whether it is acceptable to have an element whose name is a, namespace is the HTML namespace, and interface is Element I'd like to understand what you mean by interface is Element here, exactly. I'm just quoting Anne :). My interpretation is that the (object representing the) element is an own-instance of Element. From a naïve authoring point of view that seems suboptimal. I'd rather be able to do `class MyImg extends HTMLImageElement { constructor(document) { super(document); } }` and have MyImg instances treated specially by the platform in all the ways img currently is. I don't quite see the issue here. Presumably the HTMLImageElement constructor passes img as the localName to the HTMLElement constructor, so your MyImg would get img as the localName, right? Ah, right, of course. I am skipping a few steps and my steps are wrong, so my concern wasn't well-founded. My vision was that MyImg had local name my-img via some custom element stuff, as with the my-q example below. I agree that it works though as stated. Or, for an easier example, I'd like to be able to do `class MyQ extends HTMLQuoteElement { constructor(document) { super(document); } }` and have `(new MyQ()).cite` actually work, instead of throw a cite getter incompatible with MyQ error because I didn't get the HTMLQuoteElement internal slots. This should totally work, of course. Why wouldn't it, exactly? Given the subclassing proposal on the table in ES6 right now, it would work splendidly, since the HTMLQuoteElement constructor is what would perform the object allocation and it would pass along q as the localName. (Though actually, HTMLQuoteElement is excitingly complicated, because both q and blockquote would use that constructor, so it would need to either require one of those two strings be passed in, or default to q unless blockquote is passed in or something.) Right, so I should have actually written `class MyQ extends HTMLQuoteElement { constructor(document) { super(q, document); } }`. My repo's .js file covers the case of HTMLQuoteElement specifically to illustrate how to deal with classes that cover more than one local name. And yeah, I agree it works again. The other stuff, which is really about custom elements, I'll spin off into a new thread.
Re: Defining a constructor for Element and friends
On 1/9/15 8:01 PM, Domenic Denicola wrote: I was writing up my ideas in an email but it kind of snowballed into something bigger so now it's a repo: https://github.com/domenic/element-constructors Domenic, thanks for putting this together. Caveat: I won't get a chance to read through this carefully until Monday. Responses below are based on just what's in the mail. That said, I do have one question already: what does the term own-instances mean in that document? whether it is acceptable to have an element whose name is a, namespace is the HTML namespace, and interface is Element I'd like to understand what you mean by interface is Element here, exactly. One ugly point of my design is that the constructor signature is `new Element(localName, document, namespace, prefix)`, i.e. I require the document to be passed in. I am not sure this is necessary It's necessary, but I think the document should be allowed, but not required. As in, the signature should be: new Element(localName, namespace, prefix, document). (though maybe prefix should come after document; hard to say). If the document is undefined, then we get a document as follows: Element is a function, so has an associated Realm. This Realm's [[globalThis]] is a Window. This Window has a .document; use that. The only reason we need the ability to specify a document at all is that there are documents around that are not inside a browsing context (XHR responseXML, for example) and we need to be able to create elements that have those documents as an ownerDocument. But those document's don't have their own global/Realm and hence don't have separate instances of the Element constructor. I commented on the github issue in a bit less detail. what is the use case for producing something that extends HTMLImageElement (and presumably has its internal slots?) but doesn't have img as the tag name and hence will not have anything ever look at those internal slots? Elsehwere on this thread or some related one IIRC he pointed out code that looks at the local name, finds img, and casts to the C++ backing representation of HTMLImageElement. So from what I am gathering in his view the parts of the platform that treat img elements specially currently work by checking explicitly that something has local name img (and HTML namespace). Yes. And that's true of not just implementations but also specifications. The entire HTML specification is written in terms of local name tests, for example. From a naïve authoring point of view that seems suboptimal. I'd rather be able to do `class MyImg extends HTMLImageElement { constructor(document) { super(document); } }` and have MyImg instances treated specially by the platform in all the ways img currently is. I don't quite see the issue here. Presumably the HTMLImageElement constructor passes img as the localName to the HTMLElement constructor, so your MyImg would get img as the localName, right? Can you explain what the concern is here? Now I do think there's an authoring problem where if you want to do a fancyimage that's treated like img by the platform you have a problem. But that doesn't seem to be what you're talking about... or are you? Or, for an easier example, I'd like to be able to do `class MyQ extends HTMLQuoteElement { constructor(document) { super(document); } }` and have `(new MyQ()).cite` actually work, instead of throw a cite getter incompatible with MyQ error because I didn't get the HTMLQuoteElement internal slots. This should totally work, of course. Why wouldn't it, exactly? Given the subclassing proposal on the table in ES6 right now, it would work splendidly, since the HTMLQuoteElement constructor is what would perform the object allocation and it would pass along q as the localName. (Though actually, HTMLQuoteElement is excitingly complicated, because both q and blockquote would use that constructor, so it would need to either require one of those two strings be passed in, or default to q unless blockquote is passed in or something.) The logical extension of this, then, is that if after that `document.registerElement` call I do `document.body.innerHTML = my-q cite=fooblah/my-q` Ah, here we go. This is the part where the trouble starts, indeed. This is why custom elements currently uses q is=my-q for creating custom element subclasses of things that are more specific than HTMLElement. Yes, it's ugly. But the alternative is at least major rewrite of the HTML spec and at least large parts of Gecko/WebKit/Blink. :( I can't speak to whether Trident is doing a bunch of localName checks internally. However this idea that we'd like custom elements which inherit from existing elements to have their internal slots ties in to the whole upgrading mess Right, which you get for free with the q is=my-q setup, since you just get the slots for q and then the upgrade just has to worry about your proto
RE: Defining a constructor for Element and friends
OK, so I've thought about this a lot, and there was some discussion on an unfortunately-TC39-private thread that I want to put out in the open. In [1] I outlined some initial thoughts, but that was actually a thread on a different topic, and my thinking has evolved. [1]: http://lists.w3.org/Archives/Public/public-webapps/2015JanMar/0035.html I was writing up my ideas in an email but it kind of snowballed into something bigger so now it's a repo: https://github.com/domenic/element-constructors One primary concern of mine is the one you mention: whether it is acceptable to have an element whose name is a, namespace is the HTML namespace, and interface is Element I do not really think this is acceptable, and furthermore I think it is avoidable. In the private thread Boris suggested a design where you can do `new Element(localName, namespace, prefix)`. This seems necessary to explain how `createElementNS` works, so we do want that. He also suggested the following invariants: 1. The localName and namespace of an element determine its set of internal slots. 2. The return value of `new Foo` has `Foo.prototype` as the prototype. I agree we should preserve these invariants, but added a few more to do with keeping the existing (localName, namespace) - constructor links solid. I've outlined the added invariants in the readme of the above repo. Other points of interest: - Explainer for a very-recently-agreed-upon ES6 feature that helps support the design: https://github.com/domenic/element-constructors/blob/master/new-target-explainer.md - Jump straight to the code: https://github.com/domenic/element-constructors/blob/master/element-constructors.js - Jump straight to the examples of what works and what doesn't: https://github.com/domenic/element-constructors/blob/master/element-constructors.js#L194 One ugly point of my design is that the constructor signature is `new Element(localName, document, namespace, prefix)`, i.e. I require the document to be passed in. I am not sure this is necessary but am playing it safe until someone with better understanding tells me one way or the other. See https://github.com/domenic/element-constructors/issues/1 for that discussion. --- As for how this applies to custom elements, in the private thread Boris asked: what is the use case for producing something that extends HTMLImageElement (and presumably has its internal slots?) but doesn't have img as the tag name and hence will not have anything ever look at those internal slots? Elsehwere on this thread or some related one IIRC he pointed out code that looks at the local name, finds img, and casts to the C++ backing representation of HTMLImageElement. So from what I am gathering in his view the parts of the platform that treat img elements specially currently work by checking explicitly that something has local name img (and HTML namespace). From a naïve authoring point of view that seems suboptimal. I'd rather be able to do `class MyImg extends HTMLImageElement { constructor(document) { super(document); } }` and have MyImg instances treated specially by the platform in all the ways img currently is. Or, for an easier example, I'd like to be able to do `class MyQ extends HTMLQuoteElement { constructor(document) { super(document); } }` and have `(new MyQ()).cite` actually work, instead of throw a cite getter incompatible with MyQ error because I didn't get the HTMLQuoteElement internal slots. The logical extension of this, then, is that if after that `document.registerElement` call I do `document.body.innerHTML = my-q cite=fooblah/my-q` I'd really like to see `document.querySelector(my-q).cite` return `foo`. However this idea that we'd like custom elements which inherit from existing elements to have their internal slots ties in to the whole upgrading mess, which seems quite hard to work around. So maybe it is not a good idea? On the other hand upgrading might be borked no matter what, i.e. it might not be possible at all to make upgraded elements behave anything like parsed-from-scratch elements. (I am planning to think harder about the upgrading problem but I am not hopeful.)
Re: Defining a constructor for Element and friends
On 1/7/15 6:17 AM, Anne van Kesteren wrote: The main tricky thing here I think is whether it is acceptable to have an element whose name is a, namespace is the HTML namespace, and interface is Element. That depends on what you mean by interface is Element. If you mean that it has all the internal slots HTMLAnchorElement has but its prototype is Element.prototype, I think that may be fine. Libraries might get confused if you pass them elements like this, but that just comes down to don't create elements like this as a guideline, right? If you mean not having the internal slots HTMLAnchorElement has, then that would involve a good bit of both specification and implementation work. Specifically: 1) Pretty much the entire HTML spec is written in terms of tag names, and the operations it performs often assume some sort of state being stored on elements with those tag names. Conceptually this is being stored in internal slots (though of course in actual implementations slots can mean hashtable entries in some hashtable or whatnot). Significant spec work would need to happen to deal with situations where the element has some tagname but not the corresponding internal slots. 2) In specifications, there are assumptions about particular tag names having particular internal slots. For example, you often get code like this (not actual code in either one, but intended to get the flavor across) at least in WebKit and Gecko: void doWhatever(Element* element) { if (element-isHTML() element-tagName() == input) { HTMLInputElement* input = static_castHTMLInputElement*(element); // Do stuff with input here. } } If we can have HTML elements which have the input tag name but aren't represented by a subclass of the C++ HTMLInputElement in the above code, you get a security bug. So codebases would have to be audited for all instances of this and similar patterns. I just did a quick check in Gecko, and we're looking at at least 500 callsites just in C++. There are probably more in privileged JavaScript that make assumptions about things based on tagname... This is why the custom elements spec ended up with the is=... business for extending nontrivial HTML elements. :( So just to check, which of these two invariant-violations are we talking about here? If we can break that invariant it seems rather easy to build the hierarchy. The HTMLElement constructor would only take a local name and always have a null prefix and HTML namespace. I think that's fine in a world where we still create an HTMLAnchorElement under the hood if you do new HTMLElement('a') but just give it HTMLElement.prototype as the proto. And HTMLAnchorElement would always be a. HTMLQuoteElement could accept an enum and we could even add dedicated constructors for q and blockquote (provided the web allows). Yeah, this would make sense to me. -Boris
Re: Defining a constructor for Element and friends
On Wed, Jan 7, 2015 at 2:32 PM, Boris Zbarsky bzbar...@mit.edu wrote: If you mean not having the internal slots HTMLAnchorElement has, then that would involve a good bit of both specification and implementation work. That is what I meant. Otherwise in order to support new Element() you'd have to support an ever growing set of more specific objects as well and layering is out of the window. However, that does indeed seem like a lot of work and it's not clear whether that actually pays off in the end :-( -- https://annevankesteren.nl/
Re: Defining a constructor for Element and friends
On 1/7/15 9:51 AM, Anne van Kesteren wrote: That is what I meant. Otherwise in order to support new Element() you'd have to support an ever growing set of more specific objects as well and layering is out of the window. Do you mean layering of implementations or specifications? For specifications, here's one way this could work with reasonable layering. DOM provides the following bits: 1) A registry mapping (namespace, localname) pairs to abstract operations that allocate an object. 2) Abstract operations that can be used by specifications built on top of DOM to register abstract operations in this registry. 3) An abstract operation that takes a list of internal slot names and returns an object which has those internal slots, plus the internal slots all elements have, plus probably the ordinary object internal slots from ES6, depending on whether Web IDL assumes these are ordinary objects. I thought ES6 had this sort of abstract operation already, but I don't see anything like it; in any case the only issue here is that this requires implementations of DOM and specifications built on top of it to agree internally on what internal slot means for elements, I agree. Specifications that define elements on top of DOM provide the following bits: 4) An abstract operation that creates an uninitialized version of their element, via calling the thing defined in #3. 5) Registration of the abstract operation defined in #4 with the registry defined in #1, whether that happens at global-setup time or when the element definition is encountered or whatever. An implementation that wants to just implement core DOM but not things built on on top of it can skip all of this machinery. An implementation that wants to support DOM but not everything on top of it (e.g. support MathML but not HTML or something) just supports the bits it wants and the registry ends up not having stuff in it that it has in other implementations. Seems ok to me. Now some questions: * Did I cover your concern about have to support an ever growing set of more specific objects? If not, can you explain what the concern is? * Without a mechanism like the above, how would one go about supporting document.createElement(NS) as it exists on the web. However, that does indeed seem like a lot of work and it's not clear whether that actually pays off in the end :-( That's hard to say without knowing what the desired payoff is. -Boris