Os scripts podem ser inseridos com innerHTML?

223

Tentei carregar alguns scripts em uma página usando innerHTMLa <div>. Parece que o script é carregado no DOM, mas nunca é executado (pelo menos no Firefox e Chrome). Existe uma maneira de executar scripts ao inseri-los innerHTML?

Código de amostra:

<!DOCTYPE html>
<html>
  <body onload="document.getElementById('loader').innerHTML = '<script>alert(\'hi\')<\/script>'">
    Shouldn't an alert saying 'hi' appear?
    <div id="loader"></div>
  </body>
</html>

Craig
fonte

Respostas:

88

Você precisa usar eval () para executar qualquer código de script que você inseriu como texto DOM.

O MooTools fará isso por você automaticamente, e tenho certeza que o jQuery também o faria (dependendo da versão. O jQuery versão 1.6+ usa eval). Isso economiza muito trabalho de analisar <script>tags e escapar do seu conteúdo, além de várias outras "dicas".

Geralmente, se você for fazer eval()isso por conta própria, deseja criar / enviar o código de script sem nenhuma marcação HTML, como <script>, pois isso não ocorrerá eval()corretamente.

zombat
fonte
12
O que realmente quero fazer é carregar um script externo, não apenas avaliar algum script local. Adicionar uma marca de script com innerHTML é muito mais curto do que criar um elemento DOM de script e adicioná-lo ao corpo, e estou tentando tornar meu código o mais curto possível. Você precisa criar os elementos de script dom e adicioná-los ao dom em vez de apenas usar algo como innerHTML? Existe uma maneira de fazer isso com document.write de dentro de uma função?
287 Craig
5
Como o zombat sugere, use uma estrutura Javascript para carregar o script externo, não tente reinventar a roda. O JQuery torna isso extremamente fácil, basta incluir o JQuery e chamar: $ .getScript (url). Você também pode fornecer uma função de retorno de chamada que será executada assim que o script for carregado.
Ariel Popovsky
2
Ariel está certa. Agradeço tentar manter seu código curto e adicionar uma <script>tag com innerHTMLpode ser curto, mas não funciona. É tudo apenas texto sem formatação até que seja executado eval(). E, infelizmente, eval()não analisa tags HTML, então você acaba com uma cadeia de problemas.
Zombat 29/07/2009
21
eval () não é uma ótima solução para nenhum problema.
Buley
2
Eu tentei eval (). É uma ideia horrível. Você tem que avaliar a coisa toda CADA VEZ . Mesmo se você declarar um nome e valor de variável, precisará declará-lo / revalorizá-lo () todas as vezes novamente para fazê-lo funcionar. É um pesadelo de erros.
Youstay Igo
88

Aqui está uma solução muito interessante para o seu problema: http://24ways.org/2005/have-your-dom-and-script-it-too

Portanto, use isso em vez de tags de script:

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

user447963
fonte
12
Isto é brilhante!
confile 20/08/13
Não funciona para mim, quando inserido em um resultado de uma solicitação ajax: Erro de sintaxe ausente; instrução before no início da string de script
Oliver
Como você adiciona a linha & lt; img src ... ao código da sua página? Você document.write () ou usa a abordagem document.body.innerHTML + = para isso? Ambos estão falhando para mim :(
Youstay Igo 4/15/15
Não é muito prático escrever muito código dentro de um onloadatributo. Além disso, isso requer que um arquivo adicional exista e seja carregado. A solução da momo é menos comprometida.
fregante
15
Você poderia Base64 codificar sua imagem de gatilho como <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">(isso não fará uma solicitação de rede) Na verdade ... você NÃO precisa de uma imagem, faça referência a uma imagem inexistente e, em vez de onloadusar onerror(mas isso fará uma solicitação de rede)
Danny '365CSI' Engelman
83

Aqui está um método que substitui recursivamente todos os scripts por executáveis:

function nodeScriptReplace(node) {
        if ( nodeScriptIs(node) === true ) {
                node.parentNode.replaceChild( nodeScriptClone(node) , node );
        }
        else {
                var i        = 0;
                var children = node.childNodes;
                while ( i < children.length ) {
                        nodeScriptReplace( children[i++] );
                }
        }

        return node;
}
function nodeScriptIs(node) {
        return node.tagName === 'SCRIPT';
}
function nodeScriptClone(node){
        var script  = document.createElement("script");
        script.text = node.innerHTML;
        for( var i = node.attributes.length-1; i >= 0; i-- ) {
                script.setAttribute( node.attributes[i].name, node.attributes[i].value );
        }
        return script;
}

Chamada de exemplo:

nodeScriptReplace(document.getElementsByTagName("body")[0]);
mmm
fonte
9
Estou um pouco surpreso que a sua resposta seja totalmente negativa. IMHO, esta é a melhor solução, esse método ainda permite restringir scripts com URLs ou conteúdo específicos.
Davidmh
1
@ inf3rno faz ou não? Costumava funcionar, alguém já alegou algo diferente?
mmm
quais são os objetivos de [0]? você pode usar nodeScriptReplace (document.getElementById (). html);
Bao Thai
@BaoThai Sim. Você pode.
mmm
Não parece ajudar no IWebBrowser2; Posso confirmar que as tags de script foram recriadas com createElement, mas ainda não consigo invocá-las via InvokeScript ().
Dave
46

Você pode criar um script e injetar o conteúdo.

var g = document.createElement('script');
var s = document.getElementsByTagName('script')[0];
g.text = "alert(\"hi\");"
s.parentNode.insertBefore(g, s);

Isso funciona em todos os navegadores :)

