TwistedOwl
TwistedOwl

Reputation: 1324

How <script> tag forces MutationObserver to run early in Declarative Shadow DOM?

I have found this particular test in WPT tests:

<script>
function myObserver(mutationsList) {
  for (let mutation of mutationsList) {
    for (let n of mutation.addedNodes) {
      if (n.id === 'shadow') {
        n.innerHTML = "<span id=replaced>This should be in &lt;div>'s shadow root</span>";
      }
    }
  }
}
var observer = new MutationObserver(myObserver);
observer.observe(document.body, { childList: true, subtree: true });
</script>

<div id=host>
  <template id=shadow shadowroot=open>
    <span id=toreplace>This should get removed</span>
    <script></script> <!-- Ensure observer runs -->
  </template>
</div>

Now the questions is about <script></script> <!-- Ensure observer runs --> -> how does this line of code ensures that observer runs?

Upvotes: 3

Views: 362

Answers (2)

Mason Freed
Mason Freed

Reputation: 7921

Thanks for raising the issue! I agree with your analysis, and I posted also on your WPT issue. Per the spec, all classic scripts, regardless of whether they're empty, should run microtasks. Unless I missed something, this seems like a bug in Gecko.

Quick comment also - these are definitely still .tentative tests, since Gecko and WebKit are waiting for definitive evidence that Declarative Shadow DOM is "a good thing". My pull request to add it to the spec is still awaiting the support of other implementers.

Upvotes: 1

Kaiido
Kaiido

Reputation: 136638

Running a script does trigger the cleanup after running a script steps, which itself does force a microtask-checkpoint where the observer's notifications will be dispatched.
My guess is that they use it here to force these mutation notifications before the <template> tag is closed, as is hinted by the test's name "innerHTML before closing tag".

However if my guess is correct, I believe this code is actually buggy:
All browsers don't run the script if the <script> tag is completely empty, In Firefox it needs to have at least some text content for it to run all these steps and thus to force the microtask-checkpoint. Even a space character will do, but it's necessary, as demonstrated in below snippets:

Using a completely empty <script></script>, we see in Firefox that the observer runs when the whole element has already been parsed.

<script>
function myObserver(mutationsList) {
  for (let mutation of mutationsList) {
    for (let n of mutation.addedNodes) {
      if (n.id === 'parent') {
        console.log([...n.children].map(el => el.outerHTML));
      }
    }
  }
}
var observer = new MutationObserver(myObserver);
observer.observe(document.body, { childList: true, subtree: true });
</script>

<div id=parent>
  <span id=child>This should be in the log.</span>
  <script></script><!-- forces the observer to run -->
  <span id=sibling>This should not be in the log</span>
</div>

Running the same snippet with only a space character included in the <script> </script>, we see that the observer runs right after the <script> has been parsed, and before the "sibling" <span> has.

<script>
function myObserver(mutationsList) {
  for (let mutation of mutationsList) {
    for (let n of mutation.addedNodes) {
      if (n.id === 'parent') {
        console.log([...n.children].map(el => el.outerHTML));
      }
    }
  }
}
var observer = new MutationObserver(myObserver);
observer.observe(document.body, { childList: true, subtree: true });
</script>

<div id=parent>
  <span id=child>This should be in the log.</span>
  <script> </script><!-- forces the observer to run -->
  <span id=sibling>This should not be in the log</span>
</div>

I opened an issue on WPT for Mason Freed or anyone else there to have a look and confirm my hypothesis, and maybe fix these tests if needed.

Upvotes: 5

Related Questions