By phidah


2010-04-07 11:48:39 8 Comments

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>

19 comments

@allenhwkim 2017-12-03 01:47:57

Simplified ES6 version of @joshcomley's answer with an example.

No JQuery, No library, No eval, No DOM change, Just pure Javascript.

http://plnkr.co/edit/MMegiu?p=preview

var setInnerHTML = function(elm, html) {
  elm.innerHTML = html;
  Array.from(elm.querySelectorAll("script")).forEach( oldScript => {
    const newScript = document.createElement("script");
    Array.from(oldScript.attributes)
      .forEach( attr => newScript.setAttribute(attr.name, attr.value) );
    newScript.appendChild(document.createTextNode(oldScript.innerHTML));
    oldScript.parentNode.replaceChild(newScript, oldScript);
  });
}

Usage

$0.innerHTML = HTML;    // does *NOT* run <script> tags in HTML
setInnerHTML($0, HTML); // does run <script> tags in HTML

@pery mimon 2018-08-12 15:30:40

typo : the name of the function is setInnerHtml not setInnerHTML

@NKCSS 2019-01-19 10:05:13

I needed something similar, but needed the script to remain or be re-created in the same spot as the original script, since my script targets the location of the script tag in the DOM to create/target elements. I also made the script recursive to make sure it also works if it is more than one level down.

NOTE: I use const here, if you have a older browser, just use var.

    window.exec_body_scripts = function(body_el) {
        // ref: https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml based on Larry K's answer
        // 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.
        const
            type__Js = 'text/javascript',
            tagName__Script = 'script',
            tagName__Script__Upper = tagName__Script.toUpperCase();
        var scripts = [], script, i;
        function evalScript(elem) {
            var parent = elem.parentNode,
                data = (elem.text || elem.textContent || elem.innerHTML || ""),
                script = document.createElement(tagName__Script);

            script.type = type__Js;
            try {
                // doesn't work on ie...
                script.appendChild(document.createTextNode(data));
            } catch (e) {
                // IE has funky script nodes
                script.text = data;
            }
            // Make sure to re-insert the script at the same position
            // to make sure scripts that target their position
            // in the DOM function as expected.
            var parent = elem.parentNode;
            parent.insertBefore(script, elem);
            parent.removeChild(elem);
        };
        // Get all scripts (recursive)
        if (typeof (document.querySelectorAll) !== typeof (void 0)) {
            document.querySelectorAll('script').forEach((scr) => { if (!scr.type || scr.type.toLowerCase() === type__Js) scripts.push(scr); });
        }
        else {
            var children_nodes = body_el.childNodes, child;
            for (i = 0; children_nodes[i]; i++) {
                child = children_nodes[i];
                if (
                    child.nodeName
                    &&
                    child.nodeName.toUpperCase() === tagName__Script__Upper
                    &&
                    (
                        !child.type
                        ||
                        child.type.toLowerCase() === type__Js
                    )
                ) {
                    scripts.push(child);
                }
                // Recursive call
                window.exec_body_scripts(child);
            }
        }
        for (i = 0; scripts[i]; i++) {
            evalScript(scripts[i]);
        }
    };

@pery mimon 2018-08-13 09:50:03

Expending the answer of Lambder

document.body.innerHTML = '<img src="../images/loaded.gif" alt="" > onload="alert(\'test\');this.parentNode.removeChild(this);" />';

You can use base64 image to create and load your script

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAAADUlEQVQYV2P4//8/AwAI/AL+iF8G4AAAAABJRU5ErkJggg=="
    onload="var script = document.createElement('script');  script.src = './yourCustomScript.js'; parentElement.append(script);" />

Or if you have a Iframe you can use it instead

<iframe src='//your-orginal-page.com' style='width:100%;height:100%'
    onload="var script = document.createElement('script');  script.src = './your-coustom-script.js'; parentElement.append(script);"
    frameborder='0'></iframe>

@Gray 2018-02-12 16:31:12

Here is my solution in a recent project.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<h1 id="hello_world">Sample</h1>
<script type="text/javascript">
 var div = document.createElement("div");
  var t = document.createElement('template');
  t.innerHTML =  "Check Console tab for javascript output: Hello world!!!<br/><script type='text/javascript' >console.log('Hello world!!!');<\/script>";
  
  for (var i=0; i < t.content.childNodes.length; i++){
    var node = document.importNode(t.content.childNodes[i], true);
    div.appendChild(node);
  }
 document.body.appendChild(div);
</script>
 
</body>
</html>

@pery mimon 2018-08-12 15:24:35

it not work for me

@Trev14 2017-07-27 18:39:58

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"

@joshcomley 2017-03-06 14:55:24

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.

@DividedByZero 2014-11-03 14:25:58

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.

@John 2014-12-30 16:56:37

this helped me but i feel dirty using eval. making sure text cannot be compromised i don't see a vulnerability.

@jonathanKingston 2015-08-22 19:05:46

@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.

@Codewithcheese 2015-10-11 11:45:35

