Executing <script> elements inserted with .innerHTML
Solution 1
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]);
}
};
Solution 2
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
Solution 3
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);" />
Solution 4
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:
<!DOCTYPE HTML>
<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>
Solution 5
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.
phidah
Updated on January 31, 2022Comments
-
phidah over 2 years
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 goteval
'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>
-
Andreas about 14 yearsHave 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 about 14 yearsI don't think that you can have execution upon insertion in the DOM.
-
slugster about 14 yearsWhy 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.
-
phidah about 14 yearsAndreas: 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. -
Andreas about 14 yearsHello, i have posted an answer that you might find helpful..
-
Marcin over 11 yearsAwesome phidah, works like charm, cheers
-
Xatian about 7 yearsI 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.wikipedia.org/wiki/Cross-site_scripting. Stay save!
-
marciowb over 4 yearsA HTML changed a lot since 2010. These present days, maybe you want look: stackoverflow.com/a/58862506/890357
-
DylanYoung about 4 yearsBecause the node has already loaded. Can't you just put the deferred attribute on your script tag?
-
-
baptx almost 12 yearsand even better, no need to have an image with "onerror" event, nice for quick XSS injection jvfconsulting.com/blog/47/… :)
-
iirekm about 10 yearsBetter use jQuery's
$(parent).html(code)
- see my answer below. -
S4beR over 9 yearsonce script is injected to DOM, how should I remove it?
-
Jorge Fuentes González over 9 yearsFast and pretty. Thank you.
-
John over 9 yearsthis helped me but i feel dirty using eval. making sure text cannot be compromised i don't see a vulnerability.
-
st4wik about 9 yearsThe 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); }
-
Savas Vedova about 9 yearsYou can use
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" onload="alert('test');">
if you want to prevent a useless http request. -
jonathanKingston over 8 years@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 over 8 yearsTesting this in Chrome 44 causes an infinite loop when appendChild is called since this increments the scripts.length value.
-
Ryan Morlok over 8 yearsNote 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 thesrc
property of the created script element instead of setting its text content. -
RiggsFolly about 8 yearsFinally 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. -
robert4 about 8 yearsScripts 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. -
wetlip over 7 yearsi 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
-
Finesse about 7 yearsWhy 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. -
kris about 7 yearslove it ! (added
style="display:none;
) to hide the broken image icon -
Floris over 6 yearsThats it! Thank you.
-
Ron Burk over 6 years
var _in = document.getElementById(inject);
, I think. -
pery mimon over 5 yearstypo : the name of the function is
setInnerHtml
notsetInnerHTML
-
pery mimon over 5 yearslook like a genius idea
-
zavr about 5 yearsyeah 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 '' })
-
basin about 5 yearsActually,
<style>
is better than<img>
, because it does not make a network request -
danbars almost 5 yearsNote that in the plnkr that is linked, the function name is
setInnerHTML
but it is being calledsetInnerHtml
from withinrunB
function. Therefore the example doesn't work -
Manngo over 4 yearsWhat makes you say that it is easier? It’s not even shorter. I always think that overusing jQuery is a bad idea.
-
mplungjan over 3 yearsnewer invocation:
[...document.querySelectorAll(`#${id} script`)].forEach(script => { if (scripts.src != "") { ... }})
-
Vizor about 3 yearsWorking example plnkr.co/edit/b54rQgoSkguOFpIJ
-
MiBol almost 3 yearsI have an issue with VUE and the v-html where I want to inject some HTML with scripts... this help me out to fix my problem. stackoverflow.com/questions/68042540/…
-
todbott almost 3 yearsSolved my problem after 4 hours of trial-and-error (using Springboot and Thymeleaf, by the way, and this still works).
-
Čamo almost 3 yearsOk but I get an error:
parameter 1 is not of type 'Node'.
-
Bestknighter over 2 yearsYep, @basin is correct. I used
<style onload="alert('test');"/>
and it worked like a charm. No network request, minimal increase in document/request size, invisible... If you still want to remove it, you can also use the removeChild trick. Thanks! -
like2think almost 2 yearsI appreciate the compact code and still solved an issue today.