Esecuzione di elementi <script> inseriti con .innerHTML


111

Ho uno script che inserisce alcuni contenuti in un elemento utilizzando innerHTML.

Il contenuto potrebbe ad esempio essere:

<script type="text/javascript">alert('test');</script>
<strong>test</strong>

Il problema è che il codice all'interno del <script>tag non viene eseguito. Ho cercato su Google un po 'ma non c'erano soluzioni apparenti. Se ho inserito il contenuto utilizzando jQuery, $(element).append(content);le parti dello script sono evalstate ottenute prima di essere iniettate nel DOM.

Qualcuno ha uno snippet di codice che esegue tutti gli <script>elementi? Il codice jQuery era un po 'complesso, quindi non riuscivo davvero a capire come fosse fatto.

Modifica :

Sbirciando nel codice jQuery sono riuscito a capire come fa jQuery, il che ha portato al seguente codice:

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>

1
Perché non puoi semplicemente iterare i figli dell'elemento, e per ognuno che è un elemento di script devi semplicemente eval () l'internoHtml di quel bambino? Questo è il modo in cui l'ho visto fare da un grande fornitore di componenti, ogni volta che completano un callback ajax che aggiunge cose al DOM fanno esattamente questo. Tieni presente però che può essere lento, specialmente in IE7.
slugster

2
Andreas: Se aggiungo una funzione, ad esempio function testFunction(){ alert('test'); }al codice inserito in innerHTML, e poi provo a chiamarla, si dice che la funzione non è definita.
phidah

1
Fantastica phidah, funziona come un fascino, acclamazioni
Marcin

3
Penso che sia assolutamente importante capire che questo è il comportamento previsto dal browser per prevenire attacchi di scripting cross-site. Se il testo che imposti come innerHTML è fornito da Bob, verrebbe eseguito sul browser di Alice causando danni (pensa a un forum in cui le persone possono scrivere commenti aggiungendo tag di script). Puoi leggere di più al riguardo qui: en.wikipedia.org/wiki/Cross-site_scripting . Risparmia!
Xatian

1
Un HTML cambiato molto dal 2010. Questi giorni nostri, forse si vuole guardare: stackoverflow.com/a/58862506/890357
marciowb

Risposte:


27

Lo script dell'OP non funziona in IE 7. Con l'aiuto di SO, ecco uno script che funziona:

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]);
  }
};

4
Meglio usare jQuery $(parent).html(code)- vedi la mia risposta sotto.
iirekm

una volta che lo script è stato inserito nel DOM, come dovrei rimuoverlo?
S4beR

1
Lo script non è ricorsivo, quindi prenderà in considerazione solo i figli diretti. Questo funziona per me:if (nodeName(child, "script" ) && (!child.type || child.type.toLowerCase() === "text/javascript")) { scripts.push(child); } else { exec_body_scripts(child); }
st4wik

2
Si noti che il codice sopra non esegue script che vengono caricati tramite src. Lo script precedente può essere modificato per controllare elem.srce impostare in modo condizionale la srcproprietà dell'elemento script creato invece di impostare il suo contenuto di testo.
Ryan Morlok

Perché non utilizzare il evaltrucco globale invece di creare un <script>elemento e inserirlo nel file <head>? Entrambi eseguono il codice JS senza esporre la chiusura corrente.
Finesse

31

@phidah ... Ecco una soluzione molto interessante al tuo problema: http://24ways.org/2005/have-your-dom-and-script-it-too

Quindi sarebbe invece questo:

<img src="empty.gif" onload="alert('test');this.parentNode.removeChild(this);" />


2
e ancora meglio, non c'è bisogno di avere un'immagine con l'evento "onerror", bello per una rapida iniezione XSS jvfconsulting.com/blog/47/… :)
baptx

11
È possibile utilizzare <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" onload="alert('test');">se si desidera impedire una richiesta http inutile.
Savas Vedova

1
lo adoro ! (aggiunto style="display:none;) per nascondere l'icona dell'immagine rotta
kris

In realtà, <style>è meglio di <img>, perché non fa richiesta di rete
bacino

23

Ecco uno script più breve ed efficiente che funziona anche per gli script con la srcproprietà:

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);
        }
    }
}