Pablo Moretti
fonte
1
A menos que não haja outros elementos de script no documento. Use em document.documentElementvez disso.
Eli Gray
4
Não é necessário porque você está escrevendo um script de outro script. <script> var g = document.createElement('script'); var s = document.getElementsByTagName('script')[0]; //reference this script g.text = "alert(\"hi\");" s.parentNode.insertBefore(g, s); </script>
Pablo Moretti
3
Quem disse que é de outro roteiro? Você pode executar o JavaScript sem <script>elementos. Por exemplo, <img onerror="..." src="#">e <body onload="...">. Se você quiser ser técnico, isso não funcionará em documentos não HTML / SVG, devido ao espaço para nome inexplicado.
Eli Gray
2
O Facebook usa a resposta de Pablo em seu SDK. developers.facebook.com/docs/javascript/quickstart/v2.2#loading
geoyws
30

Eu usei esse código, está funcionando bem

var arr = MyDiv.getElementsByTagName('script')
for (var n = 0; n < arr.length; n++)
    eval(arr[n].innerHTML)//run script inside div
Firas Nizam
fonte
1
Obrigado. Foi corrigido o problema de adicionar o código Disqus Universal a um pop-up modal criado usando o plug-in TinyBox2 Jquery.
Gsinha #
3
Infelizmente, esta solução não funciona quando o script contém funções que serão chamadas posteriormente.
Jose Gómez
7

Eu tive esse problema com o innerHTML, tive que anexar um script Hotjar à tag "head" do meu aplicativo Reactjs e ele teria que ser executado logo após o acréscimo.

Uma das boas soluções para importação dinâmica de Nó para a tag "head" é ​​o módulo React-helment .


Além disso, há uma solução útil para o problema proposto:

Nenhuma tag de script no innerHTML!

Acontece que o HTML5 não permite que tags de script sejam adicionadas dinamicamente usando a propriedade innerHTML. Portanto, o seguinte não será executado e não haverá alerta dizendo Olá, mundo!

element.innerHTML = "<script>alert('Hello World!')</script>";

Isso está documentado na especificação HTML5:

Nota: os elementos de script inseridos usando innerHTML não são executados quando são inseridos.

Mas cuidado, isso não significa que innerHTML esteja a salvo de scripts entre sites. É possível executar JavaScript via innerHTML sem usar tags, conforme ilustrado na página innerHTML do MDN .

Solução: Adicionando scripts dinamicamente

Para adicionar dinamicamente uma tag de script, você precisa criar um novo elemento de script e anexá-lo ao elemento de destino.

Você pode fazer isso para scripts externos:

var newScript = document.createElement("script");
newScript.src = "http://www.example.com/my-script.js";
target.appendChild(newScript);

E scripts embutidos:

var newScript = document.createElement("script");
var inlineScript = document.createTextNode("alert('Hello World!');");
newScript.appendChild(inlineScript); 
target.appendChild(newScript);
p.daniel
fonte
5

Para quem ainda tentando fazer isso, não, você não pode injetar um script usando innerHTML, mas é possível carregar uma string em uma marca de script usando um Blobe URL.createObjectURL.

