Bug 112538 - Custom Elements: "readyCallback" lifecycle callback should be called.
Summary: Custom Elements: "readyCallback" lifecycle callback should be called.
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: DOM (show other bugs)
Version: 528+ (Nightly build)
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Hajime Morrita
URL:
Keywords:
Depends on: 113017
Blocks: 99688
  Show dependency treegraph
 
Reported: 2013-03-18 01:12 PDT by Hajime Morrita
Modified: 2013-03-24 18:39 PDT (History)
10 users (show)

See Also:


Attachments
Patch (50.08 KB, patch)
2013-03-21 03:14 PDT, Hajime Morrita
no flags Details | Formatted Diff | Diff
Patch (50.63 KB, patch)
2013-03-21 19:44 PDT, Hajime Morrita
no flags Details | Formatted Diff | Diff
Patch (50.71 KB, patch)
2013-03-21 20:07 PDT, Hajime Morrita
no flags Details | Formatted Diff | Diff
Patch (52.26 KB, patch)
2013-03-21 20:33 PDT, Hajime Morrita
no flags Details | Formatted Diff | Diff
Patch (52.97 KB, patch)
2013-03-21 22:02 PDT, Hajime Morrita
no flags Details | Formatted Diff | Diff
Patch (52.79 KB, patch)
2013-03-22 02:13 PDT, Hajime Morrita
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Comment 1 Hajime Morrita 2013-03-18 01:14:32 PDT
It should be called when
- a custom element is created by the parser (the callback should happen as as microtask)
- a custom element is created through createElementXX() function
- a custom element is upgraded.
Comment 2 Hajime Morrita 2013-03-21 03:14:13 PDT
Created attachment 194214 [details]
Patch
Comment 3 Elliott Sprehn 2013-03-21 03:56:08 PDT
Comment on attachment 194214 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=194214&action=review

Pretty sure it's called readyCallback now. Also we need a generic queue that tracks invocations of all the lifecycle methods in order. We can't do all ready callbacks and then all inserted callbacks, they need to happen in sequence. I think you want CustomElementInvocation to a type() field, and then loop over and execute the type of callback for each one.

> Source/WebCore/ChangeLog:39
> +        instnace are marked. See CustomElementRegistry::CallbackDeliveryScope

instances

> Source/WebCore/bindings/v8/CustomElementHelpers.cpp:153
> +    v8::Handle<v8::Value> functionValue = prototype->Get(v8::String::NewSymbol("created"));

Didn't we rename this readyCallback ?

> Source/WebCore/dom/CustomElementRegistry.cpp:236
> +        s_needsDeliverLifecycleCallbacks = true;

Can we just check isEmpty on the registries and remove this static state bool?

> Source/WebCore/dom/CustomElementRegistry.cpp:277
> +    s_needsDeliverLifecycleCallbacks = false;

Lets eliminate this bool.

> Source/WebCore/dom/CustomElementRegistry.h:65
> +    RefPtr<Element> m_element;

this needs an m_type which is one of Ready, Inserted, Removed or AttributeChanged I think.

> Source/WebCore/dom/CustomElementRegistry.h:107
> +    static bool s_needsDeliverLifecycleCallbacks;

Make activeCustomElementRegistries() inline and put it here and you don't need this anymore.

> Source/WebCore/dom/Document.cpp:900
> +        m_registry->didCreateElement(element);

How is it possible to not have a registry and still get here?
Comment 4 Hajime Morrita 2013-03-21 19:44:17 PDT
Created attachment 194421 [details]
Patch
Comment 5 Hajime Morrita 2013-03-21 19:46:47 PDT
Comment on attachment 194214 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=194214&action=review

Thanks for the review, Elliott. I uploaded revised one.

>> Source/WebCore/dom/CustomElementRegistry.h:65
>> +    RefPtr<Element> m_element;
> 
> this needs an m_type which is one of Ready, Inserted, Removed or AttributeChanged I think.

Yup, they will come later. That's exactly why we have this class.

>> Source/WebCore/dom/CustomElementRegistry.h:107
>> +    static bool s_needsDeliverLifecycleCallbacks;
> 
> Make activeCustomElementRegistries() inline and put it here and you don't need this anymore.

Will do.

>> Source/WebCore/dom/Document.cpp:900
>> +        m_registry->didCreateElement(element);
> 
> How is it possible to not have a registry and still get here?

