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 20488 - [Custom]: Need to define what happens when nodes are adopted into or out of documents that have custom element definitions
Summary: [Custom]: Need to define what happens when nodes are adopted into or out of d...
Status: RESOLVED FIXED
Alias: None
Product: WebAppsWG
Classification: Unclassified
Component: HISTORICAL - Component Model (show other bugs)
Version: unspecified
Hardware: PC All
: P2 normal
Target Milestone: ---
Assignee: Dimitri Glazkov
QA Contact: public-webapps-bugzilla
URL:
Whiteboard:
Keywords:
: 21825 (view as bug list)
Depends on:
Blocks: 14968
  Show dependency treegraph
 
Reported: 2012-12-22 00:09 UTC by Boris Zbarsky
Modified: 2013-08-29 21:54 UTC (History)
2 users (show)

See Also:


Attachments

Description Boris Zbarsky 2012-12-22 00:09:01 UTC
Though note that adoptNode and proto chains are not really defined together to start with.  Please coordinate with the DOM folks on this?  See http://lists.w3.org/Archives/Public/www-dom/2012OctDec/0143.html
Comment 1 Dimitri Glazkov 2013-04-10 20:48:20 UTC
Bug 21485 is closely related.
Comment 2 Dimitri Glazkov 2013-05-21 21:12:28 UTC
This is a tough one. Just reading this thread makes me weep in terror.
Comment 3 Dimitri Glazkov 2013-06-19 17:02:48 UTC
*** Bug 21825 has been marked as a duplicate of this bug. ***
Comment 4 Dimitri Glazkov 2013-07-10 00:20:54 UTC
Okay, starting with the easy ones: cloning a node sort of works as spec'd. The step 2 of http://dom.spec.whatwg.org/#concept-node-clone says "Let copy be a node that implements the same interfaces as node". This implies (probably) that a new node is constructed using steps from createElementNS or something, which means that the we'll just get a different element interface if new node document's registration context is different.
Comment 5 Dominic Cooney 2013-07-10 15:30:45 UTC
(In reply to comment #4)
> Okay, starting with the easy ones: cloning a node sort of works as spec'd.
> The step 2 of http://dom.spec.whatwg.org/#concept-node-clone says "Let copy
> be a node that implements the same interfaces as node". This implies
> (probably) that a new node is constructed using steps from createElementNS
> or something, which means that the we'll just get a different element
> interface if new node document's registration context is different.

How does getting a different element interface cohere with "implements the same interfaces"?

Unrelated, but I was thinking a bit about adoptNode today. If that node is removed from its original document, does the leftDocument callback run? Since that happens after the DOM operation but before returning to script, does this mean there's a time when the element is in the new document, but with the old prototype? Or maybe there is no removed callback? (There's no _death_ callback, so it might be reasonable.)
Comment 6 Dimitri Glazkov 2013-07-12 19:27:27 UTC
(In reply to comment #5)
> (In reply to comment #4)
> > Okay, starting with the easy ones: cloning a node sort of works as spec'd.
> > The step 2 of http://dom.spec.whatwg.org/#concept-node-clone says "Let copy
> > be a node that implements the same interfaces as node". This implies
> > (probably) that a new node is constructed using steps from createElementNS
> > or something, which means that the we'll just get a different element
> > interface if new node document's registration context is different.
> 
> How does getting a different element interface cohere with "implements the
> same interfaces"?

It doesn't, that's the "sort of" part :) Do you think we need to monkeypatch this in Custom Elements spec?

> Unrelated, but I was thinking a bit about adoptNode today. If that node is
> removed from its original document, does the leftDocument callback run?
> Since that happens after the DOM operation but before returning to script,
> does this mean there's a time when the element is in the new document, but
> with the old prototype? Or maybe there is no removed callback? (There's no
> _death_ callback, so it might be reasonable.)

Right. I think we should understand what transition between registration contexts looks like. It's starting to look like death+birth to me.
Comment 7 Dominic Cooney 2013-07-19 02:03:09 UTC
I have been looking at this recently. Noting that what happens to an element's prototype when it moves between contexts isn't well defined, I think there are a  choices here:


Planet 1

Similar to having an owner document, a custom element has an "owner registration context." When it moves to a new registration context, it is un-upgraded. Then it lives an independent new life; it might be upgraded in the new context if there's a matching definition for it there.

Implied weirdness #1: When an element moves to the new context, it is still the same object. So all of the per-instance expandos and whatnot are still there. Maybe this problem is ameliorated by using ES6 symbols to record per-instance custom element state.

Implied weirdness #2: Consider this case:

docB.body.appendChild(docA.body.firstChild)

where docA and docB are associated with different registration contexts, and the each registration context has a matching definition for the element (call them type A and type B).

The order of callbacks should be:

1: type A's leftDocumentCallback
2: type B's createdCallback
3: type B's enteredDocumentCallback

The weird thing is that when delivering callback 1, the element is *in* docB. So you can't rely on an element's owner document's associated registration context being the operative registration context when running a callback. I tried implementing this in Blink and it is implementable but requires jumping through hoops.

Implied weirdness #3: Changing the definition implies changing the element's [[Prototype]]. But whenever you do that, it is going to be weird. If you do it when it moves to the new document, then callback #1 (above) is going to be dealing with an element that has the "wrong" prototype. If you do it before #2, then there's a point in time when the element is in the new context with a prototype from a different context.


Planet 2

When a custom element is upgraded, that definition is associated with it for life. If it moves to a new context, it keeps the old definition. This is nice because:

- A given element has a stable API. It is elaborated from the base type (eg HTMLButtonElement) to the custom element when the element is upgraded, but it never regresses backwards or changes.

- There's less magic involved. This is similar to what an author would get if they passed a JavaScript object from one place to another.

- All of the per-instance state of the object--expandos, etc.--continue to make sense because the definition did not change.

Implied weirdness #1: innerHTML no longer reliably round-trips things, because an element may have picked up a definition in a different context. As it is serialized and reparsed, the new element sloughs off that definition and picks up whatever is in the new context.

Implied weirdness #2: An element is running around with lots of pointers to state in a separate context via the prototype and callback closures around the associated definition. This is no less weird than the author doing this in JavaScript, I suppose.


The question is--which planet is OUR planet? We just need to find the Statue of Liberty. I think she is on Planet 2.
Comment 8 Dominic Cooney 2013-07-19 05:26:05 UTC
Looking again at the spec, it now looks prescient by not talking about node adoption. Does the following make sense? I believe it is mostly a paraphrase of that is in the spec:

There's an object called a registration context.

There's some process through which documents get a registration context. This is Bug 22466.

Since elements are created in a specific document, they are associated with a registration context. That association is *fixed for the life of the element* (I may have been overlooking this part), even though the element may move into different documents later.

When a definition is registered in a registration context, any elements waiting on that definition are upgraded.

Whether callbacks are generated depends on whether an element has a definition or not, and not what document it is in or what registration context that document has.

When all of the associated documents have gone away, it means no more custom element registrations are possible, because the route to registering something is via a document.

However an element that already had a definition can continue to go about its business independent of the (now dead, or possibly zombified) registration context.

You can see that this is a very ownerDocument and adoptNode-agnostic thing. Documents only play a role at two crucial points:

1. When a node is created, either finding a definition from the associated registration context, or adding it to the registration context's upgrade candidates.

2. When a definition is provided, connecting the dots between definition and a set of upgrade candidates.

So a registration context is more like a lottery that elements are entered into when they are created. document.register('x-x', ...) announces that elements holding an x-x ticket in the lottery are winners and will be upgraded. Winners have nothing to do with the lottery after that. And there's the obvious optimization that, if there are no more documents, no more winners can be announced, so everyone left in that lottery is a loser.

So what about the lifetimes of things? Will this result in a massive leak? I think it is OK; in Blink it will probably involve the usual trick of insinuating references between objects on the JavaScript heap to make sure certain objects survive "as least as long as" certain other objects.
Comment 9 Dimitri Glazkov 2013-07-19 19:42:54 UTC
(In reply to comment #7)

> Implied weirdness #1: innerHTML no longer reliably round-trips things,
> because an element may have picked up a definition in a different context.
> As it is serialized and reparsed, the new element sloughs off that
> definition and picks up whatever is in the new context.

It's probably not a great consolation, but we already lost the battle with type extensions:

div.setAttribute("is", "boom-tshack");
div.parentNode.innerHTML = div.parentNode.innerHTML;
 
I am leaning toward Planet 2 as well. There's discussion on bug 20567 that could  be relevant.
Comment 10 Dimitri Glazkov 2013-08-08 16:26:54 UTC
Relatedly, if we do go with Planet 2, we need to allow instantiating custom elements, registered in a different context.

https://www.w3.org/Bugs/Public/show_bug.cgi?id=22827#c2
Comment 11 Dimitri Glazkov 2013-08-29 21:54:08 UTC
At this point, the concepts in Custom Elements spec are aligned with the core DOM spec concepts, so whatever decision is taken on bug 20567, the spec will do the right thing.