jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Build Chrome Extensions · Part 5 — The Background Service Worker

The event-driven heart of MV3: the lifecycle (install, wake, idle, terminate), why there is no persistent state, alarms vs setTimeout, and registering listeners correctly. With a live lifecycle simulator.

Trong Manifest V2 trang nền luôn chạy. Trong V3 điều đó biến mất — nền là một service worker thức dậy khi có sự kiện, làm việc, rồi bị tắt khi rảnh. Thay đổi duy nhất này phá mọi thói quen của dev V2, và là nguồn của hầu hết lỗi “extension thỉnh thoảng ngừng hoạt động”.

Bấm các sự kiện bên dưới và xem worker thức, rảnh, tắt — để ý bộ đếm trong bộ nhớ reset thế nào:


1. Khai báo worker

{
  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}

"type": "module" cho phép dùng import — rất nên dùng. Có một worker cho cả extension, dùng chung mọi tab và cửa sổ.


2. Vòng đời

install/update ─▶ [WAKE on event] ─▶ run handlers ─▶ idle ~30s ─▶ TERMINATE
                       ▲                                              │
                       └──────────────── next event ─────────────────┘

Chrome tắt worker sau khoảng 30 giây không hoạt động (và reset đồng hồ mỗi sự kiện). Mỗi lần thức là một khởi động lạnh: file chạy lại từ đầu, mọi global khởi tạo lại.

Các sự kiện đánh thức gồm onInstalled, onMessage, action.onClicked, alarms.onAlarm, tabs.onUpdated, webNavigation.*, v.v..


3. Quy tắc vàng: không state thường trú

Vì worker chết, mọi thứ trong biến đều mất.

// ✗ BROKEN — count resets to 0 on every wake
let count = 0;
chrome.action.onClicked.addListener(() => { count++; });

// ✓ CORRECT — storage survives termination
chrome.action.onClicked.addListener(async () => {
  const { count = 0 } = await chrome.storage.local.get("count");
  await chrome.storage.local.set({ count: count + 1 });
});

Coi worker là không trạng thái. Đọc cái cần từ chrome.storage (Phần 7) đầu mỗi handler, ghi lại ở cuối.


4. Đăng ký listener ở cấp cao nhất

Đây là quy tắc thứ hai người ta hay phá. Listener phải được đăng ký đồng bộ, ở cấp cao nhất của file — không phải bên trong promise, timeout, hay async callback.

// ✓ top-level — Chrome re-registers this on every wake before dispatching the event
chrome.runtime.onMessage.addListener(handleMessage);

// ✗ WRONG — by the time this runs, the waking event was already missed
setTimeout(() => {
  chrome.runtime.onMessage.addListener(handleMessage);
}, 1000);

Vì sao? Khi một sự kiện đánh thức worker, Chrome chạy lại file, kỳ vọng listener được đăng ký ngay, rồi mới phát sự kiện. Đăng ký muộn thì bạn lỡ chính sự kiện đã đánh thức bạn.


5. Hẹn giờ: dùng chrome.alarms, không setTimeout

setTimeout/setInterval chết theo worker — một timer 10 phút không bao giờ kích hoạt nếu worker tắt ở giây 30. Dùng chrome.alarms, nó sẽ đánh thức worker giúp bạn:

// create once (e.g. in onInstalled)
chrome.alarms.create("refresh", { periodInMinutes: 15 });

// top-level listener
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name === "refresh") refreshData();
});

Chu kỳ tối thiểu là 30 giây. Với bất cứ thứ gì định kỳ hoặc trễ hơn vài giây, alarms là công cụ tin cậy duy nhất.


6. onInstalled — thiết lập lần đầu

Dùng onInstalled để đặt giá trị storage mặc định, tạo context menu, hoặc mở trang chào mừng:

chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === "install") {
    chrome.storage.local.set({ theme: "dark", count: 0 });
    chrome.tabs.create({ url: "welcome.html" });
  }
  if (reason === "update") {
    // run migrations between versions here
  }
});

7. Gỡ lỗi worker

Trên chrome://extensions bấm “service worker” dưới extension để mở DevTools riêng. Nếu hiện “inactive”, worker đang ngủ — bình thường, không phải lỗi. Kích một sự kiện để đánh thức.


8. Bài tập

1. Bạn lưu token phiên trong let token ở đầu background.js. Đôi khi gọi API lỗi 401. Vì sao?

Lời giải

Worker tắt và token reset về undefined ở lần thức kế. Lưu vào chrome.storage và đọc theo mỗi request.

2. setInterval(poll, 600000) (10 phút) không bao giờ chạy. Sửa đi.

Lời giải

Thay bằng chrome.alarms.create("poll", { periodInMinutes: 10 }) cộng listener onAlarm — worker chết lâu trước 10 phút.

3. Bạn đăng ký onMessage bên trong chrome.storage.local.get(...).then(...). Tin nhắn đôi khi bị rớt. Vì sao?

Lời giải

Listener đăng ký bất đồng bộ, sau khi sự kiện đánh thức đã được phát. Đăng ký đồng bộ ở cấp cao nhất.

Nâng cao:trong trình mô phỏng, bấm một sự kiện, để nó tắt, rồi bấm cái khác và xem bộ đếm reset.


Điểm chính

  • Nền MV3 là service worker — thức theo sự kiện và tắt khi rảnh.
  • Không state thường trú — global reset mỗi lần thức; dùng chrome.storage.
  • Đăng ký listener đồng bộ ở cấp cao nhất.
  • Dùng chrome.alarms, không bao giờ setTimeout, cho việc trễ/định kỳ.
  • Dùng onInstalled cho mặc định và di trú.

Tiếp theo

Phần 6 — Messaging xuyên ngữ cảnh: sendMessage một lần, cổng connect lâu dài, bẫy async return true, phát sóng, và externally_connectable — cách các mảnh cô lập thực sự nói chuyện.