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.
Saat Anda mengirim pesan, pemroses peristiwa yang menangani pesan akan menerima argumen ketiga opsional, sendResponse
. Ini adalah fungsi yang menggunakan objek yang dapat diserialisasi JSON yang digunakan sebagai nilai yang ditampilkan ke fungsi yang mengirim pesan. Secara default, callback sendResponse
harus dipanggil secara sinkron. Jika ingin melakukan pekerjaan asinkron untuk mendapatkan nilai
yang diteruskan ke sendResponse
, Anda harus menampilkan true
literal (bukan hanya nilai yang benar) dari
pemroses peristiwa. Tindakan ini akan membuat saluran pesan tetap terbuka ke ujung lain hingga sendResponse
dipanggil.
// Event listener
function handleMessages(message, sender, sendResponse) {
fetch(message.url)
.then((response) => sendResponse({statusCode: response.status}))
// Since `fetch` is asynchronous, must send an explicit `true`
return true;
}
// Message sender
const {statusCode} = await chrome.runtime.sendMessage({
url: 'https://example.com'
});
Untuk informasi tentang cara mengonversi callback menjadi promise dan menggunakannya dalam ekstensi, lihat panduan migrasi Manifes V3.
Mengirim 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 memiliki respons, dan tampilkan false
untuk menunjukkan bahwa respons telah 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 yang menjadi tujuan permintaan 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 berumur 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 memberi nama channel 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 mengirim pesan ke ekstensi untuk setiap elemen input di halaman guna meminta data formulir yang akan 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 masuk 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 berbagai bagian ekstensi. Frame tingkat teratas 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 diaktifkan 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 di-unload (misalnya, jika tab dinavigasi).
- Frame tempat
connect()
dipanggil telah di-unload. - Semua frame yang menerima port (melalui
runtime.onConnect
) telah di-unload. runtime.Port.disconnect()
dipanggil oleh ujung lainnya. Jika panggilanconnect()
menghasilkan beberapa port di sisi penerima, dandisconnect()
dipanggil di salah satu port ini, peristiwaonDisconnect
hanya diaktifkan di port pengiriman, bukan di port lainnya.
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 minimal 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 minimal 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.
const editorExtensionId = 'abcdefghijklmnoabcdefhijklmnoabc';
// Check if extension is installed
if (chrome && chrome.runtime) {
// Make a request:
chrome.runtime.sendMessage(
editorExtensionId,
{
openUrlInEditor: url
},
(response) => {
if (!response.success) handleError(url);
}
);
}
Dari ekstensi Anda, dengarkan pesan dari halaman web menggunakan
API runtime.onMessageExternal
atau runtime.onConnectExternal
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 daripada 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 agar tidak menafsirkannya sebagai HTML atau menggunakannya dengan cara yang dapat memungkinkan kode yang tidak terduga 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; });