Skrypty treści

Skrypty treści to pliki, które działają w kontekście stron internetowych. Za pomocą standardowego modelu obiektu dokumentu (DOM) mogą odczytywać szczegóły stron internetowych odwiedzanych przez przeglądarkę, wprowadzać na nich zmiany i przekazywać informacje do rozszerzenia nadrzędnego.

Informacje o możliwości skryptu treści

Skrypty treści mogą uzyskiwać dostęp do plików rozszerzeń po zadeklarowaniu ich jako zasobów dostępnych w internecie. Mają bezpośredni dostęp do tych interfejsów API rozszerzeń:

Skrypty treści nie mają bezpośredniego dostępu do innych interfejsów API. ale mogą uzyskiwać do nich pośredni dostęp, wymieniając wiadomości z innymi częściami rozszerzenia.

Praca w izolowanym świecie

Skrypty treści funkcjonują w izolowanym świecie, co umożliwia skryptowi treści wprowadzanie zmian w środowisku JavaScriptu bez konfliktów ze skryptami treści strony lub innych rozszerzeń.

Rozszerzenie może działać na stronie internetowej z kodem podobnym do tego przykładu.

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>

To rozszerzenie może wstrzyknąć poniższy skrypt treści przy użyciu jednej z technik opisanych w sekcji Wstawianie 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);

Dzięki tej zmianie po kliknięciu przycisku oba alerty będą się pojawiać kolejno.

Wstrzyknij skrypty

Skrypty treści mogą być zadeklarowane statycznie, zadeklarowane dynamicznie lub wstrzykiwane automatycznie.

Wstaw za pomocą statycznych deklaracji

W pliku manifest.json użyj deklaracji skryptu treści statycznych na potrzeby skryptów, które powinny być uruchamiane automatycznie na dobrze znanym zestawie stron.

Statycznie zadeklarowane skrypty są rejestrowane w pliku manifestu pod kluczem "content_scripts". Mogą one obejmować pliki JavaScript, pliki CSS lub oba te elementy. Wszystkie automatycznie uruchamiane skrypty treści muszą mieć określone 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 ciągów Wymagane. Określa strony, na których będzie wstrzykiwany ten skrypt treści. Szczegółowe informacje o składni tych ciągów znaków znajdziesz w sekcji Wzorce dopasowania, a informacje o wykluczaniu adresów URL znajdziesz w sekcji na temat wzorców dopasowania i obiektów globs.
css tablica ciągów Opcjonalne. Lista plików CSS, które mają zostać wstawione na pasujących stronach. Są one wstrzykiwane w kolejności, w jakiej występują w tej tablicy, przed utworzeniem lub wyświetleniem elementu DOM na stronie.
js tablica ciągów znaków Opcjonalne. Lista plików JavaScript, które mają być wstawiane na pasujących stronach. Pliki są wstrzykiwane w kolejności, w jakiej pojawiają się w tej tablicy. Każdy ciąg znaków na tej liście musi zawierać ścieżkę względną do zasobu w katalogu głównym rozszerzenia. Początkowe ukośniki („/”) są automatycznie usuwane.
run_at RunAt Opcjonalne. Określa, kiedy skrypt powinien zostać wstawiony na stronie. Wartość domyślna to document_idle.
match_about_blank boolean Opcjonalne. Określa, czy skrypt powinien wstrzykiwać się do ramki about:blank, gdzie ramka nadrzędna lub otwierająca odpowiada jednemu z wzorców zadeklarowanych w matches. Wartość domyślna to fałsz.
match_origin_as_fallback boolean Opcjonalne. Określa, czy skrypt ma wstrzyknąć w ramkach, które zostały utworzone przez pasujące źródło, ale których adres URL lub źródło może nie pasować bezpośrednio do wzorca. Należą do nich klatki o różnych schematach, np. about:, data:, blob: i filesystem:. Przeczytaj artykuł o wstrzymywaniu w powiązanych klatkach.
world ExecutionWorld Opcjonalne. Świat JavaScriptu, w którym ma być wykonywany skrypt. Domyślna wartość to ISOLATED. Zobacz też Praca w odizolowanym świecie.

Wstaw za pomocą deklaracji dynamicznych

Skrypty zawartości dynamicznej są przydatne, gdy wzorce dopasowania skryptów treści nie są dobrze znane lub gdy skrypty treści nie powinny być zawsze wstrzykiwane na znanych hostach.

Wprowadzone w Chrome 96 deklaracje dynamiczne są podobne do deklaracji statycznych, 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ż programistom rozszerzeń:

Podobnie jak deklaracje statyczne, deklaracje dynamiczne mogą zawierać pliki JavaScript lub CSS albo oba te elementy.

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

