Scripts de contenu

Les scripts de contenu sont des fichiers exécutés dans le contexte des pages Web. À l'aide du modèle objet de document (DOM) standard, elles peuvent lire les détails des pages Web que le navigateur consulte, les modifier et transmettre des informations à leur extension parente.

Comprendre les fonctionnalités des scripts de contenu

Les scripts de contenu peuvent accéder directement aux API d'extension suivantes:

Les scripts de contenu ne peuvent pas accéder directement à d'autres API. Toutefois, ils peuvent y accéder indirectement en échangeant des messages avec d'autres parties de votre extension.

Vous pouvez également accéder à d'autres fichiers de votre extension à partir d'un script de contenu, à l'aide d'API telles que fetch(). Pour ce faire, vous devez les déclarer comme ressources accessibles sur le Web. Notez que cela expose également les ressources à tous les scripts propriétaires ou tiers exécutés sur le même site.

Travailler dans des mondes isolés

Les scripts de contenu évoluent dans un monde isolé, ce qui leur permet de modifier leur environnement JavaScript sans entrer en conflit avec la page ou les scripts de contenu d'autres extensions.

Une extension peut s'exécuter dans une page Web avec un code semblable à l'exemple suivant.

webPage.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

Cette extension peut injecter le script de contenu suivant à l'aide de l'une des techniques décrites dans la section Injecter des scripts.

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

Avec ce changement, les deux alertes s'affichent dans l'ordre lorsque vous cliquez sur le bouton.

Injecter des scripts

Les scripts de contenu peuvent être déclarés de manière statique, déclarés de manière dynamique ou injectés de manière programmatique.

Injecter avec des déclarations statiques

Utilisez des déclarations de script de contenu statique dans manifest.json pour les scripts qui doivent être exécutés automatiquement sur un ensemble de pages bien connu.

Les scripts déclarés de manière statique sont enregistrés dans le fichier manifeste sous la clé "content_scripts". Ils peuvent inclure des fichiers JavaScript, des fichiers CSS ou les deux. Tous les scripts de contenu à exécution automatique doivent spécifier des modèles de correspondance.

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

Nom Type Description
matches tableau de chaînes Obligatoire. Indique les pages dans lesquelles ce script de contenu sera injecté. Pour en savoir plus sur la syntaxe de ces chaînes, consultez Formats de correspondance. Pour savoir comment exclure des URL, consultez Formats de correspondance et expressions génériques.
css tableau de chaînes Facultatif. Liste des fichiers CSS à injecter dans les pages correspondantes. Ils sont injectés dans l'ordre dans lequel ils apparaissent dans ce tableau, avant qu'un DOM ne soit construit ou affiché pour la page.
js tableau de chaînes Facultatif. Liste des fichiers JavaScript à injecter dans les pages correspondantes. Les fichiers sont injectés dans l'ordre dans lequel ils apparaissent dans ce tableau. Chaque chaîne de cette liste doit contenir un chemin d'accès relatif à une ressource dans le répertoire racine de l'extension. Les barres obliques initiales (/) sont automatiquement supprimées.
run_at RunAt Facultatif. Indique quand le script doit être injecté dans la page. La valeur par défaut est document_idle.
match_about_blank booléen Facultatif. Indique si le script doit être injecté dans un frame about:blank où le frame parent ou d'ouverture correspond à l'un des modèles déclarés dans matches. Valeur par défaut : "false".
match_origin_as_fallback booléen Facultatif. Indique si le script doit injecter dans des cadres créés par une origine correspondante, mais dont l'URL ou l'origine ne correspondent pas directement au format. Cela inclut les cadres avec différents schémas, tels que about:, data:, blob: et filesystem:. Consultez également la section Injecter dans des cadres associés.
world ExecutionWorld Facultatif. Environnement JavaScript dans lequel un script doit s'exécuter. La valeur par défaut est ISOLATED. Consultez également la section Travailler dans des mondes isolés.

Injecter avec des déclarations dynamiques

Les scripts de contenu dynamique sont utiles lorsque les modèles de correspondance des scripts de contenu ne sont pas bien connus ou lorsque les scripts de contenu ne doivent pas toujours être injectés sur des hôtes connus.

Introduites dans Chrome 96, les déclarations dynamiques sont similaires aux déclarations statiques, mais l'objet du script de contenu est enregistré auprès de Chrome à l'aide de méthodes dans l'espace de noms chrome.scripting plutôt que dans le fichier manifest.json. L'API de script permet également aux développeurs d'extensions de:

Comme les déclarations statiques, les déclarations dynamiques peuvent inclure des fichiers JavaScript, des fichiers CSS ou les deux.

service-worker.js

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

service-worker.js

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

service-worker.js

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

service-worker.js

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

Injecter de manière programmatique

