Inserir código no contexto da página usando um script de conteúdo

Estou aprendendo a criar extensões do Chrome. Eu comecei a desenvolver um para assistir aos events do YouTube. Eu quero usá-lo com o YouTube flash player (mais tarde vou tentar torná-lo compatível com HTML5).

manifest.json:

{ "name": "MyExtension", "version": "1.0", "description": "Gotta catch Youtube events!", "permissions": ["tabs", "http://*/*"], "content_scripts" : [{ "matches" : [ "www.youtube.com/*"], "js" : ["myScript.js"] }] } 

myScript.js:

 function state() { console.log("State Changed!"); } var player = document.getElementById("movie_player"); player.addEventListener("onStateChange", "state"); console.log("Started!"); 

O problema é que o console me dá o “Started!” , mas não há “estado mudado!” quando reproduzo / pausa vídeos do YouTube.

Quando este código é colocado no console, funcionou. O que estou fazendo de errado?

Os scripts de conteúdo são executados em um ambiente de “mundo isolado” . Você tem que injetar seu método state() na própria página.

Quando você deseja usar uma das APIs chrome.* No script, é necessário implementar um manipulador de events especial, conforme descrito nesta resposta: Extensão do Chrome – recuperando a mensagem original do Gmail .

Caso contrário, se você não precisar usar as APIs chrome.* , Recomendo enfaticamente injetar todo o código JS na página, adicionando uma tag :

Índice

  • Método 1: Injetar outro arquivo
  • Método 2: injetar código incorporado
  • Método 2b: usando uma function
  • Método 3: usando um evento in-line
  • Valores dynamics no código injetado

Método 1: Injetar outro arquivo

Esse é o método mais fácil / melhor quando você tem muito código. Inclua seu código JS real em um arquivo dentro de sua extensão, digamos script.js . Em seguida, deixe seu script de conteúdo ser o seguinte (explicado aqui: Google Custom "Atalho de Aplicativo" JavaScript personalizado ):

 var s = document.createElement('script'); // TODO: add "script.js" to web_accessible_resources in manifest.json s.src = chrome.extension.getURL('script.js'); s.onload = function() { this.remove(); }; (document.head || document.documentElement).appendChild(s); 

Nota: Se você usar este método, o arquivo script.js injetado script.js ser incluído na seção "web_accessible_resources" ( exemplo ). Caso contrário, o Chrome recusará o carregamento do seu script e exibirá o seguinte erro no console:

Negando carga de chrome-extension: // [EXTENSIONID] /script.js. Os resources devem ser listados na chave do manifesto web_accessible_resources para serem carregados por páginas fora da extensão.

Método 2: injetar código incorporado

Esse método é útil quando você deseja executar rapidamente um pequeno código. (Veja também: Como desativar as teclas de atalho do facebook com a extensão do Chrome? ).

 var actualCode = `// Code here. // If you want to use a variable, use $ and curly braces. // For example, to use a fixed random number: var someFixedRandomValue = ${ Math.random() }; // NOTE: Do not insert unsafe variables in this way, see below // at "Dynamic values in the injected code" `; var script = document.createElement('script'); script.textContent = actualCode; (document.head||document.documentElement).appendChild(script); script.remove(); 

Nota: os literais de modelo são suportados apenas no Chrome 41 e acima. Se você quiser que a extensão funcione no Chrome 40, use:

 var actualCode = ['/* Code here. Example: */' + 'alert(0);', '// Beware! This array have to be joined', '// using a newline. Otherwise, missing semicolons', '// or single-line comments (//) will mess up your', '// code ----->'].join('\n'); 

Método 2b: usando uma function

Para um grande pedaço de código, a citação da string não é viável. Em vez de usar uma matriz, uma function pode ser usada e sequenciada:

 var actualCode = '(' + function() { // All code is executed in a local scope. // For example, the following does NOT overwrite the global `alert` method var alert = null; // To overwrite a global variable, prefix `window`: window.alert = null; } + ')();'; var script = document.createElement('script'); script.textContent = actualCode; (document.head||document.documentElement).appendChild(script); script.remove(); 

Este método funciona, porque o operador + em strings e uma function converte todos os objects em uma string. Se você pretende usar o código mais de uma vez, é aconselhável criar uma function para evitar a repetição de código. Uma implementação pode parecer:

 function injectScript(func) { var actualCode = '(' + func + ')();' ... } injectScript(function() { alert("Injected script"); }); 

Nota: Como a function é serializada, o escopo original e todas as propriedades vinculadas são perdidas!

 var scriptToInject = function() { console.log(typeof scriptToInject); }; injectScript(scriptToInject); // Console output: "undefined" 

Método 3: usando um evento in-line

Às vezes, você quer executar algum código imediatamente, por exemplo, para executar algum código antes do elemento ser criado. Isso pode ser feito inserindo uma tag com textContent (consulte o método 2 / 2b).

Uma alternativa, mas não recomendada, é usar events in-line. Não é recomendável porque, se a página definir uma política de Segurança de Conteúdo que proíba os scripts embutidos, os ouvintes de evento in-line serão bloqueados. Inline scripts injetados pela extensão, por outro lado, ainda são executados. Se você ainda quiser usar events inline, é assim:

 var actualCode = '// Some code example \n' + 'console.log(document.documentElement.outerHTML);'; document.documentElement.setAttribute('onreset', actualCode); document.documentElement.dispatchEvent(new CustomEvent('reset')); document.documentElement.removeAttribute('onreset'); 

Nota: Esse método assume que não há outros listeners de events globais que manipulem o evento de reset . Se houver, você também pode escolher um dos outros events globais. Basta abrir o console do JavaScript (F12), digitar document.documentElement.on e selecionar os events disponíveis.

Valores dynamics no código injetado

Ocasionalmente, você precisa passar uma variável arbitrária para a function injetada. Por exemplo:

 var GREETING = "Hi, I'm "; var NAME = "Rob"; var scriptToInject = function() { alert(GREETING + NAME); }; 

Para injetar esse código, você precisa passar as variables ​​como argumentos para a function anônima. Certifique-se de implementá-lo corretamente! O seguinte não funcionará:

 var scriptToInject = function (GREETING, NAME) { ... }; var actualCode = '(' + scriptToInject + ')(' + GREETING + ',' + NAME ')'; // The previous will work for numbers and booleans, but not strings. // To see why, have a look at the resulting string: var actualCode = "(function(GREETING, NAME) {...})(Hi I'm,Rob)"; // ^^^^^^ ^^^ No string literals! 

A solução é usar JSON.stringify antes de passar o argumento. Exemplo:

 var actualCode = '(' + function(greeting, name) { ... } + ')(' + JSON.stringify(GREETING) + ',' + JSON.stringify(NAME) + ')'; 

Se você tiver muitas variables, vale a pena usar JSON.stringify uma vez, para melhorar a legibilidade, da seguinte maneira:

 ... } + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')'; 

A única coisa ausência de A resposta excelente da linha W é como chamar do script injetado para o script de conteúdo e vice-versa (especialmente se você tiver objects que não podem ser estigmatizados).

No script injetado ou no conteúdo, inclua um ouvinte de evento:

 document.addEventListener('yourCustomEvent', function (e) { var data=e.detail; console.log("received "+data); }); 

Do outro lado (conteúdo ou script injetado), ligue para o evento:

 var data="anything"; // updated: this works with Chrome 30: var evt=document.createEvent("CustomEvent"); evt.initCustomEvent("yourCustomEvent", true, true, data); document.dispatchEvent(evt); // the following stopped working in Chrome 30 (Windows), detail was // not received in the listener: // document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data })); 

Eu também enfrentei o problema de ordenação de scripts carregados, o que foi resolvido através do carregamento seqüencial de scripts. O carregamento é baseado na resposta de Rob W.

 function scriptFromFile(file) { var script = document.createElement("script"); script.src = chrome.extension.getURL(file); return script; } function scriptFromSource(source) { var script = document.createElement("script"); script.textContent = source; return script; } function inject(scripts) { if (scripts.length === 0) return; var otherScripts = scripts.slice(1); var script = scripts[0]; var onload = function() { script.parentNode.removeChild(script); inject(otherScripts); }; if (script.src != "") { script.onload = onload; document.head.appendChild(script); } else { document.head.appendChild(script); onload(); } } 

O exemplo de uso seria:

 var formulaImageUrl = chrome.extension.getURL("formula.png"); var codeImageUrl = chrome.extension.getURL("code.png"); inject([ scriptFromSource("var formulaImageUrl = '" + formulaImageUrl + "';"), scriptFromSource("var codeImageUrl = '" + codeImageUrl + "';"), scriptFromFile("EqEditor/eq_editor-lite-17.js"), scriptFromFile("EqEditor/eq_config.js"), scriptFromFile("highlight/highlight.pack.js"), scriptFromFile("injected.js") ]); 

Na verdade, sou meio novo no JS, então sinta-se à vontade para me direcionar para as melhores maneiras.

no script de conteúdo, eu adiciono tag de script na cabeça que liga um manipulador ‘onmessage’, dentro do manipulador que eu uso, eval para executar o código. No booth content script eu uso o manipulador onmessage também, então eu recebo comunicação bidirecional. Documentos do Chrome

 //Content Script var pmsgUrl = chrome.extension.getURL('pmListener.js'); $("head").first().append(""); //Listening to messages from DOM window.addEventListener("message", function(event) { console.log('CS :: message in from DOM', event); if(event.data.hasOwnProperty('cmdClient')) { var obj = JSON.parse(event.data.cmdClient); DoSomthingInContentScript(obj); } }); 

pmListener.js é um ouvinte de url de mensagem

 //pmListener.js //Listen to messages from Content Script and Execute Them window.addEventListener("message", function (msg) { console.log("im in REAL DOM"); if (msg.data.cmnd) { eval(msg.data.cmnd); } }); console.log("injected To Real Dom"); 

Dessa forma, posso ter uma comunicação bidirecional entre o CS e o Real Dom. É muito útil, por exemplo, se você precisar escutar events do webscoket ou qualquer variável ou evento na memory.

Se você deseja injetar uma function pura, em vez de texto, você pode usar este método:

 function inject(){ document.body.style.backgroundColor = 'blue'; } // this includes the function as text and the barentheses make it run itself. var actualCode = "("+inject+")()"; document.documentElement.setAttribute('onreset', actualCode); document.documentElement.dispatchEvent(new CustomEvent('reset')); document.documentElement.removeAttribute('onreset');