Executing – Finesse Mar 30 '17 at 01:14

24

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.

robert4
  • 1,072
  • 15
  • 20
DividedByZero
  • 4,333
  • 2
  • 19
  • 33
  • 1
    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
  • @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
  • 4
    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
  • newer invocation: `[...document.querySelectorAll(\`#${id} script\`)].forEach(script => { if (scripts.src != "") { ... }})` – mplungjan Nov 12 '20 at 13:09
18
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

Bruce
  • 181
  • 1
  • 2
18

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);
fantactuka
  • 3,298
  • 19
  • 29
  • 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(/ – zavr Feb 13 '19 at 03:46
11

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.

joshcomley
  • 28,099
  • 24
  • 107
  • 147
3

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.

iirekm
  • 8,890
  • 5
  • 36
  • 46
  • 1
    What makes you say that it is easier? It’s not even shorter. I always think that overusing jQuery is a bad idea. – Manngo Oct 21 '19 at 05:30
3

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"
Trev14
  • 3,626
  • 2
  • 31
  • 40
3

Execute script element by normal .innerHTML just don't put "" start and closing tags in .innerhtml value. Have a look on my code, it's just simple as normal code no jQuery or any other long JavaScript function

<h2>Click on Run to execute script</h2>
<button type="button" onclick="run()">Run</button>
<script id="my_script"></script>
<script>
document.getElementById("my_script").innerHTML= "function run(){alert('Wow! Script executed :)');}";

</script>
3

Using the short and sweet approach from https://stackoverflow.com/a/62641523/3394495 :

It first parses the HTML to a DocumentFragement (using createContextualFragment which is supported in all modern browsers, yet marked experimental) and then adds that to the DOM.

This will execute inline scripts.

export function setInnerHTMLAndExecuteScripts(element: HTMLElement, html: string) {
    const newContent = document.createRange().createContextualFragment(html);
    element.innerHTML = '';
    element.append(newContent);
}
cheshire
  • 190
  • 2
  • 4
3

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

Yi Jiang
  • 49,435
  • 16
  • 136
  • 136
Jorge
  • 31
  • 1
1

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);
}
BadOPCode
  • 11
  • 1
1

Just do:

document.body.innerHTML = document.body.innerHTML + '<img src="../images/loaded.gif" alt="" onload="alert(\'test\');this.parentNode.removeChild(this);" />';
Lambder
  • 2,953
  • 1
  • 26
  • 20
1

Made this new helper function in TypeScript, maybe someone will appreciate it. If you remove type declaration from script parameter it will just be plain JS.

const evalPageScripts = () => {
  const scripts = document.querySelectorAll('script');

  scripts.forEach((script: HTMLScriptElement) => {
    const newScript = document.createElement('script');
    newScript.type = 'text/javascript';
    newScript.src = script.src;

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

    return document.body.appendChild(newScript);
  })
};

export default evalPageScripts;
kvba
  • 309
  • 3
  • 8
0

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();} );
AxD
  • 2,714
  • 3
  • 31
  • 53
0

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]);
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
0

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>
Gray
  • 67
  • 3
0

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>
pery mimon
  • 7,713
  • 6
  • 52
  • 57
0

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]);
        }
    };
NKCSS
  • 2,716
  • 1
  • 22
  • 38
0

I had also pages that needed to execute javascript code (with eval) after the javascript files are loaded so did put load events on the script createelement so when the javascript file(s) were loaded gets executed. My website is a MVC SPA application. It loads partial pages with ajax. Those partial pages are set with innerhtml and then the javascript files are loaded and the javascript code on thst partial page.

-1

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

Community
  • 1
  • 1
IgniteCoders
  • 4,834
  • 3
  • 44
  • 62