Utilisez l'injection programmatique pour les scripts de contenu qui doivent s'exécuter en réponse à des événements ou à des occasions spécifiques.

Pour injecter un script de contenu par programmation, votre extension a besoin d'autorisations d'hôte pour la page dans laquelle elle tente d'injecter des scripts. Les autorisations hôte peuvent être accordées en les demandant dans le fichier manifeste de votre extension ou en utilisant temporairement "activeTab".

Vous trouverez ci-dessous différentes versions d'une extension basée sur activeTab.

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

Les scripts de contenu peuvent être injectés sous forme de fichiers.

content-script.js


document.body.style.backgroundColor = "orange";

service-worker.js :

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

Vous pouvez également injecter et exécuter le corps d'une fonction en tant que script de contenu.

service-worker.js :

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

Notez que la fonction injectée est une copie de la fonction référencée dans l'appel chrome.scripting.executeScript(), et non la fonction d'origine elle-même. Par conséquent, le corps de la fonction doit être autonome. Les références à des variables en dehors de la fonction entraîneront l'émission d'une exception ReferenceError par le script de contenu.

Lorsque vous injectez une fonction, vous pouvez également lui transmettre des arguments.

service-worker.js

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

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

Exclure les correspondances et les expressions régulières

Pour personnaliser la mise en correspondance de pages spécifiée, incluez les champs suivants dans un enregistrement déclaratif.

Nom Type Description
exclude_matches tableau de chaînes Facultatif. Exclut les pages dans lesquelles ce script de contenu serait autrement injecté. Pour en savoir plus sur la syntaxe de ces chaînes, consultez Formats de correspondance.
include_globs tableau de chaînes Facultatif. Appliqué après matches pour n'inclure que les URL qui correspondent également à ce glob. Il s'agit d'émuler le mot clé Greasemonkey @include.
exclude_globs tableau de chaînes Facultatif. Appliqué après matches pour exclure les URL correspondant à ce format générique. Destiné à émuler le mot clé Greasemonkey @exclude.

Le script de contenu est injecté dans une page si les deux conditions suivantes sont remplies:

  • Son URL correspond à n'importe quel format matches et include_globs.
  • L'URL ne correspond pas non plus à un format exclude_matches ou exclude_globs. Étant donné que la propriété matches est obligatoire, exclude_matches, include_globs et exclude_globs ne peuvent être utilisés que pour limiter les pages concernées.

L'extension suivante injecte le script de contenu dans https://www.nytimes.com/health, mais pas dans https://www.nytimes.com/business .

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

Les propriétés glob suivent une syntaxe différente et plus flexible que les modèles de correspondance. Les chaînes glob acceptées sont des URL pouvant contenir des astérisques et des points d'interrogation "génériques". L'astérisque (*) correspond à n'importe quelle chaîne de n'importe quelle longueur, y compris la chaîne vide, tandis que le point d'interrogation (?) correspond à n'importe quel caractère.

Par exemple, le caractère générique https://???.example.com/foo/\* correspond à l'un des éléments suivants:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

Toutefois, il ne correspond pas aux éléments suivants:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

Cette extension injecte le script de contenu dans https://www.nytimes.com/arts/index.html et https://www.nytimes.com/jobs/index.htm*, mais pas dans https://www.nytimes.com/sports/index.html:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Cette extension injecte le script de contenu dans https://history.nytimes.com et https://.nytimes.com/history, mais pas dans https://science.nytimes.com ni https://www.nytimes.com/science:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Vous pouvez inclure l'un, tous ou certains de ces éléments pour obtenir la portée appropriée.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Durée de diffusion

Le champ run_at contrôle le moment où les fichiers JavaScript sont injectés dans la page Web. La valeur recommandée et par défaut est "document_idle". Pour connaître les autres valeurs possibles, consultez le type RunAt.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
Nom Type Description
document_idle chaîne Recommandé. Utilisez "document_idle" dans la mesure du possible.

Le navigateur choisit un moment pour injecter des scripts entre "document_end" et immédiatement après le déclenchement de l'événement window.onload. Le moment exact de l'injection dépend de la complexité du document et de la durée de son chargement, et est optimisé pour la vitesse de chargement de la page.

Les scripts de contenu exécutés à "document_idle" n'ont pas besoin d'écouter l'événement window.onload. Ils sont exécutés une fois le DOM terminé. Si un script doit absolument s'exécuter après window.onload, l'extension peut vérifier si onload s'est déjà déclenché à l'aide de la propriété document.readyState.
document_start chaîne Les scripts sont injectés après les fichiers de css, mais avant qu'un autre DOM ne soit construit ou qu'un autre script ne soit exécuté.
document_end chaîne Les scripts sont injectés immédiatement après la finalisation du DOM, mais avant le chargement des sous-ressources telles que les images et les cadres.

Spécifier des cadres