Testing this in Chrome 44 causes an infinite loop when appendChild is called since this increments the scripts.length value.

@robert4 2016-02-11 22:30:27

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.

@superhero 2018-03-03 18:50:40

only issue I ran into is that document.currentScript ofc changes...

@Lambder 2014-10-03 15:03:59

Just do:

document.body.innerHTML = '<img src="../images/loaded.gif" alt="" onload="alert(\'test\');this.parentNode.removeChild(this);" />';

@Dwayne Charrington 2016-02-02 00:55:08

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.

@Lambder 2016-11-18 11:29:46

@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.

@pery mimon 2018-08-13 07:07:54

look like a genius idea

@Lambder 2019-03-25 10:25:43

document.body.innerHTML is used as an illustration only. You could obviously create a dedicated div tag and have it there. I haven't provided the div creation code not to obscure the answer.

@BadOPCode 2014-07-23 19:16:09

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);
}

@IgniteCoders 2014-07-08 19:27:03

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

@iirekm 2014-05-07 08:19:34

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.

@AxD 2013-06-25 15:24:50

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();} );

@Bruce 2013-01-22 04:02:01

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

@Jorge Fuentes González 2014-12-03 22:08:52

Fast and pretty. Thank you.

@Floris 2017-12-11 18:04:31

Thats it! Thank you.

@Andreas 2010-04-07 14:06:51

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,

@RiggsFolly 2016-02-07 12:05:05

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.

@wetlip 2016-12-26 10:21:21

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

@Ron Burk 2017-12-16 06:40:12

var _in = document.getElementById(inject);, I think.

@Jorge 2011-09-16 17:47:05

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

@wetlip 2016-12-26 10:23:53

this doesnt work for me with chrome

@user447963 2010-09-15 03:01:00

@phidah... Here is a very interesting solution to your problem: http://24ways.org/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);" />

@baptx 2012-06-30 13:58:39

and even better, no need to have an image with "onerror" event, nice for quick XSS injection jvfconsulting.com/blog/47/… :)

@Savas Vedova 2015-04-14 08:56:39

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.

@kris 2017-04-26 05:57:39

love it ! (added style="display:none;) to hide the broken image icon

@basin 2019-03-22 08:01:37

Actually, <style> is better than <img>, because it does not make a network request

@Larry K 2010-07-14 20:48:56

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]);
  }
};

@iirekm 2014-05-07 08:20:13

Better use jQuery's $(parent).html(code) - see my answer below.

@Blizz 2014-08-29 08:10:05

Your code saved my day, thanks for that.

@S4beR 2014-10-31 16:53:18

once script is injected to DOM, how should I remove it?

@Mateusz Stawecki 2015-02-10 14:58:24

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); }

@Ryan Morlok 2015-11-09 15:27:34

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.

@Finesse 2017-03-30 01:14:03

Why not to use the global eval trick instead of creating a <script> element and inserting it to the <head>? They both execute JS code without exposing the current closure.

@fantactuka 2010-06-03 14:52:50

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);

@zavr 2019-02-13 03:46:30

yeah this method works but you'll get errors if you have comments or console.logs so watch out for that also you can modify to account for modules var modules = [] var cleaned = text.replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, function(m, tags, script){ if (/type="module"/.test(tags)) { modules.push(script) return } scripts += script + '\n' return '' })

@Darin Dimitrov 2010-04-07 11:58:33

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]);
}

Related Questions

Sponsored Content

15 Answered Questions

[SOLVED] How to insert an item into an array at a specific index (JavaScript)?

55 Answered Questions

[SOLVED] How do I check if an element is hidden in jQuery?

77 Answered Questions

[SOLVED] How do I detect a click outside an element?

  • 2008-09-30 13:17:12
  • Sergio del Amo
  • 1112113 View
  • 2284 Score
  • 77 Answer
  • Tags:   javascript jquery

79 Answered Questions

[SOLVED] How do I remove a particular element from an array in JavaScript?

  • 2011-04-23 22:17:18
  • Walker
  • 5841541 View
  • 7285 Score
  • 79 Answer
  • Tags:   javascript arrays

29 Answered Questions

[SOLVED] jQuery scroll to element

  • 2011-07-13 09:49:44
  • DiegoP.
  • 2260351 View
  • 2120 Score
  • 29 Answer
  • Tags:   javascript jquery

14 Answered Questions

[SOLVED] How can I select an element by name with jQuery?

25 Answered Questions

[SOLVED] Retrieve the position (X,Y) of an HTML element

12 Answered Questions

[SOLVED] How to replace innerHTML of a div using jQuery?

14 Answered Questions

[SOLVED] How to move an element into another element?

  • 2009-08-14 20:14:45
  • Mark Richman
  • 1013897 View
  • 1568 Score
  • 14 Answer
  • Tags:   javascript jquery html

16 Answered Questions

[SOLVED] How do I find out which DOM element has the focus?

  • 2009-01-30 20:21:31
  • Tony Peterson
  • 596810 View
  • 1190 Score
  • 16 Answer
  • Tags:   javascript dom

Sponsored Content