Pesan yang diteruskan

Karena skrip konten dijalankan dalam konteks halaman web, bukan ekstensi yang menjalankannya, skrip konten 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 agar menampilkan ikon tindakan untuk halaman tersebut.

Komunikasi ini menggunakan penerusan pesan, yang memungkinkan ekstensi dan skrip konten mendengarkan pesan satu sama lain dan merespons di saluran yang sama. Pesan boleh berisi objek JSON yang valid (null, boolean, angka, string, array, atau objek). Ada dua pesan yang meneruskan API: satu untuk permintaan satu kali, dan 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 jika ingin mendapatkan respons, panggil runtime.sendMessage() atau tabs.sendMessage(). Metode ini memungkinkan Anda mengirim pesan yang dapat diserialisasi JSON satu kali dari skrip konten ke ekstensi, atau dari ekstensi ke skrip konten. Untuk menangani responsnya, gunakan promise yang ditampilkan. Untuk kompatibilitas mundur dengan ekstensi yang lebih lama, Anda dapat meneruskan callback sebagai argumen terakhir. Anda tidak bisa menggunakan promise dan callback dalam panggilan yang sama.

Untuk mengetahui informasi tentang cara mengonversi callback ke promise dan penggunaannya dalam ekstensi, lihat Panduan migrasi Manifes V3.

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);
})();

Untuk mengirim permintaan ke skrip konten, tentukan tab tempat permintaan diterapkan seperti yang ditunjukkan di bawah ini. Contoh ini berfungsi di halaman pekerja layanan, pop-up, dan 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. Kedua skrip ini menggunakan kode yang sama pada skrip konten dan ekstensi:

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 tanggapan lain terhadap acara tersebut akan diabaikan.

Koneksi jangka panjang

Untuk membuat saluran penerusan pesan berdurasi panjang yang dapat digunakan kembali, 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 Anda untuk membedakan antara berbagai jenis koneksi.

Salah satu kasus penggunaan potensial untuk koneksi 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 untuk diisi. Koneksi bersama memungkinkan ekstensi untuk 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 channel dari skrip konten, lalu kirim dan dengarkan 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() di contoh sebelumnya dengan tabs.connect().

Untuk menangani koneksi yang masuk untuk skrip konten atau halaman ekstensi, siapkan pemroses peristiwa runtime.onConnect. Saat bagian lain dari ekstensi Anda memanggil connect(), peristiwa ini dan objek runtime.Port akan diaktifkan. Kode untuk merespons koneksi masuk 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

Porta dirancang sebagai metode komunikasi dua arah antara berbagai bagian ekstensi. Frame level atas adalah bagian terkecil dari ekstensi yang dapat menggunakan port. Saat bagian dari ekstensi memanggil tabs.connect(), runtime.connect(), atau runtime.connectNative(), ekstensi akan membuat Port yang dapat segera mengirim pesan menggunakan postMessage().

Jika ada beberapa frame di tab, memanggil tabs.connect() akan memanggil peristiwa runtime.onConnect satu kali untuk setiap frame di 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 yang terbuka. Untuk melakukannya, proses peristiwa runtime.Port.onDisconnect. Peristiwa ini aktif ketika tidak ada port yang valid di ujung lain saluran, yang dapat disebabkan oleh salah satu penyebab berikut:

  • Tidak ada pemroses untuk runtime.onConnect di ujung lain.
  • Tab yang berisi port akan dihapus muatannya (misalnya, jika tab dibuka).
  • Frame tempat connect() dipanggil telah menghapus muatannya.
  • Semua frame yang menerima port (melalui runtime.onConnect) telah menghapus muatannya.
  • runtime.Port.disconnect() dipanggil oleh sisi satunya. Jika panggilan connect() menghasilkan beberapa port di ujung penerima, dan disconnect() dipanggil pada salah satu port ini, peristiwa onDisconnect hanya akan diaktifkan di port pengirim, bukan di port lain.

Pesan lintas-ekstensi

Selain mengirim pesan antara berbagai komponen dalam ekstensi, Anda dapat menggunakan API pesan untuk berkomunikasi dengan ekstensi lain. Tindakan ini memungkinkan Anda mengekspos API publik untuk digunakan oleh ekstensi lain.

Untuk memproses permintaan masuk dan koneksi dari ekstensi lain, gunakan metode runtime.onMessageExternal atau runtime.onConnectExternal. Berikut contoh masing-masingnya:

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 gunakan untuk berkomunikasi seperti 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 membalas 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 ajak berkomunikasi menggunakan kunci manifes "externally_connectable". Contoh:

manifest.json

"externally_connectable": {
  "matches": ["https://*.example.com/*"]
}

API ini akan mengekspos API pesan ke halaman apa pun yang cocok dengan pola URL yang Anda tentukan. Pola URL harus berisi setidaknya satu 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 hal ini memengaruhi semua host, peninjauan 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, proses 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 adalah beberapa pertimbangan keamanan yang terkait dengan 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 setiap data yang dikirim ke skrip konten mungkin 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 pembuatan skrip lintas situs. Saat menerima data dari sumber yang tidak tepercaya seperti input pengguna, situs lain melalui skrip konten, atau API, hindari menafsirkannya sebagai HTML atau menggunakannya dengan cara yang dapat memungkinkan kode yang tidak diharapkan berjalan.

Metode yang lebih aman

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;
});
Metode tidak aman

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;
});