Bermigrasi ke pekerja layanan

Mengganti halaman latar belakang atau acara dengan service worker

Service worker menggantikan halaman latar belakang atau peristiwa ekstensi untuk memastikan kode latar belakang tetap berada di luar thread utama. Hal ini memungkinkan ekstensi berjalan hanya saat diperlukan, sehingga menghemat resource.

Halaman latar belakang telah menjadi komponen mendasar ekstensi sejak diperkenalkan. Sederhananya, halaman latar belakang menyediakan lingkungan yang tidak bergantung pada jendela atau tab lain. Hal ini memungkinkan ekstensi mengamati dan bertindak sebagai respons terhadap peristiwa.

Halaman ini menjelaskan tugas untuk mengonversi halaman latar belakang menjadi pekerja layanan ekstensi. Untuk mengetahui informasi selengkapnya tentang pekerja layanan ekstensi secara umum, lihat tutorial Menangani peristiwa dengan pekerja layanan dan bagian Tentang pekerja layanan ekstensi.

Perbedaan antara skrip latar belakang dan pekerja layanan ekstensi

Dalam beberapa konteks, Anda akan melihat service worker ekstensi yang disebut 'skrip latar belakang'. Meskipun pekerja layanan ekstensi berjalan di latar belakang, memanggilnya skrip latar belakang agak menyesatkan karena menyiratkan kemampuan yang identik. Perbedaannya dijelaskan di bawah ini.

Perubahan dari halaman latar belakang

Service worker memiliki sejumlah perbedaan dengan halaman latar belakang.

  • API ini berfungsi di luar thread utama, yang berarti tidak mengganggu konten ekstensi.
  • Mereka memiliki kemampuan khusus seperti mencegat peristiwa pengambilan data di origin ekstensi, seperti dari pop-up toolbar.
  • Klien dapat berkomunikasi dan berinteraksi dengan konteks lain melalui antarmuka Klien.

Perubahan yang perlu Anda lakukan

Anda harus melakukan beberapa penyesuaian kode untuk memperhitungkan perbedaan antara cara fungsi skrip latar belakang dan pekerja layanan. Sebagai permulaan, cara service worker ditentukan dalam file manifes berbeda dengan cara skrip latar belakang ditentukan. Selain itu:

  • Karena tidak dapat mengakses DOM atau antarmuka window, Anda harus memindahkan panggilan tersebut ke API lain atau ke dalam dokumen di luar layar.
  • Pemroses peristiwa tidak boleh didaftarkan sebagai respons terhadap promise yang ditampilkan atau di dalam callback peristiwa.
  • Karena tidak kompatibel dengan XMLHttpRequest(), Anda harus mengganti panggilan ke antarmuka ini dengan panggilan ke fetch().
  • Karena dihentikan saat tidak digunakan, Anda harus mempertahankan status aplikasi, bukan mengandalkan variabel global. Menghentikan pekerja layanan juga dapat mengakhiri timer sebelum selesai. Anda harus menggantinya dengan alarm.

Halaman ini menjelaskan tugas-tugas tersebut secara mendetail.

Perbarui kolom "background" di manifes

Di Manifest V3, halaman latar belakang digantikan oleh service worker. Perubahan manifes tercantum di bawah.

  • Ganti "background.scripts" dengan "background.service_worker" di manifest.json. Perhatikan bahwa kolom "service_worker" menggunakan string, bukan array string.
  • Hapus "background.persistent" dari manifest.json.
Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
Manifes V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

Kolom "service_worker" menggunakan satu string. Anda hanya memerlukan kolom "type" jika menggunakan modul ES (menggunakan kata kunci import). Nilainya akan selalu "module". Untuk mengetahui informasi selengkapnya, lihat Dasar-dasar pekerja layanan ekstensi

Memindahkan panggilan DOM dan jendela ke dokumen di luar layar

Beberapa ekstensi memerlukan akses ke objek DOM dan jendela tanpa membuka jendela atau tab baru secara visual. Offscreen API mendukung kasus penggunaan ini dengan membuka dan menutup dokumen yang tidak ditampilkan yang dikemas dengan ekstensi, tanpa mengganggu pengalaman pengguna. Kecuali untuk penerusan pesan, dokumen di luar layar tidak berbagi API dengan konteks ekstensi lain, tetapi berfungsi sebagai halaman web lengkap untuk berinteraksi dengan ekstensi.

Untuk menggunakan Offscreen API, buat dokumen offscreen dari service worker.

browser.offscreen.createDocument({
  url: browser.runtime.getURL('offscreen.html'),
  reasons: ['CLIPBOARD'],
  justification: 'testing the offscreen API',
});

