web-dev-qa-db-fra.com

Exécution d'éléments <script> insérés avec .innerHTML

J'ai un script qui insère du contenu dans un élément en utilisant innerHTML.

Le contenu pourrait par exemple être:

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

Le problème est que le code à l'intérieur de la balise <script> n'est pas exécuté ..__ Je l'ai googlé un peu mais il n'y avait pas de solution apparente. Si j'ai inséré le contenu à l'aide de jQuery $(element).append(content);, les parties du script ont obtenu une eval 'd avant d'être injectées dans le DOM.

Quelqu'un at-il un bout de code exécutant tous les éléments <script>? Le code jQuery était un peu complexe, je ne pouvais donc pas comprendre comment cela se faisait.

Modifier :

En fouillant dans le code jQuery, j'ai réussi à comprendre comment jQuery le fait, ce qui a abouti au code suivant:

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

Le script de l'OP ne fonctionne pas dans IE 7. Avec l'aide de SO, voici un script qui fonctionne:

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

@phidah ... Voici une solution très intéressante à votre problème: http://24ways.org/2005/have-your-dom-and-script-it-too

Cela ressemblerait donc à ceci:

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

28
user447963

Voici un script plus court, plus efficace, qui fonctionne également pour les scripts dotés de la propriété src:

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

Remarque: Bien que eval puisse être à l'origine d'un problème de sécurité, s'il n'est pas utilisé correctement, il est beaucoup plus rapide que la création instantanée d'une balise de script.

17
DividedByZero

Essayez cet extrait:

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);
15
fantactuka

Vous ne devez pas utiliser la propriété innerHTML mais plutôt la méthode appendChild du nœud: un nœud dans une arborescence de documents [HTML DOM]. De cette façon, vous pourrez appeler ultérieurement votre code injecté.

Assurez-vous de bien comprendre que node.innerHTML n'est pas identique à node.appendChild. Vous voudrez peut-être passer un peu de temps sur la référence client Javascript pour plus de détails et sur le DOM. J'espère que ce qui suit aide ...

L'injection d'échantillons fonctionne:

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

cordialement,

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

Cela fonctionne dans Chrome dans mon projet

8
Bruce

Une solution sans utiliser "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);
  }
}

Il s'agit essentiellement de cloner la balise de script, puis de remplacer la balise de script bloquée par la balise nouvellement générée, permettant ainsi son exécution.

7
joshcomley

Version simplifiée ES6 de la réponse de @ joshcomley avec un exemple.

Pas de JQuery, pas de bibliothèque, pas d'eval, pas de changement de DOM, juste du Javascript pur.

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

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

Usage

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

Il est plus facile d'utiliser jquery $(parent).html(code) au lieu de 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
    })
}

Cela fonctionne également avec les scripts qui utilisent document.write et les scripts chargés via l'attribut src. Malheureusement, même cela ne fonctionne pas avec les scripts Google AdSense.

3
iirekm

scriptNode.innerHTML = code n'a pas fonctionné pour IE. La seule chose à faire est de remplacer par scriptNode.text = code et cela fonctionne bien

3
Jorge

Il suffit de faire:

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

Dépenser la réponse de Lambder

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

Vous pouvez utiliser une image base64 pour créer et charger votre 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);" />

Ou si vous avez une Iframe vous pouvez l'utiliser à la place

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

Grâce au script de Larry, qui a parfaitement fonctionné dans IE10, voici ce que j'ai utilisé:

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

Essayez ceci, cela fonctionne pour moi sur Chrome, Safari et Firefox:

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

Une chose à noter cependant, est que le script imbriqué suivant ne sera PAS exécuté:

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

Pour qu'un script s'exécute, il doit être créé en tant que nœud, puis ajouté en tant qu'enfant. Vous pouvez même ajouter un script dans une div précédemment injectée et il sera exécuté (je l'ai déjà rencontré auparavant lorsque j'essayais de faire fonctionner le code de serveur publicitaire):

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
Trev14

J'avais besoin de quelque chose de similaire, mais j'avais besoin que le script reste ou soit recréé au même endroit que le script d'origine, car mon script cible l'emplacement de la balise de script dans le DOM pour créer/cibler des éléments. J'ai également rendu le script récursif pour m'assurer qu'il fonctionne également s'il y a plus d'un niveau.

NOTE: J'utilise const ici, si vous avez un navigateur plus ancien, utilisez simplement 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]);
        }
    };
0
NKCSS

Vous pouvez jeter un oeil à ce post . Le code pourrait ressembler à ceci:

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

Essayez la fonction eval ().

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

C’est un exemple réel tiré d’un projet que je suis en train de développer… .. Merci à ceci post

0
IgniteCoders

S'étendant au large de Larry. Je l'ai fait rechercher de manière récursive le bloc entier et les nœuds enfants.
Le script va maintenant appeler les scripts externes spécifiés avec le paramètre src . Les scripts sont ajoutés à la tête au lieu d’être insérés et placés dans l’ordre dans lequel ils ont été trouvés. Donc, spécifiquement, les scripts d’ordre sont préservés. Et chaque script est exécuté de manière synchrone, de la même manière que le navigateur gère le chargement initial du DOM. Donc, si vous avez un bloc de script qui appelle jQuery à partir d’un CDN et que le noeud de script suivant utilise jQuery ... Aucun problème! Oh et j'ai marqué les scripts ajoutés avec un identifiant sérialisé basé sur ce que vous avez défini dans le paramètre tag afin que vous puissiez trouver ce qui a été ajouté par ce 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
BadOPCode