[Chrome Extension] Part 3: Message Passing

3 min read

Ở 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ỗiNguyên nhân
Popup click không tác dụngChưa sendMessage
Content script không phản hồiSai tab
State bị resetKhông dùng storage
Background không chạyService 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

Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *