I've got a script that inserts some content into an element using innerHTML.

The content could for example be:

<script type="text/javascript">alert('test');</script>
<strong>test</strong>

Problem is that the code inside the <script> tag doesn't get executed. I googled it a bit but there were no apparent solutions. If I inserted the content using jQuery $(element).append(content);the script parts got eval'd before being injected into the DOM.

Has anyone got a snippet of code that executes all the <script> elements? The jQuery code was a bit complex so I couldn't really figure out how it was done.

Edit:

By peeking into the jQuery code I've managed to figure out how jQuery does it, which resulted in the following code:

Demo:
<div id="element"></div>

<script type="text/javascript">
  function insertAndExecute(id, text)
  {
    domelement = document.getElementById(id);
    domelement.innerHTML = text;
    var scripts = [];

    ret = domelement.childNodes;
    for ( var i = 0; ret[i]; i++ ) {
      if ( scripts && nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
            scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
        }
    }

    for(script in scripts)
    {
      evalScript(scripts[script]);
    }
  }
  function nodeName( elem, name ) {
    return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
  }
  function evalScript( elem ) {
    data = ( elem.text || elem.textContent || elem.innerHTML || "" );

    var head = document.getElementsByTagName("head")[0] || document.documentElement,
    script = document.createElement("script");
    script.type = "text/javascript";
    script.appendChild( document.createTextNode( data ) );
    head.insertBefore( script, head.firstChild );
    head.removeChild( script );

    if ( elem.parentNode ) {
        elem.parentNode.removeChild( elem );
    }
  }

  insertAndExecute("element", "<scri"+"pt type='text/javascript'>document.write('This text should appear as well.')</scr"+"ipt><strong>this text should also be inserted.</strong>");
</script>
asked Apr 7 '10 at 11:48
phidah
3,57032553
    
Have your tried adding content (JS as innerHTML of DOM Node) and then calling the function(s) added? For example if you append Javascript containing a function FOO(){ } you can try calling function later. – Andreas Apr 7 '10 at 12:05
1  
Why can't you just iterate the children of the element, and for each one that is a script element you just eval() the innerHtml of that child? This is how i've seen it done by a large component vendor, every time they complete an ajax callback that adds stuff to the DOM they do exactly that. Bear in mind though that it can be slow, especially in IE7. – slugster Apr 7 '10 at 12:33
2  
Andreas: If I add a function, for example function testFunction(){ alert('test'); } to the code inserted into innerHTML, and then try calling it, it says that the function is not defined. – phidah Apr 7 '10 at 12:52
1  
Awesome phidah, works like charm, cheers – Marcin Oct 14 '12 at 17:12
1  
I think it is absolutely important to understand that this is intended behaviour by the browser to prevent Cross-site scripting attacks. If the text you set as innerHTML is provided by Bob it would execute on Alice's browser causing damage (think of a forum where people can write comments adding script-tags to them). You can read more about it here: en./wiki/Cross-site_scripting. Stay save! – Xatian May 3 at 9:08
up vote 18 down vote accepted

The OP's script doesn't work in IE 7. With help from SO, here's a script that does:

exec_body_scripts: function(body_el) {
  // Finds and executes scripts in a newly added element's body.
  // Needed since innerHTML does not run scripts.
  //
  // Argument body_el is an element in the dom.

  function nodeName(elem, name) {
    return elem.nodeName && elem.nodeName.toUpperCase() ===
              name.toUpperCase();
  };

  function evalScript(elem) {
    var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
        head = document.getElementsByTagName("head")[0] ||
                  document.documentElement,
        script = document.createElement("script");

    script.type = "text/javascript";
    try {
      // doesn't work on ie...
      script.appendChild(document.createTextNode(data));      
    } catch(e) {
      // IE has funky script nodes
      script.text = data;
    }

    head.insertBefore(script, head.firstChild);
    head.removeChild(script);
  };

  // main section of function
  var scripts = [],
      script,
      children_nodes = body_el.childNodes,
      child,
      i;

  for (i = 0; children_nodes[i]; i++) {
    child = children_nodes[i];
    if (nodeName(child, "script" ) &&
      (!child.type || child.type.toLowerCase() === "text/javascript")) {
          scripts.push(child);
      }
  }

  for (i = 0; scripts[i]; i++) {
    script = scripts[i];
    if (script.parentNode) {script.parentNode.removeChild(script);}
    evalScript(scripts[i]);
  }
};
answered Jul 14 '10 at 20:48
Larry K
28.7k96898
2  
Better use jQuery's $(parent).html(code) - see my answer below. – iirekm May 7 '14 at 8:20
    
Your code saved my day, thanks for that. – Blizz Aug 29 '14 at 8:10
    
once script is injected to DOM, how should I remove it? – S4beR Oct 31 '14 at 16:53
1  
The script isn't recursive, so will only look at direct children. This works for me: if (nodeName(child, "script" ) && (!child.type || child.type.toLowerCase() === "text/javascript")) { scripts.push(child); } else { exec_body_scripts(child); } – Mateusz Stawecki Feb 10 '15 at 14:58
2  
Note that the above code doesn't execute scripts that load via src. The above script can be changed to check elem.src and conditionally set the src property of the created script element instead of setting its text content. – Ryan Morlok Nov 9 '15 at 15:27

@phidah... Here is a very interesting solution to your problem: http:///2005/have-your-dom-and-script-it-too

So it would look like this instead:

<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />

answered Sep 15 '10 at 3:01
user447963
83573
    
and even better, no need to have an image with "onerror" event, nice for quick XSS injection /blog/47/… :) – baptx Jun 30 '12 at 13:58
5  
You can use <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAA??AAALAAAAAABAAEAAAIBR??AA7" onload="alert('test');"> if you want to prevent a useless http request. – Savas Vedova Apr 14 '15 at 8:56
    
love it ! (added style="display:none;) to hide the broken image icon – kris Apr 26 at 5:57

Try this snippet:

function stripAndExecuteScript(text) {
    var scripts = '';
    var cleaned = text.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
        scripts += arguments[1] + '\n';
        return '';
    });

    if (window.execScript){
        window.execScript(scripts);
    } else {
        var head = document.getElementsByTagName('head')[0];
        var scriptElement = document.createElement('script');
        scriptElement.setAttribute('type', 'text/javascript');
        scriptElement.innerText = scripts;
        head.appendChild(scriptElement);
        head.removeChild(scriptElement);
    }
    return cleaned;
};


var scriptString = '<scrip' + 't + type="text/javascript">alert(\'test\');</scr' + 'ipt><strong>test</strong>';
document.getElementById('element').innerHTML = stripAndExecuteScript(scriptString);
answered Jun 3 '10 at 14:52
fantactuka
2,8251325

Here's a shorter, more efficient script that also works for scripts with the src property:

function insertAndExecute(id, text) {
    document.getElementById(id).innerHTML = text;
    var scripts = Array.prototype.slice.call(document.getElementById(id).getElementsByTagName("script"));
    for (var i = 0; i < scripts.length; i++) {
        if (scripts[i].src != "") {
            var tag = document.createElement("script");
            tag.src = scripts[i].src;
            document.getElementsByTagName("head")[0].appendChild(tag);
        }
        else {
            eval(scripts[i].innerHTML);
        }
    }
}

Note: whilst eval may cause a security vulnerability if not used properly, it is much faster than creating a script tag on the fly.

answered Nov 3 '14 at 14:25
Aleksandur Murfitt
2,61211226
    
this helped me but i feel dirty using eval. making sure text cannot be compromised i don't see a vulnerability. – John Dec 30 '14 at 16:56
    
Eval has a bad impression for some reason, but I think it's the best way to do this. I actually feel dirty using the script tag way! After all eval must have been made for some earthly reason! – Aleksandur Murfitt Jan 4 '15 at 0:16
    
@random-user eval was designed to hurt users. Any dynamic script execution is a risk and this is why CSP calls it 'unsafe-eval' because it is. You are also hurting the security of your sites if you are using it in a library as they can't turn it off. – jonathanKingston Aug 22 '15 at 19:05
    
Testing this in Chrome 44 causes an infinite loop when appendChild is called since this increments the scripts.length value. – Codewithcheese Oct 11 '15 at 11:45
3  
Scripts with the src property will be downloaded asynchronously and executed as arrived. Ordering is not preserved. Inline scripts will also be executed out-of-order, synchronously before the async ones. – robert4 Feb 11 '16 at 22:30

You should not use the innerHTML property but rather the appendChild method of the Node: a node in a document tree [HTML DOM]. This way you are able to later call your injected code.

Make sure that you understand that node.innerHTML is not the same as node.appendChild. You might want to spend some time on the Javascript Client Reference for more details and the DOM. Hope the following helps...