Dalam dokumen offscreen, lakukan tindakan apa pun yang sebelumnya akan Anda jalankan dalam skrip latar belakang. Misalnya, Anda dapat menyalin teks yang dipilih di halaman host.

let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');

Berkomunikasi antara dokumen di luar layar dan pekerja layanan ekstensi menggunakan transfer pesan.

Mengonversi localStorage ke jenis lain

Antarmuka Storage platform web (dapat diakses dari window.localStorage) tidak dapat digunakan di service worker. Untuk mengatasinya, lakukan salah satu dari dua hal berikut. Pertama, Anda dapat menggantinya dengan panggilan ke mekanisme penyimpanan lain. Namespace browser.storage.local akan melayani sebagian besar kasus penggunaan, tetapi opsi lain tersedia.

Anda juga dapat memindahkan panggilannya ke dokumen di luar layar. Misalnya, untuk memigrasikan data yang sebelumnya disimpan di localStorage ke mekanisme lain:

  1. Buat dokumen offscreen dengan rutin konversi dan handler runtime.onMessage.
  2. Tambahkan rutin konversi ke dokumen offscreen.
  3. Di pemeriksaan pekerja layanan ekstensi, browser.storage untuk data Anda.
  4. Jika data Anda tidak ditemukan, buat dokumen di luar layar dan panggil runtime.sendMessage() untuk memulai rutin konversi.
  5. Di pengendali runtime.onMessage yang Anda tambahkan ke dokumen offscreen, panggil rutin konversi.

Ada juga beberapa nuansa terkait cara kerja API penyimpanan web di ekstensi. Pelajari lebih lanjut di Penyimpanan dan Cookie.

Mendaftarkan pemroses secara sinkron

Mendaftarkan pemroses secara asinkron (misalnya di dalam promise atau callback) tidak dijamin berfungsi di Manifes V3. Pertimbangkan kode berikut.

browser.storage.local.get(["badgeText"], ({ badgeText }) => {
  browser.browserAction.setBadgeText({ text: badgeText });
  browser.browserAction.onClicked.addListener(handleActionClick);
});

Hal ini berfungsi dengan halaman latar belakang persisten karena halaman terus berjalan dan tidak pernah diinisialisasi ulang. Di Manifest V3, service worker akan diinisialisasi ulang saat peristiwa dikirim. Artinya, saat peristiwa dipicu, pemroses tidak akan terdaftar (karena ditambahkan secara asinkron), dan peristiwa akan terlewat.

Sebagai gantinya, pindahkan pendaftaran pemroses peristiwa ke tingkat teratas skrip Anda. Hal ini memastikan bahwa Chrome akan dapat segera menemukan dan memanggil handler klik tindakan Anda, meskipun ekstensi Anda belum selesai menjalankan logika startup-nya.

browser.action.onClicked.addListener(handleActionClick);

browser.storage.local.get(["badgeText"], ({ badgeText }) => {
  browser.action.setBadgeText({ text: badgeText });
});

Mengganti XMLHttpRequest() dengan fetch() global

XMLHttpRequest() tidak dapat dipanggil dari service worker, ekstensi, atau lainnya. Ganti panggilan dari skrip latar belakang Anda ke XMLHttpRequest() dengan panggilan ke global fetch().

XMLHttpRequest()
const xhr = new XMLHttpRequest();
console.log('UNSENT', xhr.readyState);

xhr.open('GET', '/api', true);
console.log('OPENED', xhr.readyState);

