Les scripts de contenu sont des fichiers exécutés dans le contexte de pages Web. En utilisant la classe Document le modèle d'objet (DOM), ils sont capables de lire les détails des pages Web qu'il consulte, les modifications et de transmettre les informations à l'extension parente.
Comprendre les fonctionnalités des scripts de contenu
Les scripts de contenu peuvent accéder directement aux API d'extension suivantes:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Les scripts de contenu ne peuvent pas accéder directement aux 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, en utilisant
telles que fetch()
. Pour ce faire, vous devez les déclarer en tant que
ressources accessibles sur le Web. Notez que les ressources sont également exposées
des scripts propriétaires ou tiers exécutés sur le même site.
Travailler dans des mondes isolés
Les scripts de contenu vivent dans un monde isolé, ce qui leur permet de modifier leur l'environnement JavaScript sans entrer en conflit avec la page ou d'autres extensions ; scripts de contenu.
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 en utilisant l'une des techniques décrites dans les 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 cette modification, les deux alertes s'affichent dans l'ordre lorsque l'utilisateur clique 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ées de manière automatisée.
Injecter des déclarations statiques
Utilisez des déclarations de script de contenu statique dans manifest.json pour les scripts qui doivent être 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"
.
Il peut s'agir de fichiers JavaScript, de fichiers CSS ou les deux. Tous les scripts de contenu exécutés automatiquement doivent spécifier
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. Spécifie les pages dans lesquelles ce script de contenu sera injecté. Consultez la section Formats de correspondance pour en savoir plus sur la syntaxe de ces chaînes. et Faire correspondre les schémas et les schémas glob pour savoir comment exclure URL. |
css |
tableau de chaînes | Facultatif. Liste des fichiers CSS à injecter dans les pages correspondantes. Il s'agit 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 |
|
Facultatif. Liste des fichiers JavaScript à injecter dans les pages correspondantes. Fichiers sont injectées dans l'ordre dans lequel elles apparaissent dans le tableau. Chaque chaîne de cette liste doit contenir un chemin d'accès relatif vers une ressource dans le répertoire racine de l'extension. Les barres obliques au début ("/") sont automatiquement coupées. |
run_at |
RunAt | Facultatif. Indique à quel moment 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. Si le script doit injecter dans les frames qui ont été
créées par une origine correspondante, mais dont l'URL ou l'origine ne peuvent pas être
correspondent au modèle. Ceux-ci incluent des cadres
avec des schémas différents, tels que
about: , data: , blob: et
filesystem: Voir aussi
Injecter dans des frames associés
|
world |
ExecutionWorld |
Facultatif. L'univers JavaScript dans lequel un script doit s'exécuter. La valeur par défaut est ISOLATED . Voir aussi
Travaillez dans des mondes isolés.
|
Injecter des déclarations dynamiques
Les scripts de contenu dynamique sont utiles lorsque les formats de correspondance des scripts de contenu sont pas bien connues ou lorsque les scripts de contenu ne doivent pas toujours être injectés sur des hôtes connus.
Introduit dans Chrome 96, les déclarations dynamiques sont semblables aux déclarations statiques
de contenu, mais l'objet de script de contenu est enregistré auprès de Chrome avec
dans l'espace de noms chrome.scripting
plutôt que dans
manifest.json. L'API Scripting permet aussi aux développeurs d'extensions
par:
- Enregistrez des scripts de contenu.
- Obtenez la liste des scripts de contenu enregistrés.
- Mettez à jour la liste des scripts de contenu enregistrés.
- Supprimez les scripts de contenu enregistrés.
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 par programmation
Utilisez l'injection programmatique pour les scripts de contenu qui doivent s'exécuter en réponse à des événements ou lors de occasions.
Pour injecter un script de contenu par programmation, votre extension a besoin d'autorisations d'hôte pour
la page dans laquelle il
essaie d'injecter des scripts. Les autorisations d’hôte peuvent
être accordées par
en les demandant dans le fichier manifeste de votre extension ou en utilisant temporairement "activeTab"
.
Voici une autre version 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"]
});
});
Un corps de fonction peut également être injecté et exécuté 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,
});
});
Sachez que la fonction injectée est une copie de la fonction référencée dans le
chrome.scripting.executeScript()
, et non la fonction d'origine elle-même. Par conséquent,
le corps doit être autonome ; des références à des variables en dehors de la fonction
pour générer une ReferenceError
.
Lors de l'injection en tant que 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 schémas glob
Pour personnaliser la mise en correspondance des pages spécifiées, incluez les champs suivants dans une l'enregistrement.
Nom | Type | Description |
---|---|---|
exclude_matches |
tableau de chaînes | Facultatif. Exclut les pages dans lesquelles ce script de contenu serait injecté . Consultez la section Formats de correspondance pour en savoir plus sur la syntaxe des ces chaînes. |
include_globs |
tableau de chaînes | Facultatif. Appliqué après le matches pour n'inclure que les URL qui ont également
correspondent à ce glob. Cela permet d'émuler la @include
Mot clé Greasemonkey. |
exclude_globs |
tableau de chaînes | Facultatif. Appliqué après le matches pour exclure les URL correspondant à cette requête
glob. Destiné à émuler @exclude
Mot clé Greasemonkey. |
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 n'importe quel formatinclude_globs
. - L'URL ne correspond pas non plus à un format
exclude_matches
ouexclude_globs
. Comme la propriétématches
est obligatoire,exclude_matches
,include_globs
etexclude_globs
ne peut être utilisée que pour limiter les pages affecté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 celle des motifs de correspondance. glob accepté
les chaînes sont des URL pouvant contenir le mot "caractère générique" des astérisques et
des points d'interrogation. 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 unique.
Par exemple, le fichier glob 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
.
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
.
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 une, toutes ou certaines d'entre elles pour atteindre le champ d'application approprié.
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 d'exécution
Le champ run_at
contrôle à quel moment des fichiers JavaScript sont injectés dans la page Web. Les préférences et
la valeur par défaut est "document_idle"
. Reportez-vous au type RunAt pour découvrir d'autres
valeurs.
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 | À privilégier : Utilisez "document_idle" autant que possible.Le navigateur choisit un horaire d'injection des scripts compris entre "document_end" et immédiatement après
le window.onload
se déclenche automatiquement. Le moment exact de l'injection dépend de la complexité du document et de la manière
et le temps de chargement est optimisé pour accélérer le chargement des pages.Scripts de contenu qui s'exécutent à "document_idle" n'ont pas besoin d'écouter
window.onload , leur exécution est garantie une fois l'exécution du DOM terminé. Si un
doit s'exécuter après window.onload , l'extension peut vérifier
onload a déjà été déclenché à l'aide de document.readyState .
. |
document_start |
chaîne | Les scripts sont injectés après les fichiers provenant de css , mais avant tout autre DOM
ou qu'un autre script soit exécuté. |
document_end |
chaîne | Les scripts sont injectés immédiatement après la fin du DOM, mais avant les sous-ressources telles que images et cadres ont été chargés. |
Spécifier des images
Le champ "all_frames"
permet à l'extension de spécifier si les fichiers JavaScript et CSS doivent être
est injecté dans tous les cadres correspondant aux exigences d'URL spécifiées ou uniquement dans le cadre supérieur d'une
.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Nom | Type | Description |
---|---|---|
all_frames |
booléen | Facultatif. La valeur par défaut est false , ce qui signifie que seul le cadre supérieur
avec correspondance.Si la valeur true est spécifiée, toutes les images sont injectées, même si les
n'est pas le cadre supérieur de l'onglet. Chaque image est vérifiée indépendamment pour l'URL
exigences. Il ne sera pas injecté dans les frames enfants si les exigences de l'URL ne sont pas remplies. |
Injecter dans les frames associés
Les extensions peuvent vouloir exécuter des scripts dans des frames liés à une correspondance mais qui ne correspondent pas. Dans ce cas, il est courant pour les cadres dont les URL ont été créées par un cadre correspondant, mais dont les URL correspondent aux formats spécifiés par le script.
C'est le cas lorsqu'une extension veut injecter dans des cadres avec des URL qui
avoir les schémas about:
, data:
, blob:
et filesystem:
. Dans ces cas,
L'URL ne correspond pas au format du script de contenu (et, dans le cas de about:
et
data:
, n'incluez même pas l'URL parente ni l'origine dans l'URL
(about:blank
ou data:text/html,<html>Hello, World!</html>
, par exemple).
Toutefois, ces cadres peuvent toujours être associés au cadre en cours de création.
Pour injecter dans ces frames, les extensions peuvent spécifier le
"match_origin_as_fallback"
sur une spécification de script de contenu dans la
fichier manifeste.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Si spécifié et défini sur true
, Chrome examine l'origine du
l'initiateur de la trame pour déterminer si la trame correspond, plutôt qu'au
l'URL de la trame elle-même. Notez qu'il peut aussi être différent de
l'origine du frame cible (par exemple, data:
URL ont une origine nulle.
L'initiateur du frame est le frame qui a créé ou parcouru la cible cadre. Bien qu'il s'agisse généralement du parent ou de l'utilisateur direct, il peut ne pas l'être (comme dans dans le cas d'un cadre qui navigue dans un iFrame).
Comme cela compare l'origine du frame de l'initiateur, le frame de l'initiateur
pourrait se trouver sur n'importe
quel chemin à partir de cette origine. Pour que cette implication soit claire,
nécessite tous les scripts de contenu spécifiés avec "match_origin_as_fallback"
Défini sur true
pour spécifier également le chemin d'accès *
.
Lorsque "match_origin_as_fallback"
et "match_about_blank"
sont tous les deux 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 les pages qui les hébergent soient isolés l'un de l'autre, ils partagent l'accès au DOM de la page. Si la page souhaite communiquer avec script de contenu, ou avec l'extension via le script de contenu, il doit le faire par le biais du DOM partagé.
Vous pouvez obtenir un exemple à l'aide de 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 vers elle-même. Ce message est intercepté et inspecté par le script de contenu, puis publié dans le processus de l'extension. De cette façon, la page établit une ligne de communication avec le processus de prolongation. L'inverse est possible via 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 Extension, comme illustré 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 construire 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 éléments doivent être déclarés en tant que ressources accessibles sur le Web 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
Alors que les mondes isolés fournissent une couche de protection, l'utilisation de scripts de contenu peut créer
les vulnérabilités dans une
extension et la page Web. Si le script de contenu reçoit du contenu d'un
site Web distinct, par exemple en appelant fetch()
, veillez à filtrer le contenu par
de type script intersites avant de l'injecter. Communiquez uniquement via HTTPS
pour éviter les attaques de type "man-in-the-middle".
Veillez à filtrer les pages Web malveillantes. Par exemple, les schémas suivants sont dangereux et non autorisé dans Manifest V3:
content-script.js
const data = document.getElementById("json-data"); // WARNING! Might be evaluating an evil script! const parsed = eval("(" + data + ")");
content-script.js
const elmt_id = ... // WARNING! elmt_id might be '); ... evil script ... //'! window.setTimeout("animate(" + elmt_id + ")", 200);
Optez plutôt pour des API plus sûres qui n'exécutent pas de scripts:
content-script.js
const data = document.getElementById("json-data") // JSON.parse does not evaluate the attacker's scripts. const parsed = JSON.parse(data);
content-script.js
const elmt_id = ... // The closure form of setTimeout does not evaluate scripts. window.setTimeout(() => animate(elmt_id), 200);