Related
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
I need help on getting some dynamic js loading working properly. the issue I'm having now is that it is not consistently loading the js script when loading the page. here is the script that I have at the bottom of the page that I'm opening:
function getJSOnload() {
var element;
var parent = document.body;
var cdn = [
LoadFormValidationScript(LoginFormValidator),
LoadFormValidationScript(SetFormLocaleLang)
];
var i = 0, file;
for (i;i<cdn.length;i++) {
file = cdn[i];
element = document.createElement("script");
element.type = "text/javascript";
element.src = file;
parent.appendChild(element);
//free file's value
file = null;
}
};
if (window.addEventListener) {
window.addEventListener("load", getJSOnload(), false);
}
else if (window.attachEvent) {
window.attachEvent("onload", getJSOnload());
}
else window.onload = getJSOnload();
this is the LoadFormValidationScript that i have in an external js file
function LoadFormValidationScript(callback){
function LoadValidationScripts() {
$.getScript('/plugins/script1.min.js', function() {
$.getScript('/plugins/script2.min.js', function() {
$.getScript('/plugins/script3.addons.min.js', function() {
$.getScript('/plugins/script.es_ES.js', function() {
$.getScript('/plugins/script4.fr_FR.js', function() {
$.getScript('/plugins/script5.de_DE.js', callback);
});
});
});
});
});
}
if (!$.fn.formValidation){
LoadValidationScripts();
} else {
if (callback && typeof(callback) === "function") {
callback();
}
}
}
and this is the "setFormLocaleLang" function as an example since it is short one
function SetFormLocaleLang(){
var nberOfForm = document.forms.length; //get number of forms on the page
// if there are no forms in the page don't waist your time
if (nberOfForm > 0) {
var Locale = "en_US"; //default formVlaidation language
//var lang = $('html').attr('lang'); // get the page language
var lang = $("#selectedLanguage").attr('data-lang-id');
// assign the Locale depending on page language
switch (lang) {
case "en" : Locale = 'en_US'; break;
case "fr" : Locale = 'fr_FR'; break;
case "es" : Locale = 'es_ES'; break;
case "de" : Locale = 'de_DE'; break;
default : break;
};
//set the locale for all the forms in the page
for (var i = 0; i < nberOfForm; i++) {
var strFormID = "#" + document.forms[i].id;
$(strFormID).formValidation('setLocale', Locale);
}
}
}
like is said it does work and load the scripts but not all the time, sometimes i have to refresh the page like 3 time for the script to load and sometimes it loads on the first try as soon as i open the page. not sure what is going on and why i get this behaviour.
You're attaching the handlers incorrectly - you're calling getJSOnload() immediately, instead of waiting for document-loaded, as you intend. Reference, don't call, the function:
if (window.addEventListener) {
window.addEventListener("load", getJSOnload, false);
}
else if (window.attachEvent) {
window.attachEvent("onload", getJSOnload);
}
else window.onload = getJSOnload;
As per this post Log a web page's dynamically-created tag attributes with a userscript
i retrieved the attributes and also included the array as suggested like
addJS_Node ("var arr=[];");
i successfully pushed the src attributes to arr array because i didn't get any error.
Now i am confused where to display the array.
My code is as follows
// ==UserScript==
// #name Tags Monitoring
// #namespace PC
// #run-at document-start
// ==/UserScript==
//--- Intercept and log document.createElement().tagattributes
addJS_Node ("var arr=[];");
function LogNewTagCreations ()
{
var oldDocumentCreateElement = document.createElement;
document.createElement = function (tagName)
{
var elem = oldDocumentCreateElement.apply (document, arguments);
if(tagName=='script')
GetScriptAttributes (elem);
return elem;
}
}
function GetScriptAttributes (elem, tagNum, timerIntVar)
{
/*--- Because the tags won't be set for some while, we need
to poll for when they are added.
*/
GetScriptAttributes.tagNum = GetScriptAttributes.tagNum || 0;
if ( ! tagNum)
{
GetScriptAttributes.tagNum++;
tagNum = GetScriptAttributes.tagNum;
}
/*-- Getting the required attributes */
if (elem.src)
{
doneWaiting ();
arr.push(elem.src);
console.log (
tagNum," has a src attribute of:", elem.src,"=========",arr.length
);
}
else
{
if ( ! timerIntVar)
{
var timerIntVar = setInterval
(
function ()
{
GetScriptAttributes (elem, tagNum, timerIntVar);
},
50
);
}
}
function doneWaiting ()
{
if (timerIntVar)
{
clearInterval (timerIntVar);
}
}
}
/*--- The userscript or GM script will start running before the DOM is available.
Therefore, we wait...
*/
var waitForDomInterval = setInterval (
function () {
var domPresentNode;
if (typeof document.head == "undefined")
domPresentNode = document.querySelector ("head, body");
else
domPresentNode = document.head;
if (domPresentNode) {
clearInterval (waitForDomInterval);
addJS_Node (GetScriptAttributes.toString() );
addJS_Node (null, null, LogNewTagCreations);
}
},
1
);
addJS_Node("document.onreadystatechange = function (){if (document.readyState == 'complete'){console.log(arr.length);}}");
//--- Handy injection function.
function addJS_Node (text, s_URL, funcToRun) {
var D = document;
var scriptNode = D.createElement ('script');
scriptNode.type = "text/javascript";
if (text) scriptNode.textContent = text;
if (s_URL) scriptNode.src = s_URL;
if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';
var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
targ.appendChild (scriptNode);
}
I want to print the number of tags created dynamically that is why i added array. I first tried with script tags only and how can i know the array is completed and so that i can print the array length
Looks like your problem is not with your code.
You simply dont know javascript at all.
I strongly recommend you start with simple codes and tutorials.
Try to code your first script.
If you have any doubt about your code, feel free to come and ask about it.
The code that Brock Adams gave you is working perfectly.
I just copy-pasted it into Greasemonkey.
Here is the output:
And please go back to Log a web page's dynamically-created tag attributes with a userscript and mark his answer as the right one.
This code is being used on a Chrome Extension.
When I call the "showOrHideYT()" function, I get a
"Uncaught ReferenceError: showOrHideYT is not defined | (anonymous
function) | onclick"
This code will search for youtube links in a page, and it will add a button (it's really a div with an event) next to the link to show the iframe with the embedded video, pretty much like Reddit Enhancement Suite. Consider the code, per se, incomplete. I just want to know what am i missing when i call the "showOrHideYT(frameZES12345)" function.
if needed, i can provide manifest.json.
Thanks
function showOrHideYT(id)
{
var YTvidWidth = 420;
var YTvidHeight = 315;
frameYT=getElementById(id);
console.log(frameYT.style.visibility);
if (frameYT.style.visibility == "hidden")
{
frameYT.style.width = YTvidWidth+"px";
frameYT.style.height = YTvidHeight+"px";
frameYT.style.visibility = "visible";
}
if (frameYT.style.visibility == "visible")
{
frameYT.style.width = "0px";
frameYT.style.height = "0px";
frameYT.style.visibility = "hidden";
}
};
// DOM utility functions
function insertAfter( referenceNode, newNode ) {
if ((typeof(referenceNode) == 'undefined') || (referenceNode == null)) {
console.log(arguments.callee.caller);
} else if ((typeof(referenceNode.parentNode) != 'undefined') && (typeof(referenceNode.nextSibling) != 'undefined')) {
if (referenceNode.parentNode == null) {
console.log(arguments.callee.caller);
} else {
referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling );
}
}
};
function createElementWithID(elementType, id, classname) {
obj = document.createElement(elementType);
if (id != null) {
obj.setAttribute('id', id);
}
if ((typeof(classname) != 'undefined') && (classname != '')) {
obj.setAttribute('class', classname);
}
return obj;
};
///////////////////////////////////////
$(document).ready(function() {
var vidWidth = 420;
var vidHeight = 315;
var linksSemID = document.getElementsByTagName("a") ;
for (var i = 0; i < linksSemID.length; i++){
if (/id=$/.test(linksSemID[i].href)) links[i].href += "1";
}
i=0;
var youTubeRegExp = /(?:v=)([\w\-]+)/g;
var forEach = Array.prototype.forEach;
var linkArray = document.getElementsByTagName('a');
forEach.call(linkArray, function(link){
linkArray.id="zes" + i++;
var linkTarget = link.getAttribute('href');
if (linkTarget!=null)
{
if (linkTarget.search(youTubeRegExp) !=-1)
{
console.log (linkTarget);
idVideo=linkTarget.match(/(?:v=)([\w\-]+)/g);
//idVideo = idVideo.replace("v=", "");
//add buton
botaoMais = document.createElement('DIV');
botaoMais.setAttribute('class','expando-button collapsed video');
botaoMais.setAttribute('onclick','showOrHideYT(frameZES'+ i +')');
insertAfter(link, botaoMais);
//add iframe
ifrm = document.createElement('IFRAME');
ifrm.setAttribute('src', 'http://www.youtube.com/embed/'+ idVideo);
ifrm.style.width = '0px';
ifrm.style.height = '0px';
ifrm.style.frameborder='0px';
ifrm.style.visibility = 'hidden';
ifrm.setAttribute('id', 'frameZES' + i);
insertAfter(link, ifrm);
}
}
});
});
When you use setAttribute with a string, the event will be executed in the context of the page. The functions which are defined in a Content script are executed in a sandboxed scope. So, you have to pass a function reference, instead of a string:
Replace:
botaoMais.setAttribute('onclick','showOrHideYT(frameZES'+ i +')');
With:
botaoMais.addEventListener('click', (function(i) {
return function() {
showOrHideYT("frameZES"+ i);
};
})(i));
Explanation of code:
(function(i) { ..})(i) is used to preserve the value of i for each event.
Inside this self-invoking function, another function is returned, used as an event listener to click.
I see that you are using jQuery in your code. I personally think if we are using a library like jQuery, then we should not mix the native javascript code and jQuery code.
You can use jQuery bind to bind your the functions you need to call on dom ready.
Read below to know more.
suppose you want to call a javascript function on a button click, Here is the HTML for the same.
<div id="clickme">
<input id= "clickmebutton" type="button" value = "clickme" />
</div>
suppose "test" is the function you need to call, here is the code for test function.
function test() {
alert("hello");
}
you now need to bind the test function on the button click.
$(document).ready(function() {
$("#clickmebutton").bind("click", function(){
// do what ever you want to do here
test();
});
});
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