dom icon indicating copy to clipboard operation
dom copied to clipboard

Hook for SVG and HTML script element insertion

Open annevk opened this issue 7 years ago • 9 comments

Given http://software.hixie.ch/utilities/js/live-dom-viewer/?saved=4098 we need to do something special for script elements, in particular when inserted from a DocumentFragment node. It's my understanding this only affects script elements, but I haven't verified.

https://github.com/whatwg/html/issues/1127 has an idea as to how to address this.

annevk avatar Feb 19 '18 14:02 annevk

Maybe the way to go here is to always notify post-DocumentFragment node insert for everyone. That seems to be what https://searchfox.org/mozilla-central/source/dom/base/nsINode.cpp#2404 is doing anyway.

annevk avatar Feb 19 '18 14:02 annevk

Oh joy, if I change things to

<!DOCTYPE html>
<script>
 var s1 = document.createElement("script"); s1.textContent = "w(1 + ' ' + document.head.childElementCount); document.head.removeChild(s2)"
 var s2 = document.createElement("script"); s2.textContent = "w(2 + ' ' + document.head.childElementCount + ' ' + s2.parentNode)"
 var df = document.createDocumentFragment(); df.appendChild(s1); df.appendChild(s2)
 document.head.appendChild(df)
</script>

s2 will not execute in Chrome and Edge, but does execute in Firefox. Safari will throw an error for s1 because in Safari s2 is not in the tree yet when s1 executes.

So maybe another thing that's needed here is that script elements without parent do not execute?

annevk avatar Feb 19 '18 15:02 annevk

Per https://html.spec.whatwg.org/#prepare-a-script browsers need to do a "connected" check, which means Firefox has a bug. (I want to check though if that check is correct or if it needs to be browsing-context connected.)

Another concern here is how this slightly post-insert notification interacts with slot changes. Basically, at what point do the shadow tree changes happen relative to notifying external consumers (which can execute script)?

annevk avatar Feb 20 '18 10:02 annevk

With the following example I still get a slotchange event. I think that means that kind of tracking should happen before script execution, similar to queuing the mutation record.

<div id=host></div>
<script>
const host = document.getElementById("host"),
      shadow = host.attachShadow({ mode: "closed" }),
      slot = shadow.appendChild(document.createElement("slot")),
      df = document.createDocumentFragment(),
      s1 = df.appendChild(document.createElement("script")),
      s2 = df.appendChild(document.createElement("script"));

slot.addEventListener("slotchange", e => console.log(e));
s1.textContent = "df.appendChild(s1); df.appendChild(s2);";
s2.textContent = "console.log('hmm');";
host.appendChild(df);
</script>

annevk avatar Feb 20 '18 11:02 annevk

I also tested custom elements:

<script>
customElements.define("ce-1", class CE1 extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {
    console.log("ce-1 connected");
  }
});

const df = document.createDocumentFragment(),
      s = df.appendChild(document.createElement("script")),
      ce1 = df.appendChild(document.createElement("ce-1"));
s.textContent = "df.appendChild(ce1);";
document.head.appendChild(df);
</script>

This logs "ce-1 connected" in Chrome and Firefox. It doesn't log anything in Safari. I'm inclined to side with Chrome and Firefox here.

annevk avatar Feb 20 '18 14:02 annevk

<div id=host></div>
<script>
const host = document.getElementById("host"),
      shadow = host.attachShadow({ mode: "closed" }),
      slot = shadow.appendChild(document.createElement("slot")),
      df = document.createDocumentFragment(),
      s1 = df.appendChild(document.createElement("script")),
      s2 = df.appendChild(document.createElement("script"));

slot.addEventListener("slotchange", e => console.log(e));
s1.textContent = "df.appendChild(s1); df.appendChild(s2);";
s2.textContent = "console.log('hmm');";
host.appendChild(df);
</script>

<script>
customElements.define("ce-1", class CE1 extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {
    console.log("ce-1 connected");
  }
});

const df = document.createDocumentFragment(),
      s = df.appendChild(document.createElement("script")),
      ce1 = df.appendChild(document.createElement("ce-1"));
s.textContent = "df.appendChild(ce1);";
document.head.appendChild(df);
</script>

autisticgeniushk avatar Apr 09 '18 07:04 autisticgeniushk

Another snippet that exposes differences between Firefox and Safari:

<body>
<select id="select"><option selected>1</option></select>
<script>
var select = document.getElementById("select");

var script = document.createElement("script");
script.textContent = "console.log(select.selectedIndex);";

var option = document.createElement("option");
option.textContent = "2";
option.selected = true;

select.append(script, option);
</script>

Changing selectedness of options is defined as part of inserting steps, AFAIK. This prints 0 in Safari and 1 in Firefox.

nox avatar Feb 01 '19 14:02 nox

Oh joy, if I change things to

<!DOCTYPE html>
<script>
 var s1 = document.createElement("script"); s1.textContent = "w(1 + ' ' + document.head.childElementCount); document.head.removeChild(s2)"
 var s2 = document.createElement("script"); s2.textContent = "w(2 + ' ' + document.head.childElementCount + ' ' + s2.parentNode)"
 var df = document.createDocumentFragment(); df.appendChild(s1); df.appendChild(s2)
 document.head.appendChild(df)
</script>

s2 will not execute in Chrome and Edge, but does execute in Firefox. Safari will throw an error for s1 because in Safari s2 is not in the tree yet when s1 executes.

So maybe another thing that's needed here is that script elements without parent do not execute?

Safari follows the current spec entirely for this one snippet, here is what the Live DOM Viewer says:

log: 1 1
error: NotFoundError: The object can not be found here. on line 1
log: 2 2 [object HTMLHeadElement]

Which means it first inserts s1 and runs it, which raises a NotFoundError because s2 is not a child of head, then it inserts s2 and runs it.

I'm not really sure what to do with this information, in which way do we want the spec to go? Do we want to specify less differences between inserting a DocumentFragment and a plain old node?

nox avatar Feb 04 '19 11:02 nox

This logs "ce-1 connected" in Chrome and Firefox. It doesn't log anything in Safari. I'm inclined to side with Chrome and Firefox here.

I guess that answers my question: we want children being inserted as part of a DocumentFragment node to behave more as if they were inserted as part of a plain old node.

nox avatar Feb 04 '19 11:02 nox