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

Estou a aprender a criar extensões cromadas. Comecei a desenvolver um para ver eventos no YouTube. Eu quero usá-lo com YouTube flash player (mais tarde eu vou tentar torná-lo compatível com HTML5).

Manifesto.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 "iniciado!", mas não há nenhum estado alterado!" Quando eu toco / pausa vídeos do YouTube.

Quando este código for colocado no consola, funcionou. O que estou a fazer de errado?

Author: Makyen, 2012-03-01

5 answers

Os programas de conteúdo são executados num ambiente "mundo isolado" . Tem de injectar o seu método state() na própria página.

Quando quiser usar uma das APIs chrome.* do programa, terá de implementar um manipulador de eventos especial, como descrito nesta resposta: extensão cromática-recuperando a mensagem original do Gmail .

Caso contrário, se não tiver de utilizar chrome.* APIs, recomendo vivamente que injecte todo o seu código JS na página via adicionar uma etiqueta <script>:

Índice

    Método 1: injectar outro ficheiro
  • Método 2: injectar o código incorporado
  • método 2b: usando uma função
  • Método 3: Utilizar um evento em linha
  • valores dinâmicos no código injectado
Método 1: injectar outro ficheiro

Este é o método mais fácil / melhor quando você tem lotes de código. inclua o seu código JS actual num ficheiro dentro da sua extensão, digamos script.js. Então deixa o teu o programa de conteúdo deve ser o seguinte (explicado aqui: Google Chome "atalho de Aplicação" 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 utilizar este método, o ficheiro script.js injectado tem de ser adicionado ao "web_accessible_resources" secção (exemplo ). Se não o fizer, o Chrome irá recusar carregar o seu programa e mostrar o seguinte erro na consola:

Negando a carga de extensão cromada:/ / [EXTENSIONID] / script.js. Os recursos devem ser indicados em a chave de manifesto web_accessible_resources para ser carregada por páginas fora da extensão.

Método 2: injectar o código incorporado

Este método é útil quando você quer executar rapidamente um pequeno pedaço de código. (Veja também: como desactivar os atalhos do facebook com a extensão 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 modelos só são suportados no cromo 41 e acima. Se 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: Usar uma função

Para um grande pedaço de código, citar o texto não é viável. Em vez de usar um array, uma função pode ser usada, e stringificada:

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 cadeias de caracteres e uma função converte todos os objectos a uma cadeia de caracteres. Se você pretende usar o código mais de uma vez, é sábio criar uma função para evitar a repetição de código. Uma implementação pode parecer como:

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

Nota: dado que a função é serializada, o original escopo, e todas as propriedades ligadas estão perdidas!

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

Método 3: Utilizar um evento em linha

Às vezes, você quer executar algum código imediatamente, por exemplo, para executar algum código antes do elemento <head> é criado. Isto pode ser feito inserindo uma marca <script> com textContent (ver método 2/2b).

Uma alternativa, mas não recomendada é a utilização de eventos inline. Não é recomendado porque se a página define uma política de segurança de conteúdo que proíbe scripts inline, então inline os ouvintes de eventos estão bloqueados. Scripts Inline injetados pela extensão, por outro lado, ainda funcionam. Se ainda quiser utilizar eventos inline, é assim que:

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: este método assume que não existem outros ouvintes de eventos globais que lidem com o evento reset. Se houver, Você também pode escolher um dos outros eventos globais. Basta abrir a consola de JavaScript (F12), tipo document.documentElement.on, e pegar nos eventos disponíveis.

Valores dinâmicos na injecção código

Ocasionalmente, é necessário passar uma variável arbitrária para a função injectada. Por exemplo:

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

Para injectar este código, você precisa passar as variáveis como argumentos para a função anônima. Certifique-se de implementá-lo corretamente! O seguinte não irá trabalhar:

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 deve ser utilizada JSON.stringify antes de passar a discussão. Exemplo:

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

Se você tem muitas variáveis, vale a pena usar JSON.stringify uma vez, para melhorar legibilidade:

...
} + ')(' + JSON.stringify([arg1, arg2, arg3, arg4]) + ')';
 739
Author: Rob W, 2018-09-15 17:29:11

A única coisa que falta escondida da excelente resposta da linha W é como ligar do script injectado para o script de Conteúdo e vice-versa (especialmente se você tem objetos que não podem ser engordados).

Na escrita injectada ou no seu conteúdo, adicione um ouvinte de Eventos:

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

Do outro lado (conteúdo ou escrita injectada) chama 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 }));
 45
Author: laktak, 2013-11-06 15:42:16

Eu também enfrentei o problema de encomendar scripts carregados, que foi resolvido através do Carregamento sequencial 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 Utilização 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 novo no JS, por isso, podes dizer-me as melhores maneiras.
 8
Author: Dmitry Ginzburg, 2017-10-18 01:11:13

No script de conteúdo , adiciono uma marca de script à cabeça que liga um manipulador 'onmessage', dentro do manipulador que uso, eval para executar o código. No guião de conteúdo de booth, também uso um manipulador de mensagens, por isso, tenho uma comunicação de dois sentidos. Documentos Cromados

//Content Script

var pmsgUrl = chrome.extension.getURL('pmListener.js');
$("head").first().append("<script src='"+pmsgUrl+"' type='text/javascript'></script>");


//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.o js é um ouvinte de mensagens post

//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");
Assim, posso ter uma comunicação de duas maneiras entre o CS e o Dom Real. É muito útil, por exemplo, se você precisar ouvir eventos webscoket , ou a qualquer outro em memória variáveis ou eventos.
 6
Author: doron aviguy, 2016-01-11 08:29:11

Se deseja injectar função pura, em vez de texto, pode utilizar 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');

E você pode passar parâmetros (infelizmente nenhum Objeto e arrays podem ser pendurados) para as funções. Adiciona-o às baretheses, assim:

function inject(color){
    document.body.style.backgroundColor = color;
}

// this includes the function as text and the barentheses make it run itself.
var color = 'yellow';
var actualCode = "("+inject+")("+color+")"; 
 0
Author: Tarmo Saluste, 2018-04-05 11:31:55