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