Inject HTML Where Script is Included - javascript

what I would like to do is this. Wherever my script is included on the page, inject some html. So wherever:
<script src="http://demo.com/stats/stats.js"></script>
Is added to the page, I can output
<div id="stats"></div>
At that position in the page.

Don't overthink the problem:
document.write('<div id="stats"></div>');
This will fail however if the script is loaded asynchronously.
For a solution which works with asynchronously loaded scripts:
(function() {
function addHtml() {
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src !== 'http://demo.com/stats/stats.js') {
continue;
}
var div = document.createElement('div');
div.id = 'stats';
if (scripts[i].nextElementSibling) {
scripts[i].parentNode.insertBefore(div, scripts[i].nextElementSibling);
} else {
scripts[i].parentNode.appendChild(div);
}
}
}
if (document.readyState === "complete") {
addHtml();
} else {
document.addEventListener('DOMContentLoaded', addHtml, false);
}
})()

Related

load script by changing innerHTML [duplicate]

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>
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
function setInnerHTML(elm, html) {
elm.innerHTML = html;
Array.from(elm.querySelectorAll("script"))
.forEach( oldScriptEl => {
const newScriptEl = document.createElement("script");
Array.from(oldScriptEl.attributes).forEach( attr => {
newScriptEl.setAttribute(attr.name, attr.value)
});
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
newScriptEl.appendChild(scriptText);
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
});
}
Usage
$0.innerHTML = HTML; // does *NOT* run <script> tags in HTML
setInnerHTML($0, HTML); // does run <script> tags in HTML
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);" />
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>
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]);
}
};
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.
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);
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
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.
scriptNode.innerHTML = code didn't work for IE. The only thing to do is replace with scriptNode.text = code and it work fine
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.
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"
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>
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);
}
Just do:
document.body.innerHTML = document.body.innerHTML + '<img src="../images/loaded.gif" alt="" onload="alert(\'test\');this.parentNode.removeChild(this);" />';
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;
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);
}
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]);
}
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();} );
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>
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>
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]);
}
};
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.
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

Unable to detect Bootstrap in website after performing check in Javascript

I am building a widget to be deployed in any site as long as they add the script tag with the src pointing to the url I set. I am trying to use javascript to detect whether or not Bootstrap library has been loaded. If it is not loaded, then I will add references to Bootstrap CDN. But, the problem is that it always return me false, so the code will inject new bootstrap script tag, therefore having 2 bootstrap tag at the same time which causes my modal bootstrap not working.
First of all, when my widget first loaded, it will check whether jQuery is loaded.
$(window).load(function()
{
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '1.4.2') {
var scriptTag = document.createElement('script');
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js");
if (scriptTag.readyState) {
scriptTag.onreadystatechange = function () { // For old versions of IE
if (this.readyState === 'complete' || this.readyState === 'loaded') {
loadScript();
}
};
} else {
scriptTag.onload = loadScript;
}
// Try to find the head, otherwise default to the documentElement
(document.getElementsByTagName("head")[0] || document.documentElement).appendChild(scriptTag);
} else {
// The jQuery version on the window is the one we want to use
jQuery = window.jQuery;
loadScript();
}
});
This is where it checks whether bootstrap has been loaded. The bootstrap_enabled is always false.
function loadScript()
{
var jsLibraries = [];
var bootstrap3_enabled = (typeof $().emulateTransitionEnd == 'function');
if (!bootstrap3_enabled) {
var bootstrapCss = $("<link>",
{
rel: "stylesheet",
type: "text/css",
href: "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.css"
});
bootstrapCss.appendTo('head');
jsLibraries.push("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js");
}
jQuery = window.jQuery.noConflict(true);
jsLibraries.push("https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js");
getScripts(jsLibraries, function () {
main();
});
}
function getScripts(list, fn) {
var cntr = list.length;
for (var i = 0; i < list.length; i++) {
$.getScript(list[i], function () {
--cntr;
if (cntr === 0) {
fn();
}
});
}
}
Can anyone tell me what goes wrong? Thanks in advance.

Close an injected iframe

