Skrip konten adalah file yang berjalan dalam konteks halaman web. Dengan menggunakan Document Object Model (DOM) standar, browser dapat membaca detail halaman web yang dikunjungi browser, membuat perubahan pada halaman tersebut, dan meneruskan informasi ke ekstensi induknya.
Memahami kemampuan skrip konten
Skrip konten dapat mengakses file ekstensi setelah mendeklarasikannya sebagai resource yang dapat diakses di web. Mereka 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 ekstensi Anda.
Bekerja di dunia yang terisolasi
Skrip konten berada di dunia yang terisolasi, yang memungkinkan skrip konten mengubah lingkungan JavaScript-nya tanpa bertentangan 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 memasukkan skrip konten berikut menggunakan salah satu teknik yang diuraikan di bagian Memasukkan 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 peringatan akan muncul secara berurutan saat tombol diklik.
Memasukkan skrip
Skrip konten dapat dideklarasikan secara statis, dideklarasikan secara dinamis, atau dimasukkan secara terprogram.
Menginjeksikan dengan deklarasi statis
Gunakan deklarasi skrip konten statis dalam manifest.json untuk skrip yang harus dijalankan otomatis di sekumpulan halaman yang sudah dikenal.
Skrip yang dideklarasikan secara statis didaftarkan dalam manifes di bawah kunci "content_scripts"
.
File tersebut dapat menyertakan file JavaScript, file CSS, atau keduanya. Semua skrip konten yang dijalankan secara otomatis harus menentukan
pola pencocokan.
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 akan dimasukkan. Lihat Pola Pencocokan untuk mengetahui detail sintaksis string ini dan Cocokkan pola dan glob untuk mengetahui informasi tentang cara mengecualikan URL. |
css |
array string | Opsional. Daftar file CSS yang akan dimasukkan ke halaman yang cocok. Objek tersebut dimasukkan sesuai urutan kemunculannya dalam array ini, sebelum DOM dibuat atau ditampilkan untuk halaman tersebut. |
js |
|
Opsional. Daftar file JavaScript yang akan dimasukkan ke halaman yang cocok. File dimasukkan sesuai urutan kemunculannya dalam array ini. Setiap string dalam daftar ini harus berisi jalur relatif ke resource di direktori utama ekstensi. Garis miring di awal (`/`) otomatis dipangkas. |
run_at |
RunAt | Opsional. Menentukan kapan skrip harus dimasukkan ke halaman. Nilai defaultnya adalah document_idle . |
match_about_blank |
boolean | Opsional. Apakah skrip harus dimasukkan ke dalam frame about:blank
tempat frame induk atau frame pembuka cocok dengan salah satu pola yang dideklarasikan dalam
matches . Nilai defaultnya adalah false (salah). |
match_origin_as_fallback |
boolean |
Opsional. Apakah skrip harus dimasukkan dalam frame yang dibuat oleh origin yang cocok, tetapi yang URL atau originnya mungkin tidak secara langsung cocok dengan pola. Ini mencakup frame dengan skema berbeda, seperti
about: , data: , blob: , dan
filesystem: . Lihat juga
Memasukkan frame terkait.
|
world |
ExecutionWorld |
Opsional. Dunia JavaScript untuk eksekusi skrip. Nilai defaultnya adalah ISOLATED . Lihat juga
Bekerja di dunia terisolasi.
|
Menginjeksikan dengan deklarasi dinamis
Skrip konten dinamis berguna jika pola pencocokan untuk skrip konten tidak terlalu dikenal atau skrip konten tidak boleh selalu dimasukkan ke host yang dikenal.
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. Dengan Scripting API, developer ekstensi dapat:
- Daftarkan skrip konten.
- Dapatkan daftar skrip konten yang terdaftar.
- Perbarui daftar skrip konten yang terdaftar.
- Hapus skrip konten yang terdaftar.
Seperti deklarasi statis, deklarasi dinamis dapat menyertakan 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"));
Memasukkan secara terprogram
Gunakan injeksi terprogram untuk skrip konten yang perlu dijalankan sebagai respons terhadap peristiwa atau pada kesempatan tertentu.
Untuk memasukkan skrip konten secara terprogram, ekstensi Anda memerlukan izin host untuk halaman yang ingin dimasukkan skrip. Izin host dapat diberikan dengan memintanya sebagai bagian dari manifes ekstensi Anda atau untuk sementara menggunakan "activeTab"
.
Berikut ini 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 dimasukkan 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 dimasukkan adalah salinan fungsi yang direferensikan dalam
panggilan chrome.scripting.executeScript()
, bukan fungsi asli itu sendiri. Akibatnya, isi fungsi harus dimuat sendiri; referensi ke variabel di luar fungsi akan menyebabkan skrip konten menampilkan ReferenceError
.
Saat memasukkan sebagai fungsi, Anda juga dapat meneruskan argumen ke fungsi tersebut.
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" ],
});
});
Kecualikan 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 tempat skrip konten ini akan dimasukkan ke dalamnya. Lihat Pola Pencocokan untuk detail sintaksis string ini. |
include_globs |
array string | Opsional. Diterapkan setelah matches untuk hanya menyertakan URL yang juga
cocok dengan glob ini. Hal ini dimaksudkan untuk mengemulasi kata kunci @include
GreaseMonkey. |
exclude_globs |
array string | Opsional. Diterapkan setelah matches untuk mengecualikan URL yang cocok dengan glob ini. Ditujukan untuk mengemulasi kata kunci @exclude
GreaseMonkey. |
Skrip konten akan dimasukkan ke halaman jika kedua hal berikut berlaku:
- URL-nya cocok dengan pola
matches
apa pun dan polainclude_globs
apa pun. - URL juga tidak cocok dengan pola
exclude_matches
atauexclude_globs
. Karena propertimatches
bersifat wajib,exclude_matches
,include_globs
, danexclude_globs
hanya dapat digunakan untuk membatasi halaman yang akan terpengaruh.
Ekstensi berikut memasukkan skrip konten ke dalam https://www.nytimes.com/health
,
tetapi tidak ke dalam 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 pencocokan. String glob yang dapat diterima adalah URL yang mungkin berisi tanda bintang dan tanda tanya "karakter pengganti". Tanda bintang (*
)
cocok dengan string apa pun dengan panjang berapa pun, termasuk string kosong, sedangkan tanda tanya (?
) cocok dengan
karakter tunggal apa pun.
Misalnya, glob https://???.example.com/foo/\*
cocok dengan salah satu kolom berikut:
https://www.example.com/foo/bar
https://the.example.com/foo/
Namun, string tersebut tidak cocok dengan kode berikut:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
Ekstensi ini memasukkan skrip konten ke dalam 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 memasukkan skrip konten ke dalam https://history.nytimes.com
dan
https://.nytimes.com/history
, tetapi tidak ke dalam 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 beberapa hal tersebut dapat disertakan untuk mencapai ruang lingkup 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 dimasukkan ke halaman web. Nilai pilihan dan
default-nya adalah "document_idle"
. Lihat jenis RunAt untuk nilai lainnya yang memungkinkan.
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 memasukkan skrip antara "document_end" dan segera setelah peristiwa window.onload diaktifkan. Momen persis injeksi bergantung pada seberapa rumit dokumen dan berapa lama waktu yang diperlukan untuk dimuat, serta dioptimalkan untuk kecepatan pemuatan halaman.Skrip konten yang berjalan di "document_idle" tidak perlu memproses peristiwa window.onload , skrip tersebut dijamin akan berjalan setelah DOM selesai. Jika skrip benar-benar perlu dijalankan setelah window.onload , ekstensi dapat memeriksa apakah onload telah diaktifkan menggunakan properti document.readyState . |
document_start |
string | Skrip dimasukkan setelah file apa pun dari css , tetapi sebelum DOM lain dibuat atau skrip lain dijalankan. |
document_end |
string | Skrip akan langsung dimasukkan setelah DOM selesai, tetapi sebelum subresource seperti gambar dan frame dimuat. |
Menentukan frame
Kolom "all_frames"
memungkinkan ekstensi untuk menentukan apakah file JavaScript dan CSS harus dimasukkan ke semua frame yang cocok dengan persyaratan URL yang ditentukan atau hanya ke frame teratas di tab.
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" ],
}]);
Nama | Jenis | Deskripsi |
---|---|---|
all_frames |
boolean | Opsional. Defaultnya adalah false , yang berarti hanya frame atas yang
cocok.Jika true ditentukan, semua frame akan dimasukkan ke dalamnya, meskipun
frame tersebut bukan frame teratas di tab. Setiap frame diperiksa secara terpisah untuk mengetahui persyaratan URL. Kode tidak akan dimasukkan ke dalam frame turunan jika persyaratan URL tidak terpenuhi. |
Memasukkan ke frame terkait
Ekstensi mungkin ingin menjalankan skrip dalam frame yang terkait dengan frame yang cocok, tetapi tidak cocok. Skenario umum jika hal ini terjadi adalah untuk frame dengan URL yang dibuat oleh frame yang cocok, tetapi URL yang URL-nya tidak cocok dengan pola skrip yang ditentukan.
Hal ini terjadi saat ekstensi ingin memasukkan frame dengan URL yang memiliki skema about:
, data:
, blob:
, dan filesystem:
. Dalam kasus ini, URL
tidak akan cocok dengan pola skrip konten (dan, untuk about:
dan
data:
, bahkan jangan sertakan URL induk atau origin sama sekali di URL, seperti di about:blank
atau data:text/html,<html>Hello, World!</html>
).
Namun, frame ini masih dapat dikaitkan dengan frame pembuatan.
Untuk memasukkan ke dalam frame ini, ekstensi dapat menentukan
properti "match_origin_as_fallback"
pada spesifikasi skrip konten di
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
inisiator frame untuk menentukan apakah frame cocok, bukan
URL frame itu sendiri. Perhatikan bahwa ini mungkin juga berbeda dengan
origin frame target (mis., URL data:
memiliki asal null).
Inisiator frame adalah frame yang membuat atau membuka frame target. Meskipun biasanya berupa induk atau pembuka langsung, hal ini mungkin tidak (seperti dalam kasus frame yang menavigasi iframe dalam iframe).
Karena nilai ini membandingkan origin frame inisiator, frame inisiator dapat berada di jalur mana pun dari origin tersebut. Untuk memperjelas implikasi ini, Chrome
memerlukan skrip konten apa pun yang ditentukan dengan "match_origin_as_fallback"
yang ditetapkan ke true
untuk 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.
Contohnya 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 halaman itu sendiri. Pesan ini dicegat dan diperiksa oleh skrip konten, lalu diposting ke proses ekstensi. Dengan cara ini, halaman menetapkan jalur komunikasi ke proses ekstensi. Hal sebaliknya dapat dilakukan melalui 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/*" ]
}
],
...
}
Tetap aman
Meskipun dunia terisolasi memberikan lapisan perlindungan, penggunaan skrip konten dapat menciptakan 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 pembuatan skrip lintas situs sebelum memasukkannya. Hanya berkomunikasi melalui HTTPS untuk menghindari
serangan "man-in-the-middle".
Pastikan Anda memfilter halaman web berbahaya. Misalnya, pola berikut berbahaya, dan tidak diizinkan di Manifes 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);
Sebaliknya, lebih baik 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);