Sample injection works:

<html>
<head>
<title>test</title>
<script language="javascript" type="text/javascript">
    function doOnLoad(){
        addScript('inject',"function foo(){ alert('injected'); }");
    }


    function addScript(inject,code){
        var _in = document.getElementById('inject');
        var scriptNode = document.createElement('script');
        scriptNode.innerHTML = code;
        _in.appendChild(scriptNode);
    }

</script>
</head>
<body onload="doOnLoad();">
    <div id="header">some content</div>
    <div id="inject"></div>
    <input type="button" onclick="foo(); return false;" value="Test Injected" />
</body>
</html>

regards,

answered Apr 7 '10 at 14:06
Andreas
3,91842649
    
Finally someone that actually explains a bit about the issue rather than all the other try this, look how clever I am answers. Deserves an UV, it got mine. – RiggsFolly Feb 7 '16 at 12:05
    
i uv this because it is the most simple way to inject javascript code that executes after injecting . I only dont grasp the difference between adding with innerHTML which doesn't execute, and the way above with appendChild which executes. I used this successfully to create a dynamic page with script from scratch with socket.io – wetlip Dec 26 '16 at 10:21
function insertHtml(id, html)  
{  
   var ele = document.getElementById(id);  
   ele.innerHTML = html;  
   var codes = ele.getElementsByTagName("script");   
   for(var i=0;i<codes.length;i++)  
   {  
       eval(codes[i].text);  
   }  
}  

It works in Chrome in my project

answered Jan 22 '13 at 4:02
Bruce
7111
    
Fast and pretty. Thank you. – Jorge Fuentes González Dec 3 '14 at 22:08

scriptNode.innerHTML = code didn't work for IE. The only thing to do is replace with scriptNode.text = code and it work fine

answered Sep 16 '11 at 17:47
Jorge
311
    
this doesnt work for me with chrome – wetlip Dec 26 '16 at 10:23

A solution without using "eval":

var setInnerHtml = function(elm, html) {
  elm.innerHTML = html;
  var scripts = elm.getElementsByTagName("script");
  // If we don't clone the results then "scripts"
  // will actually update live as we insert the new
  // tags, and we'll get caught in an endless loop
  var scriptsClone = [];
  for (var i = 0; i < scripts.length; i++) {
    scriptsClone.push(scripts[i]);
  }
  for (var i = 0; i < scriptsClone.length; i++) {
    var currentScript = scriptsClone[i];
    var s = document.createElement("script");
    // Copy all the attributes from the original script
    for (var j = 0; j < currentScript.attributes.length; j++) {
      var a = currentScript.attributes[j];
      s.setAttribute(a.name, a.value);
    }
    s.appendChild(document.createTextNode(currentScript.innerHTML));
    currentScript.parentNode.replaceChild(s, currentScript);
  }
}

This essentially clones the script tag and then replaces the blocked script tag with the newly generated one, thus allowing execution.

answered Mar 6 at 14:55
joshcomley
15.4k1781116

It's easier to use jquery $(parent).html(code) instead of parent.innerHTML = code:

var oldDocumentWrite = document.write;
var oldDocumentWriteln = document.writeln;
try {
    document.write = function(code) {
        $(parent).append(code);
    }
    document.writeln = function(code) {
        document.write(code + "<br/>");
    }
    $(parent).html(html); 
} finally {
    $(window).load(function() {
        document.write = oldDocumentWrite
        document.writeln = oldDocumentWriteln
    })
}

This also works with scripts that use document.write and scripts loaded via src attribute. Unfortunately even this doesn't work with Google AdSense scripts.

answered May 7 '14 at 8:19
iirekm
3,74732036

You may take a look at this post. The code might look like this:

var actualDivToBeUpdated = document.getElementById('test');
var div = document.createElement('div');
div.innerHTML = '<script type="text/javascript">alert("test");<\/script>';
var children = div.childNodes;
actualDivToBeUpdated.innerHTML = '';
for(var i = 0; i < children.length; i++) {
    actualDivToBeUpdated.appendChild(children[i]);
}
answered Apr 7 '10 at 11:58
Darin Dimitrov
763k19027942584

Thanks to Larry's script, which worked perfectly well in IE10, this is what I've used:

$('#' + id)[0].innerHTML = result;
$('#' + id + " script").each(function() { this.text = this.text || $(this).text();} );
answered Jun 25 '13 at 15:24
AxD
369315

Try function eval().

data.newScript = '<script type="text/javascript">//my script...</script>'
var element = document.getElementById('elementToRefresh');
element.innerHTML = data.newScript;
eval(element.firstChild.innerHTML);

This is a real example from a project that i am developing. Thanks to this post

answered Jul 8 '14 at 19:27
IgniteCoders
1,82311632

Extending off of Larry's. I made it recursively search the entire block and children nodes.
The script now will also call external scripts that are specified with src parameter. Scripts are appended to the head instead of inserted and placed in the order they are found. So specifically order scripts are preserved. And each script is executed synchronously similar to how the browser handles the initial DOM loading. So if you have a script block that calls jQuery from a CDN and than the next script node uses jQuery... No prob! Oh and I tagged the appended scripts with a serialized id based off of what you set in the tag parameter so you can find what was added by this script.

exec_body_scripts: function(body_el, tag) {
    // Finds and executes scripts in a newly added element's body.
    // Needed since innerHTML does not run scripts.
    //
    // Argument body_el is an element in the dom.

    function nodeName(elem, name) {
        return elem.nodeName && elem.nodeName.toUpperCase() ===
              name.toUpperCase();
    };

    function evalScript(elem, id, callback) {
        var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
            head = document.getElementsByTagName("head")[0] ||
                      document.documentElement;

        var script = document.createElement("script");
        script.type = "text/javascript";
        if (id != '') {
            script.setAttribute('id', id);
        }

        if (elem.src != '') {
            script.src = elem.src;
            head.appendChild(script);
            // Then bind the event to the callback function.
            // There are several events for cross browser compatibility.
            script.onreadystatechange = callback;
            script.onload = callback;
        } else {
            try {
                // doesn't work on ie...
                script.appendChild(document.createTextNode(data));      
            } catch(e) {
                // IE has funky script nodes
                script.text = data;
            }
            head.appendChild(script);
            callback();
        }
    };

    function walk_children(node) {
        var scripts = [],
          script,
          children_nodes = node.childNodes,
          child,
          i;

        if (children_nodes === undefined) return;

        for (i = 0; i<children_nodes.length; i++) {
            child = children_nodes[i];
            if (nodeName(child, "script" ) &&
                (!child.type || child.type.toLowerCase() === "text/javascript")) {
                scripts.push(child);
            } else {
                var new_scripts = walk_children(child);
                for(j=0; j<new_scripts.length; j++) {
                    scripts.push(new_scripts[j]);
                }
            }
        }

        return scripts;
    }

    var i = 0;
    function execute_script(i) {
        script = scripts[i];
        if (script.parentNode) {script.parentNode.removeChild(script);}
        evalScript(scripts[i], tag+"_"+i, function() {
            if (i < scripts.length-1) {
                execute_script(++i);
            }                
        });
    }

    // main section of function
    if (tag === undefined) tag = 'tmp';

    var scripts = walk_children(body_el);

    execute_script(i);
}
answered Jul 23 '14 at 19:16

Try this, it works for me on Chrome, Safari & Firefox:

var script = document.createElement('script');
script.innerHTML = 'console.log("hi")';
document.body.appendChild(script); 
--> logs "hi"

One thing to note though, is that the following div-nested script will NOT run:

var script = document.createElement('div');
script.innerHTML = '<script>console.log("hi")</script>';
document.body.appendChild(script);
--> doesn't log anything

For a script to run it has to be created as a node then appended as a child. You can even append a script inside a previously injected div & it will run (I've run into this before when trying to get ad server code to work):

var div = document.createElement('div');
div.id = 'test-id';
document.body.appendChild(div);
var script = document.createElement('script');
script.innerHTML = 'console.log("hi")';
document.getElementById('test-id').appendChild(script);
--> logs "hi"
answered Jul 27 at 18:39
Trev14
428113

Just do:

document.body.innerHTML = '<img src="../images/loaded.gif" alt="" onload="alert(\'test\');this.parentNode.removeChild(this);" />';
answered Oct 3 '14 at 15:03
Lambder
1,90111617
    
This is a bad idea for obvious reasons incase anyone is wondering why it is downvoted. This will replace everything else inside of your body with the string above. It would work, but removing everything else is probably not what you want to do. – Dwayne Charrington Feb 2 '16 at 0:55
    
@DwayneCharrington of course it is not what you want. I just used the snippet for illustration of what's possible as you rightly noticed as it would work. – Lambder Nov 18 '16 at 11:29