Running Dynamically Created Javascript

I read this question with the accepted answer:

The script added by setting the element's innerHTML property is not executed.

But when I try to change innerHTML of the first <script> in the following code:

 <script></script> <script> document.querySelectorAll("script")[0].innerHTML = 'console.log("Test")'; </script> 

I can see the entered code for the <script> element being executed (the console.log() function outputs Test).

Also, if I remove the first empty <script> (thus the first element [0] refers to the script itself), the script changes in the DOM, but the code never executes.

 <script> document.querySelectorAll("script")[0].innerHTML = 'console.log("Test")'; </script> 

What does this behavior suggest?

+6
source share
3 answers

This is described in Scripting . When the script is ready ,

  • In step 2, the inserted parser flag is removed:

    If the element has the parser-inserted flag set, set was-parser-insert to true and disable the parser-insert element.

  • In step 4, before the "inserted parser" flag is restored, the steps are interrupted

    If the element does not have the src attribute and its child nodes, if any, consist only of comment nodes and empty Text nodes, then the user agent must abort these steps at this moment. script is not executed.

Therefore, when you change it, it will be ready again:

When a script element that is not marked as "parser-insert" contains one of the events listed in the following list, the user agent must synchronously prepare the script element:

After running the script, the content change will not be executed because the script will be canceled:

If the script element is marked as “already started,” then the user agent must abort these steps at the same time. script is not executed.

+4
source

Very interesting find!

It seems that the empty src-less script element is in some weird state that accepts content or even a new src and interprets them. (I can’t find the reason for this. I just have a tiny hint :)

It is similar to the behavior of dynamically inserted script elements.

Here is an example / proof of your observation and a few examples are added to illustrate:

 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 need to re-run the snippet to see how the “static” cases work.

(There is also an empty script containing the empty space generated by SO, for any reason.)

As Lauranti showed, if the script had some (even white) content (or src="" ), this would not work.

Also, in the "dynamic" examples, note that the value of innerHTML or src changes after the script element has been inserted into the document. Thus, you can have an empty static or create a dynamic script, leave it in the document and install it after that.

Sorry, I didn’t give a complete answer, I just wanted to share my research and help.


(Update removed, Oriol was faster and more accurate. Fu, glad to see that figured it out!)

+2
source

If you change innerHTML, it will not execute. You must write it before and after you add it.

  var script = document.createElement("script"); script.innerHTML = 'console.log("Test");'; document.getElementsByTagName('head')[0].appendChild(script); 

Try adding a new line:

 <script> </script> <script> document.querySelectorAll("script")[0].innerHTML = 'console.log("Test")'; </script> 
0
source

All Articles