Skrip konten

Skrip konten adalah file yang berjalan dalam konteks halaman web. Dengan menggunakan Document Object Model (DOM) standar, ekstensi ini 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 API ekstensi berikut secara langsung:

Skrip konten tidak dapat mengakses API lain secara langsung. Namun, ekstensi dapat mengaksesnya secara tidak langsung dengan bertukar pesan dengan bagian lain ekstensi Anda.

Anda juga dapat mengakses file lain di ekstensi dari skrip konten, menggunakan API seperti fetch(). Untuk melakukannya, Anda perlu mendeklarasikannya sebagai resource yang dapat diakses web. Perhatikan bahwa tindakan 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 memungkinkan skrip konten membuat perubahan pada 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 pemberitahuan akan muncul secara berurutan saat tombol diklik.

Memasukkan skrip

Skrip konten dapat dideklarasikan secara statis, dideklarasikan secara dinamis, atau dimasukkan secara terprogram.

Memasukkan dengan deklarasi statis

Gunakan deklarasi skrip konten statis di manifest.json untuk skrip yang harus dijalankan secara otomatis di kumpulan halaman yang 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 berjalan 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 ini akan dimasukkan. Lihat Pola Pencocokan untuk mengetahui detail tentang sintaksis string ini dan Pola pencocokan dan glob untuk mengetahui informasi tentang cara mengecualikan URL.
css array string Opsional. Daftar file CSS yang akan dimasukkan ke halaman yang cocok. Ini dimasukkan dalam urutan yang muncul dalam array ini, sebelum DOM dibuat atau ditampilkan untuk halaman.
js array string 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 root ekstensi. Garis miring awal (`/`) akan 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 dengan frame induk atau pembuka yang cocok dengan salah satu pola yang dideklarasikan di 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 URL atau origin-nya mungkin tidak cocok secara langsung dengan pola. Ini termasuk bingkai dengan skema yang berbeda, seperti about:, data:, blob:, dan filesystem:. Lihat juga Memasukkan dalam frame terkait.
world ExecutionWorld Opsional. Lingkungan JavaScript untuk dieksekusi skrip. Nilai defaultnya adalah ISOLATED. Lihat juga Bekerja di dunia yang terisolasi.

Menyisipkan dengan deklarasi dinamis

Skrip konten dinamis berguna jika pola pencocokan untuk skrip konten tidak dikenal dengan baik atau jika skrip konten tidak boleh selalu dimasukkan 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:

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 acara tertentu.

Untuk memasukkan skrip konten secara terprogram, ekstensi Anda memerlukan izin host untuk halaman tempat skrip akan dimasukkan. Izin host dapat diberikan dengan memintanya sebagai bagian dari manifes ekstensi Anda 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 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,
  });
});

Perhatikan bahwa fungsi yang dimasukkan adalah salinan fungsi yang direferensikan dalam panggilan chrome.scripting.executeScript(), bukan fungsi aslinya sendiri. Akibatnya, isi fungsi harus mandiri; 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" ],
  });
});

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 tempat skrip konten ini akan dimasukkan. Lihat Pola Pencocokan untuk mengetahui 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 Greasemonkey @include.
exclude_globs array string Opsional. Diterapkan setelah matches untuk mengecualikan URL yang cocok dengan glob ini. Dimaksudkan untuk mengemulasi kata kunci Greasemonkey @exclude.

Skrip konten akan dimasukkan ke dalam halaman jika kedua hal berikut berlaku:

  • URL-nya cocok dengan pola matches dan pola include_globs apa pun.
  • URL juga tidak cocok dengan pola exclude_matches atau exclude_globs. Karena properti matches diperlukan, exclude_matches, include_globs, dan exclude_globs hanya dapat digunakan untuk membatasi halaman mana 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 dapat berisi tanda bintang "karakter pengganti" dan tanda tanya. Tanda bintang (*) cocok dengan string apa pun dengan panjang berapa 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:

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

Namun, tidak cocok dengan hal 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 dalam 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 dari hal ini 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 dimasukkan 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 memasukkan skrip antara "document_end" dan segera setelah peristiwa window.onload diaktifkan. Momen injeksi yang tepat bergantung pada kompleksitas dokumen dan waktu yang diperlukan untuk memuatnya, serta dioptimalkan untuk kecepatan pemuatan halaman.

Skrip konten yang berjalan di "document_idle" tidak perlu memproses peristiwa window.onload, karena skrip tersebut dijamin akan berjalan setelah DOM selesai. Jika skrip harus dijalankan setelah window.onload, ekstensi dapat memeriksa apakah onload telah diaktifkan menggunakan properti document.readyState.
document_start string Skrip dimasukkan setelah file dari css, tetapi sebelum DOM lain dibuat atau skrip lain dijalankan.
document_end string Skrip dimasukkan segera setelah DOM selesai, tetapi sebelum sub-resource 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 dimasukkan ke semua frame yang cocok dengan persyaratan URL yang ditentukan atau hanya ke frame paling atas 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 dimasukkan 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" ],
}]);

Ekstensi mungkin ingin menjalankan skrip dalam frame yang terkait dengan frame yang cocok, tetapi tidak cocok dengan dirinya sendiri. Skenario umum jika hal ini terjadi adalah untuk frame dengan URL yang dibuat oleh frame yang cocok, tetapi URL-nya tidak cocok dengan pola yang ditentukan skrip.

Hal ini terjadi jika ekstensi ingin memasukkan dalam 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 induk atau asal dalam URL sama sekali, seperti dalam 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 dalam manifes.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Jika ditentukan dan ditetapkan ke true, Chrome akan melihat asal pemicu frame untuk menentukan apakah frame cocok, bukan pada URL frame itu sendiri. Perhatikan bahwa ini mungkin juga berbeda dengan asal frame target (misalnya, URL data: memiliki origin null).

Pemula 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 ini membandingkan asal frame inisiator, frame inisiator dapat berada di jalur mana pun dari asal 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.

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 akan membuat 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 membuat 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 untuk memfilter halaman web berbahaya. Misalnya, pola berikut berbahaya, dan tidak diizinkan di Manifest V3:

Larangan

content-script.js

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

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 dan tidak menjalankan skrip:

Anjuran

content-script.js

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

content-script.js

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