Eu criei um exemplo que permite executar uma string como um script e obter as 'exportações' do script retornadas por meio de uma promessa:

function loadScript(scriptContent, moduleId) {
    // create the script tag
    var scriptElement = document.createElement('SCRIPT');

    // create a promise which will resolve to the script's 'exports'
    // (i.e., the value returned by the script)
    var promise = new Promise(function(resolve) {
        scriptElement.onload = function() {
            var exports = window["__loadScript_exports_" + moduleId];
            delete window["__loadScript_exports_" + moduleId];
            resolve(exports);
        }
    });

    // wrap the script contents to expose exports through a special property
    // the promise will access the exports this way
    var wrappedScriptContent =
        "(function() { window['__loadScript_exports_" + moduleId + "'] = " + 
        scriptContent + "})()";

    // create a blob from the wrapped script content
    var scriptBlob = new Blob([wrappedScriptContent], {type: 'text/javascript'});

    // set the id attribute
    scriptElement.id = "__loadScript_module_" + moduleId;

    // set the src attribute to the blob's object url 
    // (this is the part that makes it work)
    scriptElement.src = URL.createObjectURL(scriptBlob);

    // append the script element
    document.body.appendChild(scriptElement);

    // return the promise, which will resolve to the script's exports
    return promise;
}

...

function doTheThing() {
    // no evals
    loadScript('5 + 5').then(function(exports) {
         // should log 10
        console.log(exports)
    });
}

Eu simplifiquei isso da minha implementação real, então não há promessas de que não haja nenhum bug nela. Mas o princípio funciona.

Se você não se importa em recuperar qualquer valor após a execução do script, é ainda mais fácil; apenas deixar de fora os Promisee onloadpedaços. Você nem precisa quebrar o script ou criar a window.__load_script_exports_propriedade global .

JayArby
fonte
1
Eu apenas tentei e funciona no chrome 57. innerHTML em uma tag de script executa o texto.
iPherian
Isso é interessante, não funcionou antes. Eu me pergunto se esse comportamento é cross-browser ou apenas em cromo 57.
JayArby
4

Aqui está uma função recursiva para definir o innerHTML de um elemento que eu uso em nosso servidor de anúncios:

// o: container to set the innerHTML
// html: html text to set.
// clear: if true, the container is cleared first (children removed)
function setHTML(o, html, clear) {
    if (clear) o.innerHTML = "";

    // Generate a parseable object with the html:
    var dv = document.createElement("div");
    dv.innerHTML = html;

    // Handle edge case where innerHTML contains no tags, just text:
    if (dv.children.length===0){ o.innerHTML = html; return; }

    for (var i = 0; i < dv.children.length; i++) {
        var c = dv.children[i];

        // n: new node with the same type as c
        var n = document.createElement(c.nodeName);

        // copy all attributes from c to n
        for (var j = 0; j < c.attributes.length; j++)
            n.setAttribute(c.attributes[j].nodeName, c.attributes[j].nodeValue);

        // If current node is a leaf, just copy the appropriate property (text or innerHTML)
        if (c.children.length == 0)
        {
            switch (c.nodeName)
            {
                case "SCRIPT":
                    if (c.text) n.text = c.text;
                    break;
                default:
                    if (c.innerHTML) n.innerHTML = c.innerHTML;
                    break;
            }
        }
        // If current node has sub nodes, call itself recursively:
        else setHTML(n, c.innerHTML, false);
        o.appendChild(n);
    }
}

Você pode ver a demonstração aqui .

Adnan Korkmaz
fonte
3

Você poderia fazer assim:

var mydiv = document.getElementById("mydiv");
var content = "<script>alert(\"hi\");<\/script>";

mydiv.innerHTML = content;
var scripts = mydiv.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
    eval(scripts[i].innerText);
}
Jake
fonte
3

Aqui, uma solução que não usa evale funciona com scripts , scripts vinculados e também com módulos .

A função aceita 3 parâmetros:

  • html : String com o código html a ser inserido
  • dest : referência ao elemento de destino
  • append : sinalizador booleano para permitir a adição no final do elemento de destino html
