Karena skrip konten berjalan dalam konteks halaman web, bukan ekstensi yang menjalankannya, skrip tersebut sering kali memerlukan cara untuk berkomunikasi dengan ekstensi lainnya. Misalnya, ekstensi pembaca RSS mungkin menggunakan skrip konten untuk mendeteksi keberadaan feed RSS di halaman, lalu memberi tahu pekerja layanan untuk menampilkan ikon tindakan untuk halaman tersebut.
Komunikasi ini menggunakan penerusan pesan, yang memungkinkan ekstensi dan skrip konten untuk memproses pesan satu sama lain dan merespons di saluran yang sama. Pesan dapat berisi objek JSON yang valid (null, boolean, angka, string, array, atau objek). Ada dua API penerusan pesan: satu untuk permintaan satu kali, dan satu lagi yang lebih kompleks untuk koneksi jangka panjang yang memungkinkan beberapa pesan dikirim. Untuk informasi tentang cara mengirim pesan antar-ekstensi, lihat bagian pesan lintas ekstensi.
Permintaan satu kali
Untuk mengirim satu pesan ke bagian lain ekstensi Anda, dan secara opsional mendapatkan
respons, panggil runtime.sendMessage()
atau tabs.sendMessage()
.
Metode ini memungkinkan Anda mengirim pesan JSON yang dapat diserialisasi satu kali dari skrip konten ke ekstensi, atau dari ekstensi ke skrip konten. Untuk menangani respons, gunakan promise yang ditampilkan. Untuk kompatibilitas mundur dengan ekstensi lama, Anda dapat meneruskan callback sebagai
argumen terakhir. Anda tidak dapat menggunakan promise dan callback dalam panggilan yang sama.
Untuk informasi tentang cara mengonversi callback menjadi promise dan menggunakannya dalam ekstensi, lihat panduan migrasi Manifes V3.
Pengiriman permintaan dari skrip konten akan terlihat seperti ini:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Jika Anda ingin merespons pesan secara sinkron, cukup panggil sendResponse
setelah Anda mendapatkan respons, lalu tampilkan false
untuk menunjukkan bahwa proses sudah selesai. Untuk merespons secara asinkron, tampilkan true
agar callback sendResponse
tetap aktif hingga Anda siap menggunakannya. Fungsi asinkron tidak didukung karena menampilkan Promise, yang tidak didukung.
Untuk mengirim permintaan ke skrip konten, tentukan tab tempat permintaan tersebut diterapkan, seperti yang ditunjukkan di bawah ini. Contoh ini berfungsi di pekerja layanan, pop-up, dan halaman chrome-extension:// yang dibuka sebagai tab.
(async () => {
const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Untuk menerima pesan, siapkan pemroses peristiwa runtime.onMessage
. Keduanya menggunakan kode yang sama di ekstensi dan skrip konten:
content-script.js atau service-worker.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting === "hello")
sendResponse({farewell: "goodbye"});
}
);
Pada contoh sebelumnya, sendResponse()
dipanggil secara sinkron. Untuk menggunakan sendResponse()
secara asinkron, tambahkan return true;
ke pengendali peristiwa onMessage
.
Jika beberapa halaman memproses peristiwa onMessage
, hanya halaman pertama yang memanggil sendResponse()
untuk peristiwa tertentu yang akan berhasil mengirim respons. Semua respons lainnya terhadap peristiwa tersebut akan
diabaikan.
Koneksi jangka panjang
Untuk membuat saluran penerusan pesan yang dapat digunakan kembali dan berumur panjang, panggil runtime.connect()
untuk meneruskan pesan dari skrip konten ke halaman ekstensi, atau tabs.connect()
untuk meneruskan pesan dari halaman ekstensi ke skrip konten. Anda dapat menamai saluran untuk
membedakan berbagai jenis koneksi.
Salah satu kasus penggunaan potensial untuk koneksi yang berumur panjang adalah ekstensi pengisian formulir otomatis. Skrip konten dapat membuka saluran ke halaman ekstensi untuk login tertentu, dan mengirimkan pesan ke ekstensi untuk setiap elemen input di halaman guna meminta data formulir yang perlu diisi. Koneksi bersama memungkinkan ekstensi berbagi status antarkomponen ekstensi.
Saat membuat koneksi, setiap ujung diberi objek runtime.Port
untuk
mengirim dan menerima pesan melalui koneksi tersebut.
Gunakan kode berikut untuk membuka saluran dari skrip konten, serta mengirim dan memproses pesan:
content-script.js:
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question === "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question === "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});
Untuk mengirim permintaan dari ekstensi ke skrip konten, ganti panggilan ke runtime.connect()
dalam contoh sebelumnya dengan tabs.connect()
.
Untuk menangani koneksi yang masuk baik untuk skrip konten atau halaman ekstensi, siapkan pemroses peristiwa runtime.onConnect
. Saat bagian lain ekstensi Anda memanggil connect()
, bagian tersebut akan mengaktifkan peristiwa ini dan objek runtime.Port
. Kode untuk merespons koneksi masuk akan terlihat seperti ini:
service-worker.js:
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name === "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke === "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer === "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer === "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
Masa aktif port
Port dirancang sebagai metode komunikasi dua arah antara bagian ekstensi yang berbeda. Frame tingkat atas adalah bagian terkecil dari ekstensi yang dapat menggunakan port.
Jika bagian dari ekstensi memanggil tabs.connect()
, runtime.connect()
, atau runtime.connectNative()
, ekstensi tersebut akan membuat Port yang dapat langsung mengirim pesan menggunakan postMessage()
.
Jika ada beberapa frame dalam tab, memanggil tabs.connect()
akan memanggil
peristiwa runtime.onConnect
satu kali untuk setiap frame dalam tab. Demikian pula, jika
runtime.connect()
dipanggil, peristiwa onConnect
dapat diaktifkan satu kali untuk setiap
frame dalam proses ekstensi.
Anda mungkin ingin mengetahui kapan koneksi ditutup, misalnya jika Anda mempertahankan status
terpisah untuk setiap port terbuka. Untuk melakukannya, proses peristiwa runtime.Port.onDisconnect
. Peristiwa ini
terpicu saat tidak ada port yang valid di ujung lain saluran, yang dapat disebabkan oleh salah satu hal berikut:
- Tidak ada pemroses untuk
runtime.onConnect
di ujung lain. - Tab yang berisi port dihapus muatannya (misalnya, jika tab dibuka).
- Frame tempat
connect()
dipanggil telah dibongkar. - Semua frame yang menerima port (melalui
runtime.onConnect
) telah di-unload. runtime.Port.disconnect()
dipanggil oleh ujung lain. Jika panggilanconnect()
menghasilkan beberapa port di pihak penerima, dandisconnect()
dipanggil di salah satu port ini, peristiwaonDisconnect
hanya diaktifkan pada port pengirim, bukan di port lain.
Pesan lintas ekstensi
Selain mengirim pesan antar-komponen yang berbeda di ekstensi, Anda dapat menggunakan messaging API untuk berkomunikasi dengan ekstensi lain. Tindakan ini memungkinkan Anda mengekspos API publik untuk digunakan oleh ekstensi lain.
Untuk memproses permintaan dan koneksi masuk dari ekstensi lain, gunakan metode runtime.onMessageExternal
atau runtime.onConnectExternal
. Berikut adalah contoh
masing-masing:
service-worker.js
// For a single request:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id === blocklistedExtension)
return; // don't allow this extension access
else if (request.getTargetData)
sendResponse({targetData: targetData});
else if (request.activateLasers) {
var success = activateLasers();
sendResponse({activateLasers: success});
}
});
// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
Untuk mengirim pesan ke ekstensi lain, teruskan ID ekstensi yang ingin Anda ajak berkomunikasi sebagai berikut:
service-worker.js
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// For a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// For a long-lived connection:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Mengirim pesan dari halaman web
Ekstensi juga dapat menerima dan merespons pesan dari halaman web lain, tetapi tidak dapat mengirim pesan
ke halaman web. Untuk mengirim pesan dari halaman web ke ekstensi, tentukan di manifest.json
situs mana yang ingin Anda komunikasikan menggunakan kunci manifes "externally_connectable"
. Contoh:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Tindakan ini akan mengekspos API pesan ke halaman apa pun yang cocok dengan pola URL yang Anda tentukan. Pola URL
harus berisi setidaknya domain level kedua; yaitu, pola nama host seperti "*",
"*.com", "*.co.uk", dan "*.appspot.com" tidak didukung. Mulai Chrome 107, Anda dapat menggunakan
<all_urls>
untuk mengakses semua domain. Perhatikan bahwa karena memengaruhi semua host,
tinjauan Chrome Web Store untuk ekstensi yang menggunakannya mungkin memerlukan waktu lebih lama.
Gunakan API runtime.sendMessage()
atau runtime.connect()
untuk mengirim pesan ke aplikasi atau ekstensi tertentu. Contoh:
webpage.js
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
Dari ekstensi Anda, dengarkan pesan dari halaman web menggunakan
runtime.onMessageExternal
atau runtime.onConnectExternal
API seperti dalam pesan lintas ekstensi. Berikut contohnya:
service-worker.js
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url === blocklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
Pesan native
Ekstensi dapat bertukar pesan dengan aplikasi native yang terdaftar sebagai host pesan native. Untuk mempelajari fitur ini lebih lanjut, lihat Pesan native.
Pertimbangan keamanan
Berikut beberapa pertimbangan keamanan terkait pesan.
Skrip konten kurang tepercaya
Skrip konten kurang tepercaya dibandingkan pekerja layanan ekstensi. Misalnya, halaman web berbahaya mungkin dapat membahayakan proses rendering yang menjalankan skrip konten. Asumsikan bahwa pesan dari skrip konten mungkin telah dibuat oleh penyerang dan pastikan untuk memvalidasi dan membersihkan semua input. Asumsikan data apa pun yang dikirim ke skrip konten dapat bocor ke halaman web. Batasi cakupan tindakan dengan hak istimewa yang dapat dipicu oleh pesan yang diterima dari skrip konten.
Pembuatan skrip antarsitus
Pastikan untuk melindungi skrip Anda dari cross-site scripting. Saat menerima data dari sumber yang tidak tepercaya seperti input pengguna, situs lain melalui skrip konten, atau API, berhati-hatilah untuk tidak menafsirkannya sebagai HTML atau menggunakannya dengan cara yang dapat memungkinkan kode yang tidak terduga untuk berjalan.
Gunakan API yang tidak menjalankan skrip jika memungkinkan:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse doesn't evaluate the attacker's scripts. var resp = JSON.parse(response.farewell); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // innerText does not let the attacker inject HTML elements. document.getElementById("resp").innerText = response.farewell; });
Hindari menggunakan metode berikut yang membuat ekstensi Anda rentan:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be evaluating a malicious script! var resp = eval(`(${response.farewell})`); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be injecting a malicious script! document.getElementById("resp").innerHTML = response.farewell; });