Nota: sebbene evalpossa causare una vulnerabilità di sicurezza se non utilizzato correttamente, è molto più veloce della creazione di un tag di script al volo.


1
questo mi ha aiutato, ma mi sento sporco usando eval. assicurandomi che il testo non possa essere compromesso non vedo una vulnerabilità.
John

@ random-user è evalstato progettato per danneggiare gli utenti. Qualsiasi esecuzione di script dinamici è un rischio e questo è il motivo per cui CSP lo chiama 'unsafe-eval'perché lo è. Stai anche danneggiando la sicurezza dei tuoi siti se lo stai usando in una libreria in quanto non possono spegnerlo.
jonathanKingston

Il test in Chrome 44 causa un ciclo infinito quando viene chiamato appendChild poiché questo incrementa il valore scripts.length.
Codewithcheese

4
Gli script con la srcproprietà verranno scaricati in modo asincrono ed eseguiti come arrivati. L'ordinazione non viene conservata. Anche gli script inline verranno eseguiti fuori ordine, in modo sincrono prima di quelli asincroni.
robert 4

21

Non dovresti usare la proprietà innerHTML ma piuttosto il metodo appendChild del Node: un nodo in un albero di documenti [HTML DOM]. In questo modo potrai chiamare in un secondo momento il codice inserito.

Assicurati di capire che node.innerHTML non è la stessa cosa di node.appendChild . Potresti dedicare un po 'di tempo al riferimento al client Javascript per maggiori dettagli e al DOM. Spero che quanto segue aiuti ...

L'iniezione di esempio funziona:

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

Saluti,


2
Finalmente qualcuno che in realtà spiega un po 'il problema piuttosto che tutte le altre try this, look how clever I amrisposte. Merita un UV, ha ottenuto il mio.
RiggsFolly

Questo perché è il modo più semplice per iniettare codice javascript che viene eseguito dopo l'iniezione. Solo non capisco la differenza tra l'aggiunta con innerHTML che non viene eseguito e il modo sopra con appendChild che viene eseguito. L'ho usato con successo per creare una pagina dinamica con script da zero con socket.io
wetlip

2
var _in = document.getElementById(inject);, Penso.
Ron Burk

21

Versione ES6 semplificata della risposta di @ joshcomley con un esempio.

Nessun JQuery, nessuna libreria, nessuna valutazione, nessuna modifica DOM, solo puro Javascript.

http://plnkr.co/edit/MMegiu?p=preview

var setInnerHTML = function(elm, html) {
  elm.innerHTML = html;
  Array.from(elm.querySelectorAll("script")).forEach( oldScript => {
    const newScript = document.createElement("script");
    Array.from(oldScript.attributes)
      .forEach( attr => newScript.setAttribute(attr.name, attr.value) );
    newScript.appendChild(document.createTextNode(oldScript.innerHTML));
    oldScript.parentNode.replaceChild(newScript, oldScript);
  });
}

uso

$0.innerHTML = HTML;    // does *NOT* run <script> tags in HTML
setInnerHTML($0, HTML); // does run <script> tags in HTML

1
errore di battitura: il nome della funzione setInnerHtmlnon èsetInnerHTML
pery mimon

Si noti che nel plnkr che è collegato, il nome della funzione è setInnerHTMLma viene chiamato setInnerHtmldall'interno della runBfunzione. Pertanto l'esempio non funziona
danbars

16

Prova questo 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);

sì questo metodo funziona ma riceverai errori se hai commenti o console.logs quindi fai attenzione anche a questo che puoi modificare per tenere conto dei moduli var modules = [] var clean = text.replace (/ <script ([^> ] *)> ([\ s \ S] *?) <\ / script> / gi, function (m, tags, script) {if (/type="module"/.test(tags)) {modules.push (script) return} scripts + = script + '\ n' return ''})
zavr

13
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);  
   }  
}  

Funziona in Chrome nel mio progetto


Veloce e carino. Grazie.
Jorge Fuentes González

Questo è tutto! Grazie.
Floris

7

Una soluzione senza usare "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);
  }
}

Questo essenzialmente clona il tag di script e quindi sostituisce il tag di script bloccato con quello appena generato, consentendo così l'esecuzione.


