Ở phần 2, chúng ta đã biết cách chạy JavaScript trên mọi trang web bằng Content Script.
Tuy nhiên, đến đây bạn sẽ gặp một câu hỏi rất lớn:
- ❓Làm sao popup điều khiển trang web?
- ❓Làm sao lưu trạng thái extension?
- ❓Vì sao content script không dùng được chrome.storage?
👉 Tất cả đều xoay quanh một khái niệm cốt lõi: Message Passing.
Mục tiêu của Phần 3
Sau bài này, bạn sẽ:
- Hiểu rõ vai trò của từng thành phần
- Biết từng thành phần được làm gì, không được làm gì
- Giao tiếp giữa:
- Popup ⇄ Content Script
- Content Script ⇄ Background
- Có extension hoàn chỉnh với:
- Popup bật / tắt chức năng
- Content script thay đổi hành vi
- Background lưu trạng thái
Kiến trúc chuẩn của Chrome Extension
Chrome Extension gồm 3 thế giới tách biệt:
Popup ⇄ Background Service Worker
|
Content Script ⇄ Web Page
Trên Google Chrome:
- ❌ Các thành phần không gọi trực tiếp nhau
- ✅ Chỉ giao tiếp bằng message passing
Vai trò của từng thành phần
Popup
- UI cho người dùng
- Nhận thao tác click
- Gửi message
Content Script
- Can thiệp DOM trang web
- Nhận lệnh từ popup / background
- Không lưu state lâu dài
Background Service Worker
- Chạy ngầm
- Lưu state
- Có quyền cao (
storage,tabs)
👉 Background = não bộ của extension
Demo
Chức năng
- Popup có nút ON / OFF
- Khi ON → content script hiển thị badge trên trang
- Khi OFF → badge biến mất
- Trạng thái được lưu lại
Cấu trúc thư mục
message-passing-extension/
├── manifest.json
├── background.js
├── content.js
├── popup.html
└── popup.js
manifest.json (Manifest V3)
{
"manifest_version": 3,
"name": "Message Passing Demo",
"version": "1.0",
"description": "Demo popup giao tiếp với content script",
"permissions": ["storage", "tabs"],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}
]
}
Background – Trung tâm điều phối
background.js
let isEnabled = false;
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'TOGGLE_EXTENSION') {
isEnabled = message.enabled;
chrome.storage.local.set({ isEnabled });
chrome.tabs.query({}, (tabs) => {
tabs.forEach((tab) => {
chrome.tabs.sendMessage(tab.id, {
type: 'EXTENSION_STATUS',
enabled: isEnabled
});
});
});
}
});
📌 Background:
- Nhận lệnh từ popup
- Lưu state
- Broadcast cho tất cả tab
Popup – Gửi lệnh điều khiển
popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<style>
body { font-family: sans-serif; padding: 12px; }
button { width: 100%; padding: 8px; }
</style>
</head>
<body>
<button id="toggle">Loading...</button>
<script src="popup.js"></script>
</body>
</html>
popup.js
const btn = document.getElementById('toggle');
chrome.storage.local.get('isEnabled', (data) => {
const enabled = data.isEnabled ?? false;
updateButton(enabled);
});
btn.addEventListener('click', () => {
chrome.storage.local.get('isEnabled', (data) => {
const newValue = !(data.isEnabled ?? false);
chrome.runtime.sendMessage({
type: 'TOGGLE_EXTENSION',
enabled: newValue
});
updateButton(newValue);
});
});
function updateButton(enabled) {
btn.textContent = enabled ? 'Disable Extension' : 'Enable Extension';
}
Content Script – Nhận lệnh & thao tác DOM
content.js
const ID = 'message-passing-badge';
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'EXTENSION_STATUS') {
message.enabled ? showBadge() : hideBadge();
}
});
chrome.storage.local.get('isEnabled', (data) => {
if (data.isEnabled) showBadge();
});
function showBadge() {
if (document.getElementById(ID)) return;
const badge = document.createElement('div');
badge.id = ID;
badge.innerText = 'Extension ON';
Object.assign(badge.style, {
position: 'fixed',
bottom: '16px',
right: '16px',
padding: '6px 10px',
background: '#2563eb',
color: '#fff',
fontSize: '12px',
borderRadius: '6px',
zIndex: 9999
});
document.body.appendChild(badge);
}
function hideBadge() {
document.getElementById(ID)?.remove();
}
Luồng message thực tế
User click popup
↓
Popup → chrome.runtime.sendMessage
↓
Background cập nhật state
↓
Background → chrome.tabs.sendMessage
↓
Content Script cập nhật DOM
Những lỗi rất hay gặp
| Lỗi | Nguyên nhân |
|---|---|
| Popup click không tác dụng | Chưa sendMessage |
| Content script không phản hồi | Sai tab |
| State bị reset | Không dùng storage |
| Background không chạy | Service worker bị sleep |
Best Practices
- Background giữ state
- Popup chỉ làm UI
- Content script chỉ xử lý DOM
- Message payload rõ ràng (
type) - Luôn check inject trùng
Tóm tắt Phần 3
- Message passing là xương sống của Chrome Extension
- Không component nào “toàn năng”
- Background là trung tâm điều phối
- Extension thực tế luôn cần giao tiếp
