570 votes

Utiliser un contenu script pour accéder aux variables et fonctions du contexte de la page.

J'apprends à créer des extensions pour Chrome. Je viens de commencer à en développer une pour capter les événements de YouTube. Je veux l'utiliser avec le lecteur flash de YouTube (plus tard, j'essaierai de le rendre compatible avec 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!");

Le problème est que la console me donne le "Commencé !" mais il n'y a pas de "L'État a changé !" lorsque je joue/pause des vidéos YouTube.

Lorsque ce code est mis dans la console, il a fonctionné. Qu'est-ce que je fais de mal ?

19 votes

Essayez d'enlever les guillemets autour du nom de votre fonction : player.addEventListener("onStateChange", state);

4 votes

Il est également à noter que lors de la rédaction des correspondances, n'oubliez pas d'inclure https:// o http:// ce www.youtube.com/* ne vous laisserait pas emballer l'extension et jetterait Erreur de séparateur de schéma manquant

1 votes

1091voto

Rob W Points 125904

Cause sous-jacente :
Les scripts de contenu sont exécutés dans une "monde isolé" l'environnement.

Solution :
Injectez le code dans la page en utilisant le DOM - ce code sera capable de accès fonctions/variables du contexte de la page ("monde principal") ou exposer aux fonctions/variables du contexte de la page (dans votre cas, il s'agit de la fonction state() méthode).

  • A noter au cas où la communication avec la page script est nécessaire :
    Utiliser DOM CustomEvent manipulateur. Exemples : un , deux y trois .

  • Note en cas chrome API est nécessaire dans la page script :
    Desde chrome.* Les API ne peuvent pas être utilisées dans la page script, vous devez les utiliser dans le contenu script et envoyer les résultats à la page script via la messagerie DOM (voir la note ci-dessus).

Avertissement de sécurité :
Une page peut redéfinir ou augmenter un prototype intégré, de sorte que votre code exposé peut échouer si la page le fait d'une manière incompatible. Si vous voulez vous assurer que votre code exposé s'exécute dans un environnement sûr, vous devez soit a) déclarer votre contenu script avec "run_at" : "document_start" et utiliser les méthodes 2-3 et non 1, ou b) extraire les modules d'origine via une iframe vide, exemple . Notez qu'avec document_start vous devrez peut-être utiliser DOMContentLoaded dans le code exposé pour attendre le DOM.

Table des matières

  • Méthode 1 : Injecter un autre fichier - compatible avec ManifestV3
  • Méthode 2 : Injection de code embarqué
  • Méthode 2b : Utilisation d'une fonction
  • Méthode 3 : Utilisation d'un événement en ligne - compatible avec ManifestV3
  • Méthode 4 : Utilisation du monde d'executeScript - ManifestV3 seulement
  • Valeurs dynamiques dans le code injecté

Méthode 1 : Injecter un autre fichier - compatible avec ManifestV3

Particulièrement utile lorsque vous avez beaucoup de code. Mettez le code dans un fichier dans votre extension, par exemple script.js . Puis le charger dans votre contenu script comme ça :

var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);

Le fichier js doit être exposé dans web_accessible_resources :

  • Exemple de manifest.json pour ManifestV2

    "web_accessible_resources": ["script.js"],
  • Exemple de manifest.json pour ManifestV3

    "web_accessible_resources": [{
      "resources": ["script.js"],
      "matches": ["<all_urls>"]
    }]

Sinon, l'erreur suivante apparaîtra dans la console :

Refus de chargement de chrome-extension://[EXTENSIONID]/script.js. Les ressources doivent être répertoriées dans la clé manifeste web_accessible_resources pour pouvoir être chargées par des pages extérieures à l'extension.

Méthode 2 : Injection d'un code embarqué

