Very interesting finding indeed!
It really seems that empty src-less script
element is in some strange state that accepts either content or even new src
and interprets them. (Couldn't find a reason for that either. I just have a tiny hint: )
It resembles behavior of dynamically inserted script elements.
Here is example/proof of your observation and added few more cases for illustration:
script[src]::before,
script {
display: block;
border: 1px solid red;
padding: 1em;
}
script[src]::before,
script {
content: 'src='attr(src)
}
button {
display: block
}
<p>Existing empty script in HTML:</p>
<script id="existing"></script>
<p>Can be invoked just one of:</p>
<button onclick="eval(this.innerText)">
existing.innerHTML='console.log("innerHTML to static")'
</button>
<button onclick="eval(this.innerText)">
existing.src='data:text/javascript,console.log("src to static")'
</button>
<p>Dynamically created and inserted script (each creates own, so both work):</p>
<button onclick="eval(this.innerText)">
document.body.appendChild(document.createElement('script')).innerHTML='console.log("innerHTML to dynamic")'
</button>
<button onclick="eval(this.innerText)">
document.body.appendChild(document.createElement('script')).src='data:text/javascript,console.log("src to dynamic")'
</button>
You will have to re-run snippet to see both "static" cases works.
(Also there is a blank script containing white-space generated by SO, for whatever reason.)
As Laurianti demonstrated, if the script had some (even white-space) content (or src=""
), it would not work.
Also, in the "dynamic" examples notice that the innerHTML
or src
value is altered after the script
element had been inserted to the document. So it is possible to have a blank static or create dynamic script, leave it in the document and set use it long after that.
Sorry for not giving a full answer, just wanted to share research and the hint.
(Update removed; Oriol was faster and way more accurate. Phew, glad to see this sorted out!)