Due to JS GC / C++ lifecycle gap, an JS-wrapped custom element constructor can survive
even after Document::removedLastRef() is called (where m_registry cleared)
Comment 6 Hajime Morrita 2013-03-21 19:49:10 PDT
(In reply to comment #3)
> (From update of attachment 194214 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=194214&action=review
> 
> Pretty sure it's called readyCallback now. Also we need a generic queue that tracks invocations of all the lifecycle methods in order. We can't do all ready callbacks and then all inserted callbacks, they need to happen in sequence. I think you want CustomElementInvocation to a type() field, and then loop over and execute the type of callback for each one.

I agree that we need better control of invocation order once we get other lifecycle callbacks.
That said, I'd start from this simple version for now
and make more sophisticated plumbing when we add more callback types.
Comment 7 Elliott Sprehn 2013-03-21 19:56:46 PDT
(In reply to comment #5)
> (From update of attachment 194214 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=194214&action=review
> 
> ...
> 
> Due to JS GC / C++ lifecycle gap, an JS-wrapped custom element constructor can survive
> even after Document::removedLastRef() is called (where m_registry cleared)

Maybe add a comment about that like m_registry may have been cleared in ::dispose because we have a guardRef but no regular ref count.

(In reply to comment #6)
> (In reply to comment #3)
> > (From update of attachment 194214 [details] [details])
> > View in context: https://bugs.webkit.org/attachment.cgi?id=194214&action=review
> > 
> > ...
> 
> I agree that we need better control of invocation order once we get other lifecycle callbacks.
> That said, I'd start from this simple version for now
> and make more sophisticated plumbing when we add more callback types.

Sounds reasonable.
Comment 8 Hajime Morrita 2013-03-21 20:07:15 PDT
Created attachment 194426 [details]
Patch
Comment 9 Hajime Morrita 2013-03-21 20:08:07 PDT
> Maybe add a comment about that like m_registry may have been cleared in ::dispose because we have a guardRef but no regular ref count.

Right. Done.

Also updated the bug description to reflect the correct callback name.
Comment 10 Elliott Sprehn 2013-03-21 20:17:37 PDT
Comment on attachment 194421 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=194421&action=review

This looks pretty good, but the ordering is still suspect.

I don't think this is going to give desired behavior when you have iframes because you use a HashSet of registries. It definitely needs to be a ListHashSet otherwise the parser for a nested iframe could mean you run all the constructors in the iframe before running them in the top document, even though the top document created it's instances first.

Even then grouping by registry produces bad behavior with iframes:

<x-foo/>
<iframe that contains x-bar>
<x-baz/>
<iframe that contains x-fizz>
<script>
   // Here we see XFoo, XBaz, then XBar then XFizz but this isn't the order
   // they were actually created in.
</script>

Dimitri, how should this work? :/

> Source/WebCore/ChangeLog:3
> +        Custom Elements: "created" lifecycle callback should be called.

All the files mention "created" so you might want to rename the tests and the bug.

> Source/WebCore/ChangeLog:54
> +               fast/dom/custom/lifecycle-created-paste.html

lifecycle-ready- ?

> Source/WebCore/dom/CustomElementConstructor.cpp:68
> +    RefPtr<Element> created = createElementInternal();

created -> element.

> Source/WebCore/dom/CustomElementRegistry.cpp:247
> +        CustomElementHelpers::invokeCreatedCallbacksIfNeeded(m_scriptExecutionContext, invocations);

This can cause an infinite loop, we probably need to explain that in the spec (ex. new X()'s ready callback does new X()" since even though they're async we don't yield to the event loop.

> Source/WebCore/dom/CustomElementRegistry.h:96
> +    typedef HashSet<CustomElementRegistry*> InstanceSet;

You almost certainly want a ListHashSet otherwise the constructors will run in a random order after parsing.

> LayoutTests/fast/dom/custom/lifecycle-created-creation-api.html:1
> +<!DOCTYPE html>

This test should probably assert about the order. Right now you can pass it by running the callbacks for each element in a group which would be wrong.

ex. <x-bar><x-foo><x-bar> should run XBar.readyCallback, XFoo.readyCallback, XBar.readyCallback.

> LayoutTests/fast/dom/custom/lifecycle-created-creation-api.html:35
> +var foreignDoc = document.implementation.createDocument ('http://www.w3.org/1999/xhtml', 'html', null);

Extra space

> LayoutTests/fast/dom/custom/lifecycle-created-innerHTML.html:16
> +container.innerHTML = "<x-foo></x-foo><div is='x-bar'></div><x-foo></x-foo>";

Add an x-bar here to assert about order below. You did register x-bar above too :)
Comment 11 Elliott Sprehn 2013-03-21 20:20:07 PDT
(In reply to comment #10)
> (From update of attachment 194421 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=194421&action=review
> 
> This looks pretty good, but the ordering is still suspect.
> 

Lets make this a ListHashSet for now and then when Dimitri makes the spec clear we can fix it if needed. After that change I'll r+.
Comment 12 Hajime Morrita 2013-03-21 20:33:17 PDT
Created attachment 194430 [details]
Patch
Comment 13 Hajime Morrita 2013-03-21 20:33:56 PDT
Comment on attachment 194430 [details]
Patch

oops. this isn't ready yet.
Comment 14 Hajime Morrita 2013-03-21 22:02:53 PDT
Created attachment 194441 [details]
Patch
Comment 15 Hajime Morrita 2013-03-21 22:03:02 PDT
Comment on attachment 194421 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=194421&action=review

>> Source/WebCore/ChangeLog:3
>> +        Custom Elements: "created" lifecycle callback should be called.
> 
> All the files mention "created" so you might want to rename the tests and the bug.

Yup.

>> Source/WebCore/ChangeLog:54
>> +               fast/dom/custom/lifecycle-created-paste.html
> 
> lifecycle-ready- ?

Done.

>> Source/WebCore/dom/CustomElementConstructor.cpp:68
>> +    RefPtr<Element> created = createElementInternal();
> 
> created -> element.

Done.

>> Source/WebCore/dom/CustomElementRegistry.cpp:247
>> +        CustomElementHelpers::invokeCreatedCallbacksIfNeeded(m_scriptExecutionContext, invocations);
> 
> This can cause an infinite loop, we probably need to explain that in the spec (ex. new X()'s ready callback does new X()" since even though they're async we don't yield to the event loop.

I noticed that we even shouldn't need while() since any node creation which is originated script should be invoked before returning from the callback.
This is different from mutation observer which can delay callback invocation.
So I removed while() .

>> Source/WebCore/dom/CustomElementRegistry.h:96
>> +    typedef HashSet<CustomElementRegistry*> InstanceSet;
> 
> You almost certainly want a ListHashSet otherwise the constructors will run in a random order after parsing.

Right. Done.

>> LayoutTests/fast/dom/custom/lifecycle-created-creation-api.html:1
>> +<!DOCTYPE html>
> 
> This test should probably assert about the order. Right now you can pass it by running the callbacks for each element in a group which would be wrong.
> 
> ex. <x-bar><x-foo><x-bar> should run XBar.readyCallback, XFoo.readyCallback, XBar.readyCallback.

createElement() create element atomically so we don't need to test the order. 
We also have another test lifecycle-ready-innerHTML-reentrancy.html to test
cases in conjunction with an element-creating callback function.

I added order test for importNode() below.

>> LayoutTests/fast/dom/custom/lifecycle-created-creation-api.html:35
>> +var foreignDoc = document.implementation.createDocument ('http://www.w3.org/1999/xhtml', 'html', null);
> 
> Extra space

Removed.

>> LayoutTests/fast/dom/custom/lifecycle-created-innerHTML.html:16
>> +container.innerHTML = "<x-foo></x-foo><div is='x-bar'></div><x-foo></x-foo>";
> 
> Add an x-bar here to assert about order below. You did register x-bar above too :)

I updated this test to clarify the order being examined, and also added another case to cover nested case.
Comment 16 Hajime Morrita 2013-03-21 22:11:07 PDT
Actually, the order problem Elliott mentioned is tricky and is not limited to <iframe>.
Think about <x-parent><x-child></x-child></x-parent>.

If we call x-parent first and x-child next, it seems conceptually correct.
However in practice, the callback of x-parent sees un-readified x-child,
which is not what people expect IMO.
Calling x-child first makes sense  only if x-child is not inserted into x-parent.
This is true for C++ constructor but it isn't for readyCallbacks I wrote.

Calling x-child's readyCallback will be more useful since x-parent got readyCallback
with readified x-child in its child list. But this means readyCallback becomes unintentionally powerful.

We possibly want to shuffle the invocation list so that people cannot except one specific order :-/
Comment 17 Elliott Sprehn 2013-03-21 22:13:21 PDT
Comment on attachment 194441 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=194441&action=review

> Source/WTF/ChangeLog:9
> +        (WTF::copyToVector): Generalized to let it accept variants like ListHahsSet instead of only HashSet.

You're my hero!
Comment 18 Elliott Sprehn 2013-03-21 22:15:53 PDT
(In reply to comment #16)
> Actually, the order problem Elliott mentioned is tricky and is not limited to <iframe>.
> Think about <x-parent><x-child></x-child></x-parent>.
> 
> If we call x-parent first and x-child next, it seems conceptually correct.
> However in practice, the callback of x-parent sees un-readified x-child,
> which is not what people expect IMO.
> Calling x-child first makes sense  only if x-child is not inserted into x-parent.
> This is true for C++ constructor but it isn't for readyCallbacks I wrote.
> 
> Calling x-child's readyCallback will be more useful since x-parent got readyCallback
> with readified x-child in its child list. But this means readyCallback becomes unintentionally powerful.
> 
> We possibly want to shuffle the invocation list so that people cannot except one specific order :-/

Random order sounds like hell for debugging in JS/Dev Tools. Lets land this and then consult Dimitri and Mozilla.
Comment 19 WebKit Review Bot 2013-03-21 23:18:32 PDT
Comment on attachment 194441 [details]
Patch

Clearing flags on attachment: 194441

Committed r146565: <http://trac.webkit.org/changeset/146565>
Comment 20 WebKit Review Bot 2013-03-21 23:18:37 PDT
All reviewed patches have been landed.  Closing bug.
Comment 21 Yury Semikhatsky 2013-03-22 01:17:13 PDT
The change was rolled out in http://trac.webkit.org/changeset/146572 along with other change (http://trac.webkit.org/changeset/146534) that caused perf regression.
Comment 22 Hajime Morrita 2013-03-22 02:13:46 PDT
Created attachment 194477 [details]
Patch
Comment 23 WebKit Review Bot 2013-03-22 02:56:32 PDT
Comment on attachment 194477 [details]
Patch

Clearing flags on attachment: 194477

Committed r146583: <http://trac.webkit.org/changeset/146583>
Comment 24 WebKit Review Bot 2013-03-22 02:56:37 PDT
All reviewed patches have been landed.  Closing bug.
Comment 25 Yury Semikhatsky 2013-03-22 05:17:12 PDT
(In reply to comment #23)
> (From update of attachment 194477 [details])
> Clearing flags on attachment: 194477
> 
> Committed r146583: <http://trac.webkit.org/changeset/146583>

This change broke compilation on Windows:
http://build.chromium.org/p/chromium.webkit/builders/WebKit%20Win%20Builder/builds/37309/steps/compile/logs/stdio

I landed a fix: http://trac.webkit.org/changeset/146592
Comment 26 Erik Arvidsson 2013-03-22 07:54:55 PDT
Comment on attachment 194477 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=194477&action=review

Missing [V8DeliverCustomElementCallbacks] on Range.prototype.createContextualFragment

> Source/WebCore/bindings/v8/V8RecursionScope.cpp:53
> +        CustomElementRegistry::deliverAllLifecycleCallbacks();

It is not obvious that this order is correct? Is this speced?

> Source/WebCore/html/HTMLElement.idl:42
> +             [TreatNullAs=NullString, V8DeliverCustomElementCallbacks] attribute DOMString innerHTML

Missing [V8DeliverCustomElementCallbacks] on outerHTML and insertAdjecentHTML
Comment 27 Hajime Morrita 2013-03-24 18:39:13 PDT
(In reply to comment #26)
> (From update of attachment 194477 [details])
> View in context: https://bugs.webkit.org/attachment.cgi?id=194477&action=review
> 
> Missing [V8DeliverCustomElementCallbacks] on Range.prototype.createContextualFragment
> 
> > Source/WebCore/bindings/v8/V8RecursionScope.cpp:53
> > +        CustomElementRegistry::deliverAllLifecycleCallbacks();
> 
> It is not obvious that this order is correct? Is this speced?
No. I started the thread here
http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0935.html


> 
> > Source/WebCore/html/HTMLElement.idl:42
> > +             [TreatNullAs=NullString, V8DeliverCustomElementCallbacks] attribute DOMString innerHTML
> 
> Missing [V8DeliverCustomElementCallbacks] on outerHTML and insertAdjecentHTML

Good catch! will fix. Filed Bug 113164.