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>

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.


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);" />


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

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


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

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

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 .


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 .


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

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

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.

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


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>


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.


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

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

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
}

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.


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.