Search code examples
callbackweb-componentclass-methodcustom-element

wait for Element Upgrade in connectedCallback: FireFox and Chromium differences


Update 2024

Full blogpost:

https://dev.to/dannyengelman/web-component-developers-do-not-connect-with-the-connectedcallback-yet-4jo7

Update March 2023

Note: this works because in the next tick your N (not all!) DOM elements IN lightDOM will have been parsed.

for (app.) N > 1000 you will run into trouble, as the delay will end before all N Elements are parsed.

So either add (about 20 lines) of code that actually checks all lightDOM is parsed. (but since your DOM is then suffering Obesitas, you will probably also have other performance problems)

Or just keep the N amount of DOM elements inside lightDOM small.

Ofcourse any DOM you add later, after parsing, doesn't affect anything (when the connectedCallback doesn't fire)

Update March 2021:

FireFox bug fixed, now behaves the same as Chromium and Safari.

That means waiting for the JS EventLoop to be empty (with setTimeout or requestAnimationFrame) in the connectedCallback is now a cross-browser method

connectedCallback(){
 setTimeout(()=>{
   // can access lightDOM here
 }); // ,0 not required
}

What the heck is the Event Loop? - Philip Roberts
https://www.youtube.com/watch?v=8aGhZQkoFbQ



Update Oct. 28 2020:



First post May. 2020:

Bitten again by this Chrome Element upgrade issue, after spending a week in FireFox.

Forgot to wrap code in a setTimeout before delivering to Chromium browsers.

  • FireFox prints: ABCD

  • Chromium prints: ADCD

Question: Why the difference?

<script>
  customElements.define('my-element', class extends HTMLElement {
    connectedCallback() {
      console.log(this.innerHTML);// "A" in FireFox, "" in other Browsers
      if (this.innerHTML == "A")
        this.innerHTML = this.innerHTML + "B";
      else
        setTimeout(() => this.innerHTML = this.innerHTML + "D");
    }
  })
</script>

<my-element>A</my-element><my-element>C</my-element>

Related answers over the past years:

Update #1

  • Apple/Safari: prints: ADCD (same as Chromium)

note: Chromium Blink engine is a fork of Apples (WebKit)WebCore code!!

Update #2

With Supersharps reference we found the related threads:

order of callbacks in FireFox versus Chromium:

source: https://jsfiddle.net/WebComponents/9p5qyk1z/


Solution

  • I think the Chrome/Safari behaviour is less intuitive for the beginners, but with some more complex scenarios (for example with child custom elements) then it is much more consistant.

    See the different examples below. They act strangely in Firefox...

    Another use case that I don't have the courage to code: when a document is parsed, maybe you don't have the end of the document yet. Therefore, when a custom element is created, you cannot be sure you get all its children until you get the closing tag (that could never arrive).

    According to Ryosuke Niwa for WebKit:

    The problem then is that the element won't get connectedCallback until all children are parsed. For example, if the entire document was a single custom element, that custom element would never receive connectedCallback until the entire document is fetched & parsed even though the element is really in the document. That would be bad.

    So it's better no to wait and connect the custom element as soon as it is created, that means with no child.

    <script>
        customElements.define( 'c-e', class extends HTMLElement {} ) 
        customElements.define('my-element', class extends HTMLElement {
          connectedCallback() {
            console.log(this.innerHTML, this.childNodes.length)
            let span = document.createElement( 'span' )
            if (this.innerHTML.indexOf( 'A' ) >= 0 )
                span.textContent = 'B'
            else
                span.textContent = 'D'
            setTimeout( () => this.appendChild( span ) )
          }
        })
    </script>
    <my-element>A</my-element><my-element>C</my-element>
    <br>
    <my-element><c-e></c-e>A</my-element><my-element>A<c-e></c-e></my-element>
    <br>
    <my-element><c-e2></c-e2>A</my-element><my-element>A<c-e2></c-e2></my-element>

    As far as I understand, there was a consensus on it that led to adjust the spec that (Chrome/Safari) way:

    Fixes w3c/webcomponents#551 by ensuring that insertions into the DOM trigger connectedCallback immediately, instead of putting the callback reaction on the the backup element queue and letting it get triggered at the next microtask checkpoint. This means connectedCallback will generally be invoked when the element has zero children, as expected, instead of a random number depending on when the next custom element is seen.

    We can conclude that Firefox also follow the spec... yes, but we should not rely on the content in connectedCallback for the reasons discussed above.