Cette méthode est utile lorsque vous souhaitez exécuter rapidement un petit morceau de code. (Voir aussi : Comment désactiver les touches de raccourci de Facebook avec l'extension 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();

Note : littéraux de modèle ne sont prises en charge qu'à partir de la version 41 de Chrome. Si vous souhaitez que l'extension fonctionne dans Chrome 40-, utilisez :

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éthode 2b : Utilisation d'une fonction

Pour un gros morceau de code, il n'est pas possible de citer la chaîne de caractères. Au lieu d'utiliser un tableau, on peut utiliser une fonction et la transformer en chaîne :

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

Cette méthode fonctionne, car le + sur les chaînes de caractères et une fonction convertit tous les objets en chaîne de caractères. Si vous avez l'intention d'utiliser le code plus d'une fois, il est sage de créer une fonction pour éviter la répétition du code. Une implémentation pourrait ressembler à ceci :

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

Remarque : Comme la fonction est sérialisée, la portée originale et toutes les propriétés liées sont perdues !

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

Méthode 3 : Utilisation d'un événement en ligne - compatible avec ManifestV3

Parfois, vous voulez exécuter un certain code immédiatement, par exemple avant que la fonction <head> est créé. Cela peut être fait en insérant un <script> tag avec textContent (voir méthode 2/2b).

Une alternative, mais non recommandé est d'utiliser des événements en ligne. Ce n'est pas recommandé car si la page définit une politique de sécurité du contenu qui interdit les scripts en ligne, alors les écouteurs d'événements en ligne sont bloqués. Les scripts en ligne injectés par l'extension, par contre, s'exécutent quand même. Si vous voulez toujours utiliser les événements en ligne, voici comment :

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

Remarque : Cette méthode suppose qu'il n'y a pas d'autres écouteurs d'événements globaux qui gèrent l'événement reset événement. Si c'est le cas, vous pouvez également choisir l'un des autres événements globaux. Il suffit d'ouvrir la console JavaScript (F12), de taper document.documentElement.on et choisissez l'un des événements disponibles.

Méthode 4 : Utilisation du monde d'executeScript - ManifestV3 seulement

Dans Chrome 95 ou plus récent, utilisez chrome.scripting.executeScript con world: 'MAIN' dans votre extension script comme le fond script ou la popup script, mais pas dans le contenu script.
Voir le documentation y exemples .

Valeurs dynamiques dans le code injecté

Occasionnellement, vous devez passer une variable arbitraire à la fonction injectée. Par exemple :

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

Pour injecter ce code, vous devez passer les variables comme arguments à la fonction anonyme. Veillez à l'implémenter correctement ! Ce qui suit no travail :

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 solution consiste à utiliser JSON.stringify avant de passer l'argument. Exemple :

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

Si vous avez beaucoup de variables, il vaut la peine d'utiliser la fonction JSON.stringify une fois, pour améliorer la lisibilité, comme suit :

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

113 votes

Cette réponse devrait faire partie de la documentation officielle. La documentation officielle devrait contenir la méthode recommandée --> 3 façons de faire la même chose... N'est-ce pas ?

4 votes

La méthode 1 est généralement préférable dans la mesure du possible, en raison des restrictions CSP (Content Security Policy) de Chrome pour certaines extensions.

13 votes

@Qantas94Heavy Le CSP de l'extension fait no affecter le contenu scripts. Seuls les page CSP est pertinent. La méthode 1 peut être bloquée en utilisant un script-src qui exclut l'origine de l'extension, la méthode 2 peut être bloquée en utilisant un CSP qui exclut "unsafe-inline"`.

93voto

hcris Points 9359

La seule chose manquant caché de l'excellente réponse de Rob W est de savoir comment communiquer entre la page injectée script et le contenu script.

Du côté récepteur (soit votre contenu script, soit la page injectée script), ajoutez un écouteur d'événements :

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

Du côté de l'initiateur (contenu script ou page injectée script) envoyer l'événement :

var data = {
  allowedTypes: 'those supported by structured cloning, see the list below',
  inShort: 'no DOM elements or classes/functions',
};

document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));

Notes :

  • La messagerie DOM utilise un algorithme de clonage structuré, qui peut transférer seulement certains types de données en plus des valeurs primitives. Il ne peut pas envoyer des instances de classe, des fonctions ou des éléments DOM.
  • Dans Firefox, pour envoyer un objet (c'est-à-dire pas une valeur primitive) du contenu script au contexte de la page, vous devez explicitement le cloner dans la cible en utilisant cloneInto (une fonction intégrée), sinon il échouera avec une erreur de violation de sécurité.

    document.dispatchEvent(new CustomEvent('yourCustomEvent', {
      detail: cloneInto(data, document.defaultView),
    }));

1 votes

En fait, j'ai créé un lien vers le code et l'explication à la deuxième ligne de ma réponse, à l'adresse suivante stackoverflow.com/questions/9602022/ .

1 votes

Disposez-vous d'une référence pour votre méthode mise à jour (par exemple, un rapport de bogue ou un cas de test ?) ? CustomEvent remplace le constructeur déprécié document.createEvent API.

0 votes

Pour moi, "dispatchEvent(new CustomEvent..." a fonctionné. J'ai Chrome 33. De plus, cela ne fonctionnait pas avant parce que j'ai écrit le addEventListener après avoir injecté le code js.

8voto

Dmitry Ginzburg Points 1787

J'ai également été confronté au problème de l'ordre des scripts chargés, qui a été résolu par le chargement séquentiel des scripts. Le chargement est basé sur La réponse 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();
    }
}

Un exemple d'utilisation serait :

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

En fait, je suis plutôt novice en matière de JS, alors n'hésitez pas à m'indiquer les meilleures méthodes.

7voto

doron aviguy Points 258

Dans le contenu script , j'ajoute le tag script à la tête qui lie un handler 'onmessage', à l'intérieur du handler j'utilise , eval pour exécuter le code. Dans le contenu de la cabine script, j'utilise également le gestionnaire onmessage, ce qui me permet d'obtenir une communication bidirectionnelle. Docs Chrome

//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.js est un écouteur d'url pour les messages postaux.

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

De cette façon, je peux avoir une communication bidirectionnelle entre CS et Real Dom. C'est très utile par exemple si vous avez besoin d'écouter les événements de la webcoket, ou à des variables ou événements en mémoire.

7voto

Arik Points 611

Vous pouvez utiliser une fonction utilitaire que j'ai créée dans le but d'exécuter du code dans le contexte de la page et de récupérer la valeur renvoyée.

Pour ce faire, on sérialise une fonction en une chaîne de caractères et on l'injecte dans la page Web.

L'utilité est disponible ici sur GitHub .

Exemples d'utilisation -

// Some code that exists only in the page context -
window.someProperty = 'property';
function someFunction(name = 'test') {
    return new Promise(res => setTimeout(()=>res('resolved ' + name), 1200));
}
/////////////////

// Content script examples -

await runInPageContext(() => someProperty); // returns 'property'

await runInPageContext(() => someFunction()); // returns 'resolved test'

await runInPageContext(async (name) => someFunction(name), 'with name' ); // 'resolved with name'

await runInPageContext(async (...args) => someFunction(...args), 'with spread operator and rest parameters' ); // returns 'resolved with spread operator and rest parameters'

await runInPageContext({
    func: (name) => someFunction(name),
    args: ['with params object'],
    doc: document,
    timeout: 10000
} ); // returns 'resolved with params object'

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X