function insertHTML(html, dest, append=false){
    // if no append is requested, clear the target element
    if(!append) dest.innerHTML = '';
    // create a temporary container and insert provided HTML code
    let container = document.createElement('div');
    container.innerHTML = html;
    // cache a reference to all the scripts in the container
    let scripts = container.querySelectorAll('script');
    // get all child elements and clone them in the target element
    let nodes = container.childNodes;
    for( let i=0; i< nodes.length; i++) dest.appendChild( nodes[i].cloneNode(true) );
    // force the found scripts to execute...
    for( let i=0; i< scripts.length; i++){
        let script = document.createElement('script');
        script.type = scripts[i].type || 'text/javascript';
        if( scripts[i].hasAttribute('src') ) script.src = scripts[i].src;
        script.innerHTML = scripts[i].innerHTML;
        document.head.appendChild(script);
        document.head.removeChild(script);
    }
    // done!
    return true;
}
colxi
fonte
Quero dizer ... Anexar uma tag de script com o conteúdo do código é uma avaliação, não é?
23719 Kevin B
@KevinB Existem diferenças notórias ... tente eval('console.log(this)')e você verá a mais óbvia
colxi
então o contexto é diferente e? ainda é apenas uma avaliação.
22719 Kevin B
@ KevinB Não, não é uma avaliação. Tente isso eval('let b=100').. e tente acessar bde fora do eval .... boa sorte com ele, você precisará dele
colxi
Funciona para mim. Cheers
Bezzzo 18/03
1

Krasimir Tsonev tem uma ótima solução que supera todos os problemas. Seu método não precisa usar eval; portanto, não existem problemas de desempenho nem de segurança. Ele permite que você defina a string innerHTML que contenha html com js e traduza-a imediatamente para um elemento DOM, além de executar as partes js existentes no código. curto, simples e funciona exatamente como você deseja.

Aproveite sua solução:

http://krasimirtsonev.com/blog/article/Convert-HTML-string-to-DOM-element

Anotações importantes:

  1. Você precisa envolver o elemento de destino com a tag div
  2. Você precisa envolver a string src com a tag div.
  3. Se você escrever a string src diretamente e incluir partes js, lembre-se de escrever as tags de script de fechamento corretamente (com \ before /), pois esta é uma string.
user3198805
fonte
1

Use em $(parent).html(code)vez deparent.innerHTML = code .

O seguinte também corrige scripts que usam document.writee scripts carregados via srcatributo. Infelizmente, mesmo isso não funciona com os scripts do Google AdSense.

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

Fonte

iirekm
fonte
1
um pouco atrasado aqui, mas qualquer pessoa que esteja usando esse método, observe que no JQuery você precisa carregar seus scripts usando $ .loadScript (url) em vez de <script src = "url> </script> - o último causará uma obsoleto erro XMLHttpRequest síncrono nos navegadores.
Stavm 23/11
1

Tente usar o template e document.importNode. Aqui está um exemplo:

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

cinzento
fonte
1
Isso não funciona com o Microsoft Edge, qualquer outra solução alternativa?
Soul
1

Você também pode quebrar o seu <script>assim e ele será executado:

<your target node>.innerHTML = '<iframe srcdoc="<script>alert(top.document.title);</script>"></iframe>';

Observe: O escopo interno srcdocrefere-se ao iframe, portanto, você deve usar topcomo no exemplo acima para acessar o documento pai.

naden
fonte
0

Sim, você pode, mas precisa fazer isso fora do DOM e a ordem deve estar correta.

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    n.innerHTML = scr;
    document.body.appendChild(n);
}

... irá alertar 'foo'. Isso não vai funcionar:

document.getElementById("myDiv").innerHTML = scr;

E mesmo isso não funcionará, porque o nó é inserido primeiro:

var scr = '<scr'+'ipt>alert("foo")</scr'+'ipt>';
window.onload = function(){
    var n = document.createElement("div");
    document.body.appendChild(n);
    n.innerHTML = scr;  
}
mwilcox
fonte
16
Pelo que vale a pena: isso parece não funcionar nos navegadores atuais.
Wichert Akkerman
0

Minha solução para esse problema é definir um Mutation Observer para detectar <script></script>nós e substituí-lo por um novo <script></script>nó com o mesmo src. Por exemplo:

let parentNode = /* node to observe */ void 0
let observer = new MutationObserver(mutations=>{
    mutations.map(mutation=>{
        Array.from(mutation.addedNodes).map(node=>{
            if ( node.parentNode == parentNode ) {
                let scripts = node.getElementsByTagName('script')
                Array.from(scripts).map(script=>{
                    let src = script.src
                    script = document.createElement('script')
                    script.src = src
                    return script
                })
            }
        })
    })
})
observer.observe(document.body, {childList: true, subtree: true});
gabriel garcia
fonte
1
Obrigado por me recusar a votação sem dizer o porquê. Amo todos vocês.
gabriel garcia
0

