New issue
Advanced search Search tips

Issue 821195 link

Starred by 1 user

Issue metadata

Status: WontFix
Owner: ----
Closed: Mar 2018
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: ----
Pri: 3
Type: Bug



Sign in to add a comment

connectedCallback called when disconnected

Project Member Reported by jridgewell@google.com, Mar 12 2018

Issue description

Chrome Version: 64.0.3282.186 (Official Build) (64-bit)
OS: Mac OS X 10.13.3

What steps will reproduce the problem?
(1) Go to https://output.jsbin.com/qumape/3/quiet
(2) Custom element 1 (Reparenter) defines a `connectedCallback` that will reparent all children nodes into a new div element (which it then appends to itself).
(3) Custom element 2 (Child) has any connectedCallback.
(4) Create a second Document tree, with the innerHTML `<x-parenter><x-child></x-child></x-parenter>`
(5) Import the second Document's body (deep: true), and append the result to the host body.

What is the expected result?
Child's connectedCallback should not fire when it is appended to the disconnected div. It should fire when the div is appended to the Reparenter (and the Reparenter is in the host DOM tree).


What happens instead?
Child's connectedCallback fires when it is appended to the disconneted div (making it disconnected from DOM tree).

(This is displayed by adding the h1 element to the DOM)



Interestingly, if you comment out Child's disconnectedCallback, everything works.


- - - - -

Reproduction HTML, in case JSBin is inaccessible:

<!doctype html>
<html>
<head>
  <title>connectedCallback when isConnected=false</title>
  <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
  <style>
    h1 {
      color: red;
    }
  </style>
</head>
<body>
  <script type="application/javascript">
    class Parenter extends HTMLElement {
      connectedCallback() {
        const div = this.ownerDocument.createElement('div');

        console.group('reparent');
        while (this.firstChild) {
          div.appendChild(this.firstChild);
        }
        console.groupEnd('reparent');

        this.appendChild(div);
      }
    }
    customElements.define('x-parenter', Parenter);

    class Child extends HTMLElement {
      connectedCallback() {
        const is = isConnected(this);
        console.log('img connected', is);

        if (!is) {
          const h1 = document.createElement('h1');
          h1.textContent = 'connectedCallback called when not connected!';
          document.body.appendChild(h1)
        }
      };

      //*
      disconnectedCallback() {
        console.log('disconnected');
      };
      //*/
    }
    customElements.define('x-child', Child);

    function isConnected(node) {
      let isConnected = node.isConnected;
      if (isConnected !== undefined) {
        return isConnected;
      }

      return node.getRootNode().nodeType === Node.DOCUMENT_NODE;
    }

    function fetchDocument(url) {
      var parser = new DOMParser();
      return parser.parseFromString(`
          <x-parenter>
            <x-child></x-child>
          </x-parenter>`, 'text/html');
    }
    const doc = fetchDocument()
    const resultBody = document.importNode(doc.body, true);
    document.body.appendChild(resultBody);
  </script>
</body>
</html>

 

Comment 1 by nainar@chromium.org, Mar 12 2018

Cc: tkent@chromium.org
Description: Show this description

Comment 3 by tkent@chromium.org, Mar 13 2018

Status: WontFix (was: Untriaged)
I think this behavior conforms to the HTML standard.  The point is that connectedCallback is asynchronous, and can be called after the element was disconnected.

With disconnectedCallback:
1. document.body.appendChild(resultBody) enqueues connectedCallback for x-parenter, then connectedCallback for x-child.
2. CEReaction attribute for the above appendChild() invokes custom element reactions (Step 4 in https://html.spec.whatwg.org/multipage/custom-elements.html#cereactions)
2.1 Invokes connectedCallback for x-parenter
2.1.1 div.appendChild(this.firstChild) disconnects x-child, enqueues disconnectedCallback for x-child. Now x-child is disconnected.
2.1.2 CEReaction attribute for the above appendChild() invokes custom element reactions because an element queue is not empty, it invokes connectedCallback for x-child first, then disconnectedCallback for x-child.
2.1.3 this.appendChild(div) enqueues connectedCallback for x-child.
2.1.4 CEReaction attribute for the above appendChild() invokes custom element reactions for x-child. connectedCallback is called once.

Safari TP and Firefox Nightly seem to have the same behavior.

Without disconnectedCallback:
1. document.body.appendChild(resultBody) enqueues connectedCallback for x-parenter, then connectedCallback for x-child.
2. CEReaction attribute for the above appendChild() invokes custom element reactions
2.1 Invokes connectedCallback for x-parenter
2.1.1 div.appendChild(this.firstChild) disconnects x-child, it doesn't enqueue disconnectedCallback for x-child.
2.1.2 CEReaction attribute for the above appendChild() doesn't invokes custom element reactions because element queue is empty.
2.1.3 this.appendChild(div) enqueues connectedCallback for x-child
2.1.4 CEReaction attribute for the above appendChild() invokes custom element reactions for x-child. connectedCallback is called twice.

Firefox Nightly seems to have the same behavior. Safari TP doesn't match. It might be a Safari bug.

Comment 4 by tkent@chromium.org, Mar 13 2018

> Safari TP doesn't match. It might be a Safari bug.

Filed a WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=183586

Sign in to add a comment