xhr.onload = () => {
    console.log('DONE', xhr.readyState);
};
xhr.send(null);
fetch()
const response = await fetch('https://www.example.com/greeting.json'')
console.log(response.statusText);

Mempertahankan status

Service worker bersifat sementara, yang berarti service worker akan dimulai, dijalankan, dan dihentikan berulang kali selama sesi browser pengguna. Artinya juga bahwa data tidak langsung tersedia di variabel global karena konteks sebelumnya telah dihentikan. Untuk mengatasinya, gunakan API penyimpanan sebagai sumber tepercaya. Contoh akan menunjukkan cara melakukannya.

Contoh berikut menggunakan variabel global untuk menyimpan nama. Di pekerja layanan, variabel ini dapat direset beberapa kali selama sesi browser pengguna.

Skrip latar belakang Manifest V2
let savedName = undefined;

browser.runtime.onMessage.addListener(({ type, name }) => {
  if (type === "set-name") {
    savedName = name;
  }
});

browser.browserAction.onClicked.addListener((tab) => {
  browser.tabs.sendMessage(tab.id, { name: savedName });
});

Untuk Manifes V3, ganti variabel global dengan panggilan ke Storage API.

Service worker Manifest V3
browser.runtime.onMessage.addListener(({ type, name }) => {
  if (type === "set-name") {
    browser.storage.local.set({ name });
  }
});

browser.action.onClicked.addListener(async (tab) => {
  const { name } = await browser.storage.local.get(["name"]);
  browser.tabs.sendMessage(tab.id, { name });
});

Mengonversi timer menjadi alarm

Operasi tertunda atau berkala biasanya menggunakan metode setTimeout() atau setInterval(). Namun, API ini dapat gagal di service worker karena timer dibatalkan setiap kali service worker dihentikan.

Skrip latar belakang Manifest V2
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  browser.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

Sebagai gantinya, gunakan Alarms API. Seperti pemroses lainnya, pemroses alarm harus didaftarkan di tingkat teratas skrip Anda.

Service worker Manifest V3
async function startAlarm(name, duration) {
  await browser.alarms.create(name, { delayInMinutes: 3 });
}

browser.alarms.onAlarm.addListener(() => {
  browser.action.setIcon({
    path: getRandomIconPath(),
  });
});

Memastikan service worker tetap aktif

Service worker menurut definisinya berbasis peristiwa dan akan dihentikan saat tidak ada aktivitas. Dengan begitu, Chrome dapat mengoptimalkan performa dan konsumsi memori ekstensi Anda. Pelajari lebih lanjut di dokumentasi siklus proses service worker kami. Kasus luar biasa mungkin memerlukan langkah-langkah tambahan untuk memastikan service worker tetap aktif lebih lama.

Memastikan service worker tetap aktif hingga operasi yang berjalan lama selesai

Selama operasi service worker yang berjalan lama yang tidak memanggil API ekstensi, service worker dapat dimatikan di tengah operasi. Contohnya mencakup:

  • Permintaan fetch() yang berpotensi memakan waktu lebih dari lima menit (misalnya, download besar pada koneksi yang berpotensi buruk).
  • Penghitungan asinkron yang kompleks dan memerlukan waktu lebih dari 30 detik.

Untuk memperpanjang masa aktif service worker dalam kasus ini, Anda dapat memanggil API ekstensi sepele secara berkala untuk mereset penghitung waktu tunggu. Perlu diketahui bahwa hal ini hanya diperuntukkan bagi kasus-kasus pengecualian dan dalam sebagian besar situasi, biasanya ada cara yang lebih baik dan idiomatis untuk mencapai hasil yang sama.

Contoh berikut menunjukkan fungsi bantuan waitUntil() yang membuat service worker Anda tetap aktif hingga promise tertentu diselesaikan:

async function waitUntil(promise) {
  const keepAlive = setInterval(browser.runtime.getPlatformInfo, 25 * 1000);
  try {
    await promise;
  } finally {
    clearInterval(keepAlive);
  }
}

waitUntil(someExpensiveCalculation());

Memastikan service worker terus aktif

Dalam kasus yang jarang terjadi, masa aktif perlu diperpanjang tanpa batas. Kami telah mengidentifikasi perusahaan dan lembaga pendidikan sebagai kasus penggunaan terbesar, dan kami secara khusus mengizinkannya di sana, tetapi kami tidak mendukungnya secara umum. Dalam keadaan luar biasa ini, agar service worker tetap aktif, Anda dapat memanggil API ekstensi sepele secara berkala. Perlu diperhatikan bahwa rekomendasi ini hanya berlaku untuk ekstensi yang berjalan di perangkat terkelola untuk kasus penggunaan perusahaan atau pendidikan. Hal ini tidak diizinkan dalam kasus lain dan tim ekstensi Chrome berhak mengambil tindakan terhadap ekstensi tersebut pada masa mendatang.

Gunakan cuplikan kode berikut agar service worker Anda tetap aktif:

/**
 * Tracks when a service worker was last alive and extends the service worker
 * lifetime by writing the current time to extension storage every 20 seconds.
 * You should still prepare for unexpected termination - for example, if the
 * extension process crashes or your extension is manually stopped at
 * chrome://serviceworker-internals. 
 */
let heartbeatInterval;

async function runHeartbeat() {
  await browser.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}

/**
 * Starts the heartbeat interval which keeps the service worker alive. Call
 * this sparingly when you are doing work which requires persistence, and call
 * stopHeartbeat once that work is complete.
 */
async function startHeartbeat() {
  // Run the heartbeat once at service worker startup.
  runHeartbeat().then(() => {
    // Then again every 20 seconds.
    heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
  });
}

async function stopHeartbeat() {
  clearInterval(heartbeatInterval);
}

/**
 * Returns the last heartbeat stored in extension storage, or undefined if
 * the heartbeat has never run before.
 */
async function getLastHeartbeat() {
  return (await browser.storage.local.get('last-heartbeat'))['last-heartbeat'];
}