Pour les scripts de contenu déclaratifs spécifiés dans le fichier manifeste, le champ "all_frames" permet à l'extension de spécifier si les fichiers JavaScript et CSS doivent être injectés dans tous les cadres correspondant aux exigences d'URL spécifiées ou uniquement dans le cadre le plus élevé d'un onglet:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Lorsque vous enregistrez des scripts de contenu de manière programmatique à l'aide de chrome.scripting.registerContentScripts(...), le paramètre allFrames peut être utilisé pour spécifier si le script de contenu doit être injecté dans tous les cadres correspondant aux exigences d'URL spécifiées ou uniquement dans le cadre le plus élevé d'un onglet. Cette option ne peut être utilisée qu'avec tabId et ne peut pas être utilisée si des frameIds ou des documentIds sont spécifiés:

service-worker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

Les extensions peuvent vouloir exécuter des scripts dans des cadres associés à un cadre correspondant, mais qui ne correspondent pas eux-mêmes. Dans ce cas, il s'agit généralement de cadres dont les URL ont été créées par un cadre correspondant, mais dont les URL ne correspondent pas elles-mêmes aux formats spécifiés dans le script.

C'est le cas lorsqu'une extension souhaite injecter des cadres avec des URL qui comportent des schémas about:, data:, blob: et filesystem:. Dans ce cas, l'URL ne correspond pas au format du script de contenu (et, dans le cas de about: et data:, n'inclut même pas l'URL ou l'origine parente dans l'URL, comme dans about:blank ou data:text/html,<html>Hello, World!</html>). Toutefois, ces cadres peuvent toujours être associés au cadre de création.

Pour injecter du code dans ces cadres, les extensions peuvent spécifier la propriété "match_origin_as_fallback" sur une spécification de script de contenu dans le fichier manifeste.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Lorsqu'il est spécifié et défini sur true, Chrome examine l'origine de l'initiateur du frame pour déterminer si le frame correspond, plutôt que l'URL du frame lui-même. Notez que cette valeur peut également être différente de l'origine du frame cible (par exemple, Les URL data: ont une origine nulle).

L'initiateur du frame est le frame qui a créé ou navigué dans le frame cible. Bien que ce soit généralement le parent direct ou l'ouvreur, il se peut que ce ne soit pas le cas (comme dans le cas d'un frame qui navigue dans un iFrame dans un iFrame).

Comme cette opération compare l'origine du frame initiateur, le frame initiateur peut se trouver sur n'importe quel chemin à partir de cette origine. Pour clarifier cette implication, Chrome exige que tous les scripts de contenu spécifiés avec "match_origin_as_fallback" défini sur true spécifient également un chemin d'accès *.

Lorsque les champs "match_origin_as_fallback" et "match_about_blank" sont spécifiés, "match_origin_as_fallback" est prioritaire.

Communication avec la page d'intégration

Bien que les environnements d'exécution des scripts de contenu et des pages qui les hébergent soient isolés les uns des autres, ils partagent l'accès au DOM de la page. Si la page souhaite communiquer avec le script de contenu ou avec l'extension via le script de contenu, elle doit le faire via le DOM partagé.

Vous pouvez par exemple utiliser window.postMessage():

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

example.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

La page sans extension, example.html, publie des messages sur elle-même. Ce message est intercepté et inspecté par le script de contenu, puis publié dans le processus d'extension. De cette manière, la page établit une ligne de communication avec le processus d'extension. L'inverse est possible par des moyens similaires.

Accéder aux fichiers d'extension

Pour accéder à un fichier d'extension à partir d'un script de contenu, vous pouvez appeler chrome.runtime.getURL() pour obtenir l'URL absolue de votre composant d'extension, comme indiqué dans l'exemple suivant (content.js):

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

Pour utiliser des polices ou des images dans un fichier CSS, vous pouvez utiliser @@extension_id pour créer une URL, comme illustré dans l'exemple suivant (content.css):

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

Tous les composants doivent être déclarés en tant que ressources Web accessibles dans le fichier manifest.json:

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

Bénéficiez d'une sécurité optimale

Bien que les mondes isolés fournissent une couche de protection, l'utilisation de scripts de contenu peut créer des failles dans une extension et une page Web. Si le script de contenu reçoit du contenu à partir d'un autre site Web, par exemple en appelant fetch(), veillez à filtrer le contenu contre les attaques de script intersites avant de l'injecter. Ne communiquez que via HTTPS pour éviter les attaques de l'"man-in-the-middle".

Veillez à filtrer les pages Web malveillantes. Par exemple, les modèles suivants sont dangereux et non autorisés dans le fichier manifeste V3:

À éviter

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
À éviter

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

Privilégiez plutôt des API plus sûres qui n'exécutent pas de scripts:

À faire

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
À faire

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);