A menção de Gabriel Garcia aos MutationObservers está no caminho certo, mas não funcionou muito bem para mim. Não tenho certeza se isso foi devido a uma peculiaridade do navegador ou devido a um erro do meu lado, mas a versão que acabou funcionando para mim foi a seguinte:

document.addEventListener("DOMContentLoaded", function(event) {
    var observer = new MutationObserver(mutations=>{
        mutations.map(mutation=>{
            Array.from(mutation.addedNodes).map(node=>{
                if (node.tagName === "SCRIPT") {
                    var s = document.createElement("script");
                    s.text=node.text;
                    if (typeof(node.parentElement.added) === 'undefined')
                        node.parentElement.added = [];
                    node.parentElement.added[node.parentElement.added.length] = s;
                    node.parentElement.removeChild(node);
                    document.head.appendChild(s);
                }
            })
        })
    })
    observer.observe(document.getElementById("element_to_watch"), {childList: true, subtree: true,attributes: false});
};

Obviamente, você deve substituir element_to_watchpelo nome do elemento que está sendo modificado.

node.parentElement.addedé usado para armazenar as tags de script adicionadas document.head. Na função usada para carregar a página externa, você pode usar algo como o seguinte para remover as tags de script não mais relevantes:

function freeScripts(node){
    if (node === null)
        return;
    if (typeof(node.added) === 'object') {
        for (var script in node.added) {
            document.head.removeChild(node.added[script]);
        }
        node.added = {};
    }
    for (var child in node.children) {
        freeScripts(node.children[child]);
    }
}

E um exemplo do início de uma função de carregamento:

function load(url, id, replace) {
    if (document.getElementById(id) === null) {
        console.error("Element of ID "+id + " does not exist!");
        return;
    }
    freeScripts(document.getElementById(id));
    var xhttp = new XMLHttpRequest();
    // proceed to load in the page and modify innerHTML
}
pixelherodev
fonte
Você percebe que você está adicionando um novo MutationObservercada vez que um elemento é anexado ao documento, certo? Btw, eu me pergunto por que você diz que meu código não é funcional.
gabriel garcia
@gabrielgarcia Eu disse que seu código não estava funcional porque eu tentei e simplesmente não funcionou. Olhando para isso agora, é perfeitamente possível que estivesse em mim, não em você, e peço desculpas sinceramente pela maneira como expressei isso. Corrigindo agora.
pixelherodev
re: adicionando um MutationObserver cada vez que um elemento é adicionado ao documento, do que você está falando? O DOMContentLoaded, e cito o MDN aqui ", é acionado quando o documento HTML inicial foi completamente carregado e analisado, sem esperar que as folhas de estilo, imagens e sub-quadros terminem de carregar". Isso é uma vez e apenas uma vez. Além disso, este script está funcionando sem problemas no meu site, e a depuração mostra que isso só acontece uma vez, portanto, é uma vez na prática e também na teoria.
pixelherodev
1
você está certo ... eu errei. Minhas desculpas também.
gabriel garcia
@gabrielgarcia Não é um problema :)
pixelherodev
0

Para mim, a melhor maneira é inserir o novo conteúdo HTML através do innerHtml e, em seguida, usar

setTimeout(() => {
        var script_el = document.createElement("script")
        script_el.src = 'script-to-add.js'
        document.body.appendChild(script_el)
    }, 500)

O setTimeout não é necessário, mas funciona melhor. Isso funcionou para mim.

Luis Tiago Flores Cristóvão
fonte
-1

Etiqueta Executar (Java Script) do innerHTML

Substitua seu elemento de script por div com um atributo de classe class = "javascript" e feche-o com </div>

Não altere o conteúdo que você deseja executar (anteriormente estava na tag de script e agora está na tag div)

Adicione um estilo à sua página ...

<style type="text/css"> .javascript { display: none; } </style>

Agora execute eval usando jquery (o jquery js já deve estar incluído)

   $('.javascript').each(function() {
      eval($(this).text());

    });`

Você pode explorar mais aqui , no meu blog.

Servir conhecimento
fonte