Skrip konten adalah file yang berjalan dalam konteks halaman web. Dengan menggunakan Document Object Model (DOM) standar, mereka dapat membaca detail halaman web yang dikunjungi browser, membuat perubahan padanya, dan meneruskan informasi ke ekstensi induknya.
Memahami kemampuan skrip konten
Skrip konten dapat mengakses API ekstensi berikut secara langsung:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Skrip konten tidak dapat mengakses API lain secara langsung. Namun, mereka dapat mengaksesnya secara tidak langsung dengan bertukar pesan dengan bagian lain dari ekstensi Anda.
Anda juga dapat mengakses file lain di ekstensi dari skrip konten, menggunakan
API seperti fetch()
. Untuk melakukannya, Anda harus mendeklarasikannya sebagai
resource yang dapat diakses web. Perhatikan bahwa hal ini juga mengekspos resource ke skrip pihak pertama atau pihak ketiga yang berjalan di situs yang sama.
Bekerja di dunia yang terisolasi
Skrip konten berada di dunia yang terisolasi, sehingga skrip konten dapat membuat perubahan pada lingkungan JavaScript-nya tanpa berkonflik dengan halaman atau skrip konten ekstensi lainnya.
Ekstensi dapat berjalan di halaman web dengan kode yang mirip dengan contoh berikut.
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>
Ekstensi tersebut dapat menyuntikkan skrip konten berikut menggunakan salah satu teknik yang diuraikan di bagian Menyuntikkan skrip.
content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
Dengan perubahan ini, kedua pemberitahuan akan muncul secara berurutan saat tombol diklik.
Menyisipkan skrip
Skrip konten dapat dideklarasikan secara statis, dideklarasikan secara dinamis, atau disisipkan secara terprogram.
Menyuntikkan dengan deklarasi statis
Gunakan deklarasi skrip konten statis di manifest.json untuk skrip yang harus dijalankan secara otomatis di serangkaian halaman yang sudah dikenal.
Skrip yang dideklarasikan secara statis didaftarkan dalam manifes dengan kunci "content_scripts"
.
File ini dapat mencakup file JavaScript, file CSS, atau keduanya. Semua skrip konten yang berjalan otomatis harus menentukan
pola kecocokan.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
Nama | Jenis | Deskripsi |
---|---|---|
matches |
array string | Wajib. Menentukan halaman tempat skrip konten ini akan disisipkan. Lihat Pola Pencocokan untuk mengetahui detail sintaksis string ini dan Pola pencocokan dan glob untuk mengetahui informasi tentang cara mengecualikan URL. |
css |
array string | Opsional. Daftar file CSS yang akan disisipkan ke halaman yang cocok. Ini disuntikkan dalam urutan kemunculannya di array ini, sebelum DOM dibuat atau ditampilkan untuk halaman. |
js |
|
Opsional. Daftar file JavaScript yang akan disisipkan ke halaman yang cocok. File disisipkan sesuai urutannya dalam array ini. Setiap string dalam daftar ini harus berisi jalur relatif ke resource di direktori root ekstensi. Garis miring di awal (`/`) akan dipangkas secara otomatis. |
run_at |
RunAt | Opsional. Menentukan kapan skrip harus dimasukkan ke halaman. Nilai defaultnya adalah document_idle . |
match_about_blank |
boolean | Opsional. Apakah skrip harus disisipkan ke dalam frame about:blank
tempat frame induk atau pembuka cocok dengan salah satu pola yang dideklarasikan di
matches . Nilai defaultnya adalah false (salah). |
match_origin_as_fallback |
boolean |
Opsional. Apakah skrip harus disisipkan dalam frame yang
dibuat oleh asal yang cocok, tetapi URL atau asalnya mungkin tidak langsung
cocok dengan pola. Ini mencakup frame dengan skema yang berbeda, seperti
about: , data: , blob: , dan
filesystem: . Lihat juga
Menyuntikkan dalam frame terkait.
|
world |
ExecutionWorld |
Opsional. Dunia JavaScript untuk skrip yang akan dieksekusi di dalamnya. Nilai defaultnya adalah ISOLATED . Lihat juga
Bekerja di dunia yang terisolasi.
|
Menyuntikkan dengan deklarasi dinamis
Skrip konten dinamis berguna jika pola kecocokan untuk skrip konten tidak diketahui dengan baik atau jika skrip konten tidak boleh selalu disisipkan di host yang diketahui.
Diperkenalkan di Chrome 96, deklarasi dinamis mirip dengan deklarasi statis, tetapi objek skrip konten didaftarkan ke Chrome menggunakan metode di namespace chrome.scripting
, bukan di manifest.json. Scripting API juga memungkinkan developer ekstensi untuk:
- Daftarkan skrip konten.
- Dapatkan daftar skrip konten terdaftar.
- Perbarui daftar skrip konten terdaftar.
- Menghapus skrip konten terdaftar.
Seperti deklarasi statis, deklarasi dinamis dapat mencakup file JavaScript, file CSS, atau keduanya.
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"));
Menyuntikkan secara terprogram
Gunakan injeksi terprogram untuk skrip konten yang perlu dijalankan sebagai respons terhadap peristiwa atau pada kesempatan tertentu.
Untuk menyuntikkan skrip konten secara terprogram, ekstensi Anda memerlukan izin host untuk
halaman yang akan disuntikkan skripnya. Izin host dapat diberikan dengan
memintanya sebagai bagian dari manifes ekstensi atau menggunakan "activeTab"
untuk sementara.
Berikut adalah versi lain dari ekstensi berbasis activeTab.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Skrip konten dapat dimasukkan sebagai file.
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"]
});
});
Atau, isi fungsi dapat disisipkan dan dieksekusi sebagai skrip konten.
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Perlu diketahui bahwa fungsi yang disuntikkan adalah salinan fungsi yang dirujuk dalam panggilan
chrome.scripting.executeScript()
, bukan fungsi aslinya. Akibatnya, isi
fungsi harus mandiri; referensi ke variabel di luar fungsi akan menyebabkan skrip
konten memunculkan ReferenceError
.
Saat menyuntikkan sebagai fungsi, Anda juga dapat meneruskan argumen ke fungsi.
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" ],
});
});
Mengecualikan kecocokan dan glob
Untuk menyesuaikan pencocokan halaman yang ditentukan, sertakan kolom berikut dalam pendaftaran deklaratif.
Nama | Jenis | Deskripsi |
---|---|---|
exclude_matches |
array string | Opsional. Mengecualikan halaman yang seharusnya disisipkan skrip konten ini. Lihat Pola Pencocokan untuk mengetahui detail sintaksis string ini. |
include_globs |
array string | Opsional. Diterapkan setelah matches untuk menyertakan hanya URL yang juga cocok dengan glob ini. Tujuannya adalah untuk meniru kata kunci Greasemonkey @include . |
exclude_globs |
array string | Opsional. Diterapkan setelah matches untuk mengecualikan URL yang cocok dengan
glob ini. Dimaksudkan untuk meniru kata kunci @exclude
Greasemonkey. |
Skrip konten akan disisipkan ke dalam halaman jika kedua hal berikut terpenuhi:
- URL-nya cocok dengan pola
matches
dan polainclude_globs
. - URL juga tidak cocok dengan pola
exclude_matches
atauexclude_globs
. Karena propertimatches
wajib diisi,exclude_matches
,include_globs
, danexclude_globs
hanya dapat digunakan untuk membatasi halaman mana yang akan terpengaruh.
Ekstensi berikut menyuntikkan skrip konten ke https://www.nytimes.com/health
, tetapi tidak ke 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" ],
}]);
Properti glob mengikuti sintaksis yang berbeda dan lebih fleksibel daripada pola kecocokan. String glob yang dapat diterima adalah URL yang dapat berisi tanda bintang "karakter pengganti" dan tanda tanya. Tanda bintang (*
)
cocok dengan string apa pun dengan panjang apa pun, termasuk string kosong, sedangkan tanda tanya (?
) cocok dengan
satu karakter apa pun.
Misalnya, glob https://???.example.com/foo/\*
cocok dengan salah satu dari berikut ini:
https://www.example.com/foo/bar
https://the.example.com/foo/
Namun, nilai ini tidak cocok dengan nilai berikut:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
Ekstensi ini menyuntikkan skrip konten ke https://www.nytimes.com/arts/index.html
dan
https://www.nytimes.com/jobs/index.htm*
, tetapi tidak ke
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"]
}
],
...
}
Ekstensi ini menyuntikkan skrip konten ke https://history.nytimes.com
dan
https://.nytimes.com/history
, tetapi tidak ke https://science.nytimes.com
atau
https://www.nytimes.com/science
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Satu, semua, atau sebagian di antaranya dapat disertakan untuk mencapai cakupan yang benar.
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"]
}
],
...
}
Waktu proses
Kolom run_at
mengontrol kapan file JavaScript disisipkan ke halaman web. Nilai pilihan dan
defaultnya adalah "document_idle"
. Lihat jenis RunAt untuk kemungkinan
nilai lainnya.
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" ],
}]);
Nama | Jenis | Deskripsi |
---|---|---|
document_idle |
string | Lebih disukai. Gunakan "document_idle" jika memungkinkan.Browser memilih waktu untuk menyuntikkan skrip antara "document_end" dan segera setelah
peristiwa window.onload
diaktifkan. Momen injeksi yang tepat bergantung pada seberapa kompleks dokumen dan berapa lama waktu yang dibutuhkan untuk memuatnya, serta dioptimalkan untuk kecepatan pemuatan halaman.Skrip konten yang berjalan di "document_idle" tidak perlu memproses peristiwa window.onload , karena dijamin akan berjalan setelah DOM selesai. Jika
skrip pasti perlu dijalankan setelah window.onload , ekstensi dapat memeriksa apakah
onload telah diaktifkan menggunakan properti document.readyState . |
document_start |
string | Skrip disisipkan setelah file apa pun dari css , tetapi sebelum DOM lain dibangun atau skrip lain dijalankan. |
document_end |
string | Skrip disisipkan segera setelah DOM selesai, tetapi sebelum subresource seperti gambar dan frame dimuat. |
Menentukan frame
Untuk skrip konten deklaratif yang ditentukan dalam manifes, kolom "all_frames"
memungkinkan ekstensi menentukan apakah file JavaScript dan CSS harus disisipkan ke dalam semua frame yang cocok dengan persyaratan URL yang ditentukan atau hanya ke dalam frame teratas di tab:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Saat mendaftarkan skrip konten secara terprogram menggunakan chrome.scripting.registerContentScripts(...)
, parameter allFrames
dapat digunakan untuk
menentukan apakah skrip konten harus disisipkan ke semua frame yang cocok dengan
persyaratan URL yang ditentukan atau hanya ke frame paling atas di tab. Ini hanya dapat digunakan dengan tabId, dan tidak dapat digunakan jika frameIds atau documentIds ditentukan:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Menyisipkan ke dalam frame terkait
Ekstensi mungkin ingin menjalankan skrip dalam frame yang terkait dengan frame yang cocok, tetapi tidak cocok dengan sendirinya. Skenario umum saat hal ini terjadi adalah untuk frame dengan URL yang dibuat oleh frame yang cocok, tetapi URL-nya sendiri tidak cocok dengan pola yang ditentukan skrip.
Hal ini terjadi saat ekstensi ingin menyuntikkan frame dengan URL yang memiliki skema about:
, data:
, blob:
, dan filesystem:
. Dalam kasus ini, URL tidak akan cocok dengan pola skrip konten (dan, dalam kasus about:
dan data:
, bahkan tidak menyertakan URL atau asal induk dalam URL sama sekali, seperti dalam about:blank
atau data:text/html,<html>Hello, World!</html>
).
Namun, frame ini masih dapat dikaitkan dengan frame yang membuatnya.
Untuk menyuntikkan ke dalam frame ini, ekstensi dapat menentukan properti
"match_origin_as_fallback"
pada spesifikasi skrip konten dalam
manifes.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Jika ditentukan dan disetel ke true
, Chrome akan melihat asal
pemrakarsa frame untuk menentukan apakah frame cocok, bukan pada
URL frame itu sendiri. Perhatikan bahwa ini mungkin juga berbeda dengan
origin frame target (misalnya, URL data:
memiliki asal null).
Inisiator frame adalah frame yang membuat atau menavigasi frame target. Meskipun biasanya merupakan induk atau pembuka langsung, hal ini mungkin tidak terjadi (seperti dalam kasus frame yang menavigasi iframe dalam iframe).
Karena membandingkan asal frame pemrakarsa, frame pemrakarsa
dapat berada di jalur mana pun dari asal tersebut. Untuk memperjelas implikasi ini, Chrome mewajibkan skrip konten apa pun yang ditentukan dengan "match_origin_as_fallback"
yang ditetapkan ke true
juga menentukan jalur *
.
Jika "match_origin_as_fallback"
dan "match_about_blank"
ditentukan, "match_origin_as_fallback"
akan diprioritaskan.
Komunikasi dengan halaman penyematan
Meskipun lingkungan eksekusi skrip konten dan halaman yang menghostingnya terisolasi satu sama lain, keduanya berbagi akses ke DOM halaman. Jika halaman ingin berkomunikasi dengan skrip konten, atau dengan ekstensi melalui skrip konten, halaman harus melakukannya melalui DOM bersama.
Contoh dapat dilakukan menggunakan 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);
Halaman non-ekstensi, example.html, memposting pesan ke dirinya sendiri. Pesan ini dicegat dan diperiksa oleh skrip konten, lalu diposting ke proses ekstensi. Dengan cara ini, halaman menjalin komunikasi dengan proses ekstensi. Hal sebaliknya dapat dilakukan dengan cara yang serupa.
Mengakses file ekstensi
Untuk mengakses file ekstensi dari skrip konten, Anda dapat memanggil
chrome.runtime.getURL()
untuk mendapatkan URL absolut aset ekstensi seperti yang ditunjukkan dalam contoh berikut (content.js
):
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
Untuk menggunakan font atau gambar dalam file CSS, Anda dapat menggunakan @@extension_id
untuk membuat URL seperti yang ditunjukkan dalam contoh berikut (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');
}
Semua aset harus dideklarasikan sebagai resource yang dapat diakses web dalam file manifest.json
:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Kebijakan Keamanan Konten
Skrip konten yang berjalan di dunia terisolasi memiliki Kebijakan Keamanan Konten (CSP) berikut:
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Serupa dengan batasan yang diterapkan pada konteks ekstensi lainnya, hal ini mencegah penggunaan eval()
serta pemuatan skrip eksternal.
Untuk ekstensi yang tidak di-unzip, CSP juga menyertakan localhost:
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
Saat skrip konten disuntikkan ke dunia utama, CSP halaman akan berlaku.
Tetap aman
Meskipun dunia terisolasi memberikan lapisan perlindungan, penggunaan skrip konten dapat menimbulkan kerentanan dalam ekstensi dan halaman web. Jika skrip konten menerima konten dari situs terpisah, seperti dengan memanggil fetch()
, berhati-hatilah untuk memfilter konten terhadap serangan cross-site scripting sebelum menyuntikkannya. Hanya berkomunikasi melalui HTTPS untuk menghindari serangan "man-in-the-middle".
Pastikan untuk memfilter halaman web berbahaya. Misalnya, pola berikut berbahaya, dan tidak diizinkan di 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);
Sebagai gantinya, pilih API yang lebih aman yang tidak menjalankan skrip:
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);