Skrypty treści to pliki, które działają w kontekście stron internetowych. Za pomocą standardowego modelu DOM mogą odczytywać szczegóły stron internetowych odwiedzanych przez przeglądarkę, wprowadzać w nich zmiany i przekazywać informacje do rozszerzenia nadrzędnego.
Możliwości skryptów treści
Skrypty treści mogą bezpośrednio korzystać z tych interfejsów API rozszerzeń:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Skrypty treści nie mogą bezpośrednio uzyskiwać dostępu do innych interfejsów API. Mogą jednak uzyskać do nich dostęp pośrednio, wymieniając wiadomości z innymi częściami rozszerzenia.
Możesz też uzyskać dostęp do innych plików w rozszerzeniu za pomocą skryptu treści i interfejsów API, takich jak fetch()
. Aby to zrobić, musisz je zadeklarować jako zasoby dostępne w internecie. Pamiętaj, że spowoduje to również udostępnienie zasobów wszystkim skryptom własnym i zewnętrznym działającym w tej samej witrynie.
Praca w odizolowanych światach
Skrypty dotyczące zawartości działają w odizolowanym środowisku, co pozwala im wprowadzać zmiany w swoim środowisku JavaScriptu bez konfliktu ze stroną lub skryptami dotyczącymi zawartości innych rozszerzeń.
Rozszerzenie może działać na stronie internetowej z kodem podobnym do tego:
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>
Rozszerzenie może wstrzyknąć ten skrypt treści, korzystając z jednej z technik opisanych w sekcji Wstrzykiwanie skryptów.
content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
Po wprowadzeniu tej zmiany po kliknięciu przycisku oba alerty będą się wyświetlać kolejno.
Wstrzykiwanie skryptów
Skrypty treści mogą być deklarowane statycznie, deklarowane dynamicznie lub wstrzykiwane automatycznie.
Wstrzyknięcie za pomocą statycznych deklaracji
W pliku manifest.json używaj deklaracji skryptów treści statycznej w przypadku skryptów, które powinny być automatycznie uruchamiane na znanym zbiorze stron.
Scenariusze zadeklarowane statycznie są rejestrowane w pliku manifestu pod kluczem "content_scripts"
.
Mogą to być pliki JavaScript lub CSS albo oba te typy. Wszystkie skrypty treści uruchamiane automatycznie muszą zawierać wzorce dopasowania.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
Nazwa | Typ | Opis |
---|---|---|
matches |
tablica tekstowa | Wymagany. Określa, na których stronach ma być wstrzykiwany skrypt treści. Szczegółowe informacje o składni tych ciągów znaków znajdziesz w artykule Wzorce dopasowania, a informacje o wykluczaniu adresów URL – w artykule Wzorce dopasowania i wzorce glob. |
css |
tablica tekstowa | Opcjonalnie. Lista plików CSS, które mają zostać wstrzyknięte na pasujące strony. Są one wstawiane w kolejności, w jakiej występują w tym tablicy, zanim zostanie utworzony lub wyświetlony DOM strony. |
js |
|
Opcjonalnie. Lista plików JavaScript, które mają zostać wstrzyknięte na pasujące strony. Pliki są wstrzykiwane w kolejności, w jakiej występują w tej tablicy. Każdy ciąg na tej liście musi zawierać ścieżkę względną do zasobu w katalogu głównym rozszerzenia. Początkowe ukośniki (/) są automatycznie obcinane. |
run_at |
RunAt | Opcjonalnie. Określa, kiedy skrypt powinien zostać wstrzyknięty na stronie. Domyślna wartość to document_idle . |
match_about_blank |
wartość logiczna | Opcjonalnie. Określa, czy skrypt ma zostać wstrzyknięty do elementu iframe about:blank , w którym element nadrzędny lub otwierający odpowiada jednemu z wzorów zadeklarowanych w elementzie matches . Wartość domyślna to fałsz. |
match_origin_as_fallback |
wartość logiczna |
Opcjonalnie. Określa, czy skrypt ma być wstrzykiwany do ramek utworzonych przez dopasowane źródło, ale których adres URL lub źródło może nie pasować bezpośrednio do wzorca. Obejmują one ramki z różnymi schematami, takimi jak about: , data: , blob: i filesystem: . Zobacz też: Wstawianie w powiązanych klatkach.
|
world |
ExecutionWorld |
Opcjonalnie. Środowisko JavaScriptu, w którym ma działać skrypt. Domyślna wartość to ISOLATED . Zobacz też:
Praca w odizolowanych światach.
|
Wstrzykiwanie za pomocą deklaracji dynamicznych
Dynamiczne skrypty treści są przydatne, gdy wzorce dopasowania skryptów treści są mało znane lub gdy skrypty treści nie powinny być zawsze wstrzykiwane na znanych hostach.
Deklaracje dynamiczne zostały wprowadzone w Chrome 96. Są podobne do statycznych deklaracji, ale obiekt skryptu treści jest rejestrowany w Chrome za pomocą metod w przestrzeni nazw chrome.scripting
, a nie w pliku manifest.json. Interfejs Scripting API umożliwia też deweloperom rozszerzeń:
- Zarejestruj skrypty treści.
- Pobierz listę zarejestrowanych skryptów treści.
- Zaktualizuj listę zarejestrowanych skryptów treści.
- Usuń zarejestrowane skrypty treści.
Podobnie jak deklaracje statyczne, deklaracje dynamiczne mogą zawierać pliki JavaScript lub CSS albo oba te pliki.
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"));
Wstawianie za pomocą kodu
Za pomocą programowego wstrzykiwania możesz uruchamiać skrypty treści w odpowiedzi na zdarzenia lub w określonych sytuacjach.
Aby wstrzyknąć skrypt treści programowo, rozszerzenie musi mieć uprawnienia hosta na stronie, na której ma zostać wstrzyknięty skrypt. Uprawnienia dotyczące hosta można przyznać, prosząc o nie w ramach pliku manifestu rozszerzenia lub tymczasowo używając "activeTab"
.
Poniżej znajdziesz różne wersje rozszerzenia opartego na aktywnym elemencie.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Skrypty treści można wstrzykiwać jako pliki.
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"]
});
});
Możesz też wstrzyknąć kod funkcji i wykonywać go jako skrypt treści.
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Pamiętaj, że wstrzyknięta funkcja jest kopią funkcji, do której odwołuje się wywołanie chrome.scripting.executeScript()
, a nie oryginalną funkcją. Dlatego ciało funkcji musi być samowystarczalne. Odwołania do zmiennych spoza funkcji spowodują, że skrypt treści wyrzuci błąd ReferenceError
.
Podczas wstrzykiwania jako funkcji możesz też przekazywać jej argumenty.
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" ],
});
});
Wykluczanie dopasowań i dopasowań nieprecyzyjnych
Aby dostosować dopasowywanie określonej strony, dodaj te pola w deklaratywnej rejestracji.
Nazwa | Typ | Opis |
---|---|---|
exclude_matches |
tablica tekstowa | Opcjonalnie. Wyklucza strony, do których skrypt treści miałby zostać wstrzyknięty. Szczegółowe informacje o składni tych ciągów znajdziesz w artykule Wzorce dopasowania. |
include_globs |
tablica tekstowa | Opcjonalnie. Stosuje się go po matches , aby uwzględnić tylko te adresy URL, które pasują do tego wyrażenia regularnego. Ma to na celu emulowanie słowa kluczowego @include w Greasemonkey. |
exclude_globs |
tablica tekstowa | Opcjonalnie. Stosuje się go po znaku matches , aby wykluczyć adresy URL pasujące do tego zbioru. Słowo kluczowe Greasemonkey służące do naśladowania @exclude . |
Skrypt treści zostanie wstrzyknięty na stronę, jeśli są spełnione oba te warunki:
- Jego adres URL pasuje do dowolnego wzorca
matches
i dowolnego wzorcainclude_globs
. - Adres URL nie pasuje też do wzorca
exclude_matches
aniexclude_globs
. Właściwośćmatches
jest wymagana, więc atrybutyexclude_matches
,include_globs
iexclude_globs
można używać tylko do ograniczania zakresu stron, na których mają one obowiązywać.
Podane niżej rozszerzenie wstrzykuje skrypt treści do pliku https://www.nytimes.com/health
, ale nie do pliku 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" ],
}]);
Właściwości globa mają inną, bardziej elastyczną składnię niż wzorce dopasowania. Dopuszczalne ciągi tekstowe to adresy URL, które mogą zawierać „symbole wieloznaczne” w postaci gwiazdek i znaków zapytania. Gwiazdka (*
) odpowiada dowolnemu ciągu znaków o dowolnej długości, w tym pustemu ciągu, a znak zapytania (?
) odpowiada dowolnemu pojedynczemu znakowi.
Na przykład wyrażenie https://???.example.com/foo/\*
pasuje do dowolnego z tych elementów:
https://www.example.com/foo/bar
https://the.example.com/foo/
Nie odpowiada jednak:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
To rozszerzenie wstrzykuje skrypt treści do https://www.nytimes.com/arts/index.html
i https://www.nytimes.com/jobs/index.htm*
, ale nie do 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"]
}
],
...
}
To rozszerzenie wstrzykuje skrypt treści do https://history.nytimes.com
i https://.nytimes.com/history
, ale nie do https://science.nytimes.com
ani https://www.nytimes.com/science
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Aby uzyskać odpowiedni zakres, możesz uwzględnić jeden, wszystkie lub niektóre z tych elementów.
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"]
}
],
...
}
Czas trwania
Pole run_at
określa, kiedy pliki JavaScript są wstrzykiwane na stronę internetową. Preferowaną i domyślną wartością jest "document_idle"
. Inne możliwe wartości znajdziesz w typie 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" ],
}]);
Nazwa | Typ | Opis |
---|---|---|
document_idle |
ciąg znaków | Preferowana. W miarę możliwości używaj tagu "document_idle" .Przeglądarka wybiera czas wstrzyknięcia skryptów między "document_end" a chwilą, gdy nastąpiło zdarzenie window.onload . Dokładny moment wstrzyknięcia zależy od złożoności dokumentu i czasu jego wczytywania. Jest on optymalizowany pod kątem szybkości wczytywania strony.Skrypty treści działające w trybie "document_idle" nie muszą nasłuchiwać zdarzenia window.onload , ponieważ zawsze są wykonywane po zakończeniu wczytywania DOM. Jeśli skrypt musi działać dopiero po window.onload , rozszerzenie może sprawdzić, czy onload zostało już wywołane, korzystając z właściwości document.readyState . |
document_start |
ciąg znaków | Skrypty są wstrzykiwane po wszystkich plikach z folderu css , ale przed utworzeniem dowolnego innego DOM lub uruchomieniem dowolnego innego skryptu. |
document_end |
ciąg znaków | Skrypty są wstrzykiwane natychmiast po utworzeniu modelu DOM, ale przed załadowaniem zasobów podrzędnych, takich jak obrazy i ramki. |
Określanie ramek
W przypadku skryptów deklaratywnych treści określonych w pliku manifestu pole "all_frames"
umożliwia rozszerzeniu określenie, czy pliki JavaScript i CSS mają być wstrzykiwane do wszystkich ramek pasujących do określonych wymagań adresu URL, czy tylko do najwyższej ramki w karcie:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Podczas rejestrowania skryptów treści za pomocą kodu za pomocą chrome.scripting.registerContentScripts(...)
możesz użyć parametru allFrames
, aby określić, czy skrypt treści ma być wstrzykiwany do wszystkich ramek pasujących do określonych wymagań adresu URL, czy tylko do najwyższej ramki w karcie. Można go używać tylko z identyfikatorem tabId. Nie można go używać, jeśli określone są identyfikatory frameId lub documentId:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
wstawiać je w powiązanych klatkach.
Rozszerzenia mogą uruchamiać skrypty w ramkach, które są powiązane z ramką pasującą do nich, ale same do niej nie pasują. Typowym scenariuszem jest sytuacja, gdy ramki z adresami URL utworzonymi przez pasującą ramkę nie pasują do wzorców określonych w skrypcie.
Dzieje się tak, gdy rozszerzenie chce wstrzyknąć ramki z adresami URL o schematach about:
, data:
, blob:
i filesystem:
. W takich przypadkach adres URL nie będzie pasować do wzorca skryptu treści (a w przypadku about:
i data:
nie będzie nawet zawierać adresu URL nadrzędnego ani źródła, jak w przypadku about:blank
lub data:text/html,<html>Hello, World!</html>
). Te ramki można jednak nadal powiązać z ramką tworzącą.
Aby wstrzyknąć treści do tych ramek, rozszerzenia mogą określić właściwość "match_origin_as_fallback"
w specyfikacji skryptu treści w pliku manifestu.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Jeśli to ustawienie jest określone i wyrażone jako true
, Chrome sprawdza pochodzenie inicjatora ramki, aby określić, czy dana ramka jest zgodna, a nie adres URL samej ramki. Pamiętaj, że może być ona inna niż pochodzenie ramki docelowej (np. Adresy URL data:
mają null jako źródło.
Inicjator ramki to rama, która utworzyła lub nawigowała do ramki docelowej. Zwykle jest to element nadrzędny lub element otwierający, ale nie zawsze (np. w przypadku ramki przechodzącej do elementu iframe w ramce iframe).
Ponieważ porównuje to pochodzenie ramki inicjującej, może ona znajdować się na dowolnej ścieżce od tego punktu. Aby to wyjaśnić, Chrome wymaga, aby skrypty treści określone za pomocą "match_origin_as_fallback"
ustawione na true
, również określały ścieżkę *
.
Jeśli podane są zarówno parametry "match_origin_as_fallback"
, jak i "match_about_blank"
, parametr "match_origin_as_fallback"
ma pierwszeństwo.
Komunikacja ze stroną, na której znajduje się element docelowy
Chociaż środowiska wykonywania skryptów treści i stron, na których są one hostowane, są od siebie odizolowane, mają wspólny dostęp do modelu DOM strony. Jeśli strona chce komunikować się ze skryptem treści lub z rozszerzeniem za pomocą skryptu treści, musi to robić za pomocą udostępnionego DOM.
Przykładowe użycie funkcji 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);
Strona bez rozszerzenia (example.html) wysyła wiadomości do siebie. Skrypt treści przechwytuje i sprawdza tę wiadomość, a następnie przekazuje ją do procesu rozszerzenia. W ten sposób strona umożliwia komunikację w ramach procesu rozszerzania. Odwrotne przekształcenie jest możliwe za pomocą podobnych metod.
Dostęp do plików rozszerzeń
Aby uzyskać dostęp do pliku rozszerzenia z poziomu skryptu treści, możesz wywołać funkcję chrome.runtime.getURL()
, aby uzyskać bezwzględny adres URL zasobu rozszerzenia, jak pokazano w tym przykładzie (content.js
):
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
Aby używać czcionek lub obrazów w pliku CSS, możesz użyć funkcji @@extension_id
, aby utworzyć adres URL w sposób pokazany w tym przykładzie (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');
}
Wszystkie zasoby muszą być zadeklarowane jako zasoby dostępne w sieci w pliku manifest.json
:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Zadbaj o bezpieczeństwo
Chociaż izolowane światy zapewniają pewien poziom ochrony, skrypty dotyczące zawartości mogą tworzyć luki w zabezpieczeniach rozszerzenia i strony internetowej. Jeśli skrypt treści otrzymuje treści z innej witryny, np. przez wywołanie funkcji fetch()
, przed wstrzyknięciem treści należy je przefiltrować, aby zapobiec atakom XSS. Komunikacja powinna odbywać się tylko przez HTTPS, aby uniknąć ataków typu "man-in-the-middle".
Pamiętaj, aby filtrować złośliwe strony internetowe. Na przykład te wzorce są niebezpieczne i niedozwolone w pliku manifestu w wersji 3:
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);
Zamiast tego wybieraj bezpieczniejsze interfejsy API, które nie uruchamiają skryptów:
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);