Wstawiaj automatycznie

Używaj wstrzykiwania automatycznego w przypadku skryptów treści, które muszą być uruchamiane w odpowiedzi na zdarzenia lub w określonych sytuacjach.

Aby automatycznie wstawić skrypt treści, rozszerzenie musi mieć uprawnienia hosta dla strony, na której próbuje wstawić skrypty. Uprawnienia hosta można przyznać, prosząc o nie w pliku manifestu rozszerzenia lub tymczasowo za pomocą narzędzia "activeTab".

Poniżej znajdziesz różne wersje aktywnego rozszerzenia opartego na karcie.

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

Treść funkcji może być też wstrzykiwana i wykonywana 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ę funkcja chrome.scripting.executeScript(), a nie samej funkcji. W efekcie treść funkcji musi być samodzielna – odwołania do zmiennych spoza tej funkcji spowodują zgłoszenie przez skrypt treści funkcji ReferenceError.

Podczas wstrzykiwania jako funkcji możesz też przekazywać do niej 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" ],
  });
});

Wyklucz dopasowania i globs

Aby dostosować określone dopasowanie stron, uwzględnij te pola w rejestracji deklaratywnej.

Nazwa Typ Opis
exclude_matches tablica ciągów Opcjonalne. Wyklucza strony, do których w innym przypadku zostałby wstrzyknięty ten skrypt treści. Szczegółowe informacje o składni tych ciągów tekstowych znajdziesz w sekcji Wzorce dopasowania.
include_globs tablica ciągów Opcjonalne. Stosowane po matches, aby uwzględnić tylko te adresy URL, które również pasują do tego wzorca glob. Ma to na celu emulację słowa kluczowego @include Greasemonkey.
exclude_globs tablica ciągu znaków Opcjonalne. Stosowane po matches, aby wykluczać adresy URL pasujące do tego wzorca glob. Ma na celu emulowanie słowa kluczowego @exclude Greasemonkey.

Skrypt treści zostanie wstawiony na stronie, jeśli spełnione są oba te warunki:

  • Jej adres URL pasuje do dowolnego wzorca matches i dowolnego wzorca include_globs.
  • Adres URL nie pasuje też do wzorca exclude_matches lub exclude_globs. Wymagana jest właściwość matches, więc właściwości exclude_matches, include_globs i exclude_globs można używać tylko do ograniczenia zakresu stron, których to dotyczy.

To rozszerzenie wstawia skrypt treści do interfejsu https://www.nytimes.com/health, ale nie do interfejsu 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 Glob mają inną, bardziej elastyczną składnię niż wzorce dopasowania. Dopuszczalne ciągi znaków glob to adresy URL, które mogą zawierać gwiazdki z symbolami wieloznacznymi i znaki zapytania. Gwiazdka (*) odpowiada dowolnemu ciągowi o dowolnej długości, w tym pustemu ciągowi, a znak zapytania (?) odpowiada dowolnemu pojedynczemu znakowi.

Na przykład wzorzec glob https://???.example.com/foo/\* odpowiada dowolnym z tych elementów:

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

Nie jest jednak zgodne z tymi:

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

To rozszerzenie wstawia skrypt treści do interfejsów 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 wstawia skrypt treści do interfejsów 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"]
    }
  ],
  ...
}

Można uwzględnić jeden, wszystkie lub niektóre z nich, aby uzyskać prawidłowy zakres.

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 na stronie internetowej są wstawiane pliki JavaScript. Wartość preferowana i domyślna to "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 string, Preferowana. Gdy tylko jest to możliwe, używaj "document_idle".

Przeglądarka wybiera czas wstrzykiwania skryptów między "document_end" a bezpośrednio po uruchomieniu zdarzenia window.onload. Dokładny moment wstrzyknięcia zależy od złożoności dokumentu i czasu jego wczytywania oraz jest zoptymalizowany pod kątem szybkości wczytywania strony.

Skrypty treści działające w "document_idle" nie muszą nasłuchiwać zdarzenia window.onload, a ich uruchamianie po zakończeniu DOM jest gwarantowane. Jeśli skrypt musi zostać uruchomiony po window.onload, rozszerzenie może za pomocą właściwości document.readyState sprawdzić, czy został już uruchomiony skrypt onload.
document_start string, Skrypty są wstrzykiwane po plikach z css, ale przed utworzeniem innego modelu DOM lub uruchomieniem jakiegokolwiek innego skryptu.
document_end string, Skrypty są wstrzykiwane bezpośrednio po zakończeniu DOM, ale przed wczytaniem zasobów podrzędnych, takich jak obrazy i ramki.

Określ ramki