I have a content script that runs from my chrome extension.
this script injects an iframe to the body of the current page.
i want to have the possibility to close the iframe from within the iframe.
how do i do this?
when i searched this issue on the web, almost each solution uses the window.parent.document property which for some reason is undefined in my case. any ideas?
EDIT - Code Sample:
in the HTML of the iframe:
<script type="text/javascript">
function frameClose() {
var windowFrames = window.parent.frames;
for (var i = 0; i < windowFrames.length; i++) {
var aFrame = windowFrames[i];
if (aFrame.name == 'myFrame') {
alert('in frame');
// WHAT TO DO HERE?
// window.parent.document is undefined
// aFrame.parentNode.removeChild(aFrame); - THIS DOES NOT WORK ALSO
break;
}
}
}
</script>
this is how i inject the iframe:
Extension.js
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {
file : "/js/PushIFrame.js"
}, function() {
if (chrome.extension.lastError) {
}
});
});
and in PushIFrame.js i have:
chrome.extension.sendMessage({
action: "pushFrame",
source: pushIframe(document)
});
function pushIframe(document) {
var existingFrame = document.getElementById('bmarkFrame');
if (existingFrame == null) {
var temp = document.createElement('iframe');
temp.id = 'myFrame';
temp.name = 'myFrame';
temp.setAttribute('scrolling', 'no');
temp.setAttribute('allowtransparency', 'true');
temp.style.border = 'none';
temp.style.height = '100%';
temp.style.width = '100%';
temp.style.position = 'fixed';
temp.style.zIndex = 99999999;
temp.style.top = 0;
temp.style.left = 0;
temp.style.display = 'block';
temp.src = 'https://www.mysite.com/';
document.body.appendChild(temp);
}
else {
existingFrame.style.display = 'block';
}
}
Let the content script (say PushIframe.js) bind a message event to the main frame. Then, whenever you want to hide the iframe, call parent.postMessage to notify the main frame. This message is received by the content script, from where you can hide the frame (as defined in your function pushIframe).
// PushIframe.js:
addEventListener('message', function(ev) {
if (ev.data === 'closeIframe') {
pushIframe(document); // Your code
}
});
// Iframe:
<script>
function frameClose() {
parent.postMessage('closeIframe', '*');
}
</script>

How to collect all script tags of HTML page in a variable

I would like to collect all the <script> ....</script> code section present in the HTML page in some variable.
What should be the simpler way to do this, Any idea how it can be retrieved using JavaScript.??
Any help will be greatly appreciated.
To get a list of scripts you can use
document.getElementsByTagName("script"); by tag
document.scripts; Built-in collection
document.querySelectorAll("script"); by selector
$("script") jQuery by selector
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src) console.log(i, scripts[i].src)
else console.log(i, scripts[i].innerHTML)
}
// To get the content of the external script
// - I use jQuery here - only works if CORS is allowing it
// find the first script from google
var url = $("script[src*='googleapis']")[0].src;
$.get(url,function(data) { // get the source
console.log(data.split("|")[0]); // show version info
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
console.log("Inline script");
</script>
<script>
function bla() {
console.log("Other inline script");
}
</script>
The simplest way is probably document.scripts
You would do:
var scripts = document.getElementsByTagName( 'script' );
Now scripts is a NodeList (like an array), and you can access each one using scripts[0], scripts[1] and so on.
try this
var scripts = document.getElementsByTagName("script");
Without jQuery :
var scripts = document.getElementsByTagName("script");
With jQuery :
var scripts = $("script");
Here you go --
(function () {
'use strict';
let logscript = function () {
let js = document.scripts;
for (let i = 0; i < js.length; i++) {
if (js[i].src) {
console.log(i, js[i].src);
} else {
console.log(i, js[i].innerHTML);
}
}
};
if (document.readyState === 'complete') {
logscript();
} else {
window.addEventListener('load', logscript);
}
})();

Executing <script> elements inserted with .innerHTML

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>
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
function setInnerHTML(elm, html) {
elm.innerHTML = html;
Array.from(elm.querySelectorAll("script"))
.forEach( oldScriptEl => {
const newScriptEl = document.createElement("script");
Array.from(oldScriptEl.attributes).forEach( attr => {
newScriptEl.setAttribute(attr.name, attr.value)
});
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
newScriptEl.appendChild(scriptText);
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
});
}
Usage
$0.innerHTML = HTML; // does *NOT* run <script> tags in HTML
setInnerHTML($0, HTML); // does run <script> tags in HTML
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);" />
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>
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]);
}
};
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.
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);
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
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.
scriptNode.innerHTML = code didn't work for IE. The only thing to do is replace with scriptNode.text = code and it work fine
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.
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"
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>
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);
}
Just do:
document.body.innerHTML = document.body.innerHTML + '<img src="../images/loaded.gif" alt="" onload="alert(\'test\');this.parentNode.removeChild(this);" />';
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;
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);
}
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]);
}
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();} );
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>
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>
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]);
}
};
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.
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

Categories