Inserisci il codice nel contesto della pagina utilizzando uno script di contenuto

Sto imparando come creare estensioni di Chrome. Ho appena iniziato a svilupparne uno per catturare gli eventi di YouTube. Voglio usarlo con il flash player di YouTube (più avanti cercherò di renderlo compatibile con 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!"); 

Il problema è che la console mi dà il “Started!” , ma non c’è “Stato modificato!” quando suono / metto in pausa i video di YouTube.

Quando questo codice è inserito nella console, ha funzionato. Che cosa sto facendo di sbagliato?

Gli script di contenuto sono eseguiti in un ambiente “mondo isolato” . Devi inserire il tuo metodo state() nella pagina stessa.

Quando si desidera utilizzare una delle API chrome.* nello script, è necessario implementare un gestore di eventi speciale, come descritto in questa risposta: Estensione di Chrome : recupero del messaggio originale di Gmail .

Altrimenti, se non devi usare chrome.* API, ti consiglio caldamente di inserire tutto il tuo codice JS nella pagina aggiungendo un tag :

Sommario

  • Metodo 1: Inietti un altro file
  • Metodo 2: inserire il codice incorporato
  • Metodo 2b: utilizzo di una funzione
  • Metodo 3: utilizzo di un evento inline
  • Valori dinamici nel codice iniettato

Metodo 1: Inietti un altro file

Questo è il metodo più semplice / migliore quando hai un sacco di codice. Includi il tuo codice JS effettivo in un file all'interno della tua estensione, ad esempio script.js . Quindi lascia che lo script del tuo contenuto sia come segue (spiegato qui: Google Chome "Application Shortcut" Custom Javascript ):

 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 si utilizza questo metodo, il file script.js iniettato deve essere aggiunto alla sezione "web_accessible_resources" ( esempio ). Se non lo fai, Chrome rifiuterà di caricare il tuo script e visualizzerà il seguente errore nella console:

Negare il carico di chrome-extension: // [EXTENSIONID] /script.js. Le risorse devono essere elencate nella chiave manifest web_accessible_resources per essere caricate da pagine esterne all'estensione.

Metodo 2: inserire il codice incorporato

Questo metodo è utile quando vuoi eseguire rapidamente una piccola porzione di codice. (Vedi anche: Come disabilitare i tasti di scelta rapida di Facebook con l'estensione di 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: i modelli letterali sono supportati solo in Chrome 41 e versioni successive. Se vuoi che l'estensione funzioni in Chrome 40, utilizza:

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

Metodo 2b: utilizzo di una funzione

Per una grande porzione di codice, non è ansible citare la stringa. Invece di utilizzare un array, è ansible utilizzare una funzione e stringificare:

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

Questo metodo funziona, perché l'operatore + su stringhe e una funzione converte tutti gli oggetti in una stringa. Se si intende utilizzare il codice più di una volta, è consigliabile creare una funzione per evitare la ripetizione del codice. Un'implementazione potrebbe essere simile a:

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

Nota: poiché la funzione è serializzata, l'ambito originale e tutte le proprietà associate vengono perse!

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

Metodo 3: utilizzo di un evento inline

A volte, si desidera eseguire immediatamente del codice, ad esempio per eseguire codice prima che venga creato l'elemento . Questo può essere fatto inserendo un tag con textContent (vedi metodo 2 / 2b).

Un'alternativa, ma non consigliata, è l'uso di eventi in linea. Non è consigliato perché se la pagina definisce un criterio di sicurezza del contenuto che vieta gli script inline, i listener di eventi in linea vengono bloccati. Gli script inline iniettati dall'estensione, d'altra parte, continuano a essere eseguiti. Se vuoi ancora utilizzare eventi in linea, ecco come:

 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: questo metodo presuppone che non ci siano altri listener di eventi globali che gestiscono l'evento di reset . Se esiste, puoi anche scegliere uno degli altri eventi globali. Basta aprire la console JavaScript (F12), digitare document.documentElement.on e scegliere gli eventi disponibili.

Valori dinamici nel codice iniettato

Occasionalmente, è necessario passare una variabile arbitraria alla funzione iniettata. Per esempio:

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

Per iniettare questo codice, è necessario passare le variabili come argomenti alla funzione anonima. Assicurati di implementarlo correttamente! Quanto segue non funzionerà:

 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! 

La soluzione è usare JSON.stringify prima di passare l'argomento. Esempio:

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

Se hai molte variabili, vale la pena utilizzare JSON.stringify una volta, per migliorare la leggibilità, come segue:

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

L’unica cosa mancante nascosto dall’eccellente risposta di Row W è come chiamare dallo script iniettato allo script del contenuto e viceversa (specialmente se si hanno oggetti che non possono essere sottoposti a stringa).

Nell’injected o nello script di contenuto aggiungi un listener di eventi:

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

Dall’altra parte (contenuto o script iniettato) chiama l’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 })); 

Ho anche affrontato il problema dell’ordinamento degli script caricati, che è stato risolto tramite il caricamento sequenziale degli script. Il caricamento è basato sulla risposta di 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(); } } 

L’esempio di utilizzo sarebbe:

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

In realtà, sono un po ‘nuovo per JS, quindi sentitevi liberi di darmi un ping sui modi migliori.

nello script Contenuto, aggiungo tag script alla testa che lega un gestore ‘onmessage’, all’interno del gestore che uso, eval per eseguire il codice. Nello script di contenuto di booth utilizzo anche il gestore onmessage, quindi ottengo una comunicazione bidirezionale. Chrome Docs

 //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 è un listener di url post-messaggio

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

In questo modo, posso avere una comunicazione bidirezionale tra CS e Real Dom. È molto utile, ad esempio, se è necessario ascoltare eventi webscoket o qualsiasi variabile o evento di memoria.

Se desideri inserire funzioni pure, invece del testo, puoi utilizzare questo metodo:

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