Pole "all_frames" pozwala rozszerzeniu określić, czy pliki JavaScript i CSS mają być wstrzykiwane we wszystkich ramkach spełniających wymagania dotyczące adresu URL, czy tylko do najwyższej ramki na karcie.

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" ],
}]);
Nazwa Typ Opis
all_frames boolean Opcjonalne. Wartość domyślna to false, co oznacza, że dopasowywana jest tylko górna klatka.

Jeśli określisz właściwość true, do danych będą wstrzykiwane wszystkie klatki, nawet jeśli klatka nie jest najwyższa na karcie. Każda ramka jest sprawdzana niezależnie pod kątem wymagań dotyczących adresów URL. Jeśli wymagania dotyczące adresu URL nie zostaną spełnione, nie będzie on wstrzykiwany w ramkach podrzędnych.

Rozszerzenia mogą chcieć uruchamiać skrypty w ramkach, które są powiązane z pasującą ramką, ale same w sobie nie pasują. Częstym przypadkiem są tu ramki z adresami URL, które zostały utworzone przez pasującą ramkę, ale nie pasują do wzorców skryptu.

Dzieje się tak, gdy rozszerzenie chce wstrzykiwać treści w ramkach z adresami URL, które mają schematy about:, data:, blob: i filesystem:. W takich przypadkach adres URL nie będzie pasować do wzorca skryptu treści (a w przypadku obiektów about: i data: w ogóle nie umieszczaj nadrzędnego adresu URL ani źródła, jak w przypadku about:blank czy data:text/html,<html>Hello, World!</html>). Ramki nadal można jednak powiązać z ramką tworzenia.

Aby umieszczać je w tych ramkach, 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"]
    }
  ],
  ...
}

Gdy określisz wartość true i ustawisz w nim wartość true, Chrome sprawdzi, czy ramka pasuje do jej źródła, a nie jej adres URL. Pamiętaj, że ta wartość może się też różnić od początku ramki docelowej (np. data: adresy URL mają źródło o wartości null).

Inicjator ramki to ramka, która utworzyła ramkę docelową lub do niej poprowadziła. Zwykle jest to bezpośredni element nadrzędny lub otwierający, ale nie musi nim być (jak w przypadku ramki poruszającej się po elemencie iframe w elemencie iframe).

Ponieważ porównuje ona źródło ramki inicjującej, ta ramka może znajdować się na dowolnej ścieżce z tego źródła. Aby było to jasne, Chrome wymaga, aby w przypadku skryptów treści podanych w parametrze "match_origin_as_fallback" ustawiono też true, a także określić ścieżkę *.

Jeśli określone są zarówno "match_origin_as_fallback", jak i "match_about_blank", priorytet ma "match_origin_as_fallback".

Komunikacja ze stroną umieszczania

Mimo że środowiska wykonawcze skryptów treści i strony, które je hostują, są od siebie odizolowane, mają jednakowy dostęp do DOM strony. Jeśli strona chce komunikować się ze skryptem treści lub rozszerzeniem przez skrypt treści, musi to robić za pomocą współdzielonego modelu DOM.

Przykład można użyć za pomocą 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 example.html bez rozszerzeń wysyła wiadomości do siebie. Wiadomość jest przechwycona i sprawdzana przez skrypt treści, a następnie publikowana w procesie rozszerzenia. Dzięki temu strona komunikuje się z prośbą o wydłużenie terminu. W podobny sposób można zastosować odwrotność.

Dostęp do plików rozszerzeń

Aby uzyskać dostęp do pliku rozszerzenia ze skryptu treści, wywołaj chrome.runtime.getURL(), aby uzyskać bezwzględny URL zasobu rozszerzenia, jak w tym przykładzie (content.js):

content-script.js

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

Aby użyć czcionek lub obrazów w pliku CSS, możesz użyć @@extension_id do utworzenia adresu URL, jak 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 w pliku manifest.json jako zasoby dostępne w internecie:

manifest.json

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

Dbaj o bezpieczeństwo

Odizolowane światy zapewniają warstwę zabezpieczeń, ale używanie skryptów treści może powodować luki w zabezpieczeniach rozszerzenia i strony internetowej. Jeśli skrypt treści otrzymuje treści z innej witryny, np. wywołując fetch(), przed wstrzyknięciem treści przefiltruj ją pod kątem ataków typu cross-site scripting. Aby uniknąć ataków typu "man-in-the-middle", komunikuj się tylko przez HTTPS.

Pamiętaj, by odfiltrować złośliwe strony internetowe. Na przykład w platformie Manifest V3 niedozwolone są te wzorce:

Nie

content-script.js

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

content-script.js

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

Lepiej stosuj bezpieczniejsze interfejsy API, które nie uruchamiają skryptów:

Tak

content-script.js

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

content-script.js

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