3

scriptNode.innerHTML = codenon ha funzionato per IE. L'unica cosa da fare è sostituirla scriptNode.text = codee funziona bene


3

È più facile usare jquery $(parent).html(code)invece di 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
    })
}

Funziona anche con gli script che utilizzano document.writee gli script caricati tramite srcattributo. Purtroppo anche questo non funziona con gli script di Google AdSense.


1
Cosa ti fa dire che è più facile? Non è nemmeno più breve. Penso sempre che un uso eccessivo di jQuery sia una cattiva idea.
Manngo

2

Basta fare:

document.body.innerHTML = document.body.innerHTML + '<img src="../images/loaded.gif" alt="" onload="alert(\'test\');this.parentNode.removeChild(this);" />';

1
sembra un'idea geniale
pery mimon

0

Puoi dare un'occhiata a questo post . Il codice potrebbe essere simile a questo:

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]);
}

0

Grazie allo script di Larry, che ha funzionato perfettamente in IE10, questo è quello che ho usato:

$('#' + id)[0].innerHTML = result;
$('#' + id + " script").each(function() { this.text = this.text || $(this).text();} );

0

Estendendosi da Larry's. Ho fatto in modo che la ricerca ricorsiva nell'intero blocco e nei nodi figlio.
Lo script ora chiamerà anche script esterni specificati con il parametro src. Gli script vengono aggiunti alla testata anziché inseriti e posizionati nell'ordine in cui si trovano. Quindi gli script degli ordini specificatamente vengono preservati. E ogni script viene eseguito in modo sincrono in modo simile a come il browser gestisce il caricamento iniziale del DOM. Quindi, se hai un blocco di script che chiama jQuery da un CDN e il successivo nodo di script utilizza jQuery ... Nessun problema! Oh, e ho taggato gli script aggiunti con un ID serializzato basato su ciò che hai impostato nel parametro del tag in modo da poter trovare cosa è stato aggiunto da questo 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);
}

0

Prova questo, funziona per me su Chrome, Safari e Firefox:

var script = document.createElement('script');
script.innerHTML = 'console.log("hi")';
document.body.appendChild(script); 
--> logs "hi"

Una cosa da notare, però, è che il seguente script div-annidato NON verrà eseguito:

var script = document.createElement('div');
script.innerHTML = '<script>console.log("hi")</script>';
document.body.appendChild(script);
--> doesn't log anything

Affinché uno script possa essere eseguito, deve essere creato come nodo e quindi aggiunto come figlio. Puoi persino aggiungere uno script all'interno di un div iniettato in precedenza e verrà eseguito (mi sono imbattuto in questo prima quando ho provato a far funzionare il codice dell'ad server):

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"

0

Spendendo la risposta di Lambder

document.body.innerHTML = '<img src="../images/loaded.gif" alt="" > onload="alert(\'test\');this.parentNode.removeChild(this);" />';

Puoi usare l'immagine base64 per creare e caricare il tuo 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);" />

O se ne hai uno Iframepuoi usarlo invece

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

0

Avevo bisogno di qualcosa di simile, ma avevo bisogno che lo script rimanesse o venisse ricreato nello stesso punto dello script originale, poiché il mio script mira alla posizione del tag script nel DOM per creare / indirizzare gli elementi. Ho anche reso lo script ricorsivo per assicurarmi che funzioni anche se è più di un livello in basso.

NOTA: io uso constqui, se hai un browser più vecchio, usa semplicemente var.

    window.exec_body_scripts = function(body_el) {
        // ref: /programming/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]);
        }
    };

0

Creata questa nuova funzione di supporto in TypeScript, forse qualcuno la apprezzerà. Se rimuovi la dichiarazione del tipo dal parametro dello script, sarà semplicemente 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;


-1

Prova la funzione eval ().

data.newScript = '<script type="text/javascript">//my script...</script>'
var element = document.getElementById('elementToRefresh');
element.innerHTML = data.newScript;
eval(element.firstChild.innerHTML);

Questo è un vero esempio di un progetto che sto sviluppando. Grazie a questo post


-1

Ecco la mia soluzione in un progetto recente.

<!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>

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.