Chrome Extension - 在 Background 中判斷 Popup 是否開啟中,並傳遞訊息

 在 Chrome 擴充功能中,background.js 無法直接「判斷」popup.html 是否開啟中。popup.html 是在用戶點擊擴充功能圖標時才載入和執行的,關閉彈出視窗時也會卸載。

正確的做法是,當 popup.html 載入時,它應該主動向 background.js 發送一個訊息來表明自己已準備好。然後 background.js 就可以利用這個連接來傳送訊息。

這通常通過 Chrome 的訊息傳遞 API (chrome.runtime.sendMessage 和 chrome.runtime.onMessage 或 chrome.runtime.connect 和 port.postMessage) 來實現。

以下是一個使用 chrome.runtime.connect 和 port 的例子,這種方法更適合在 popup 開啟期間保持一個雙向通訊通道。

manifest.json (確保權限正確)

  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0",
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "background": {
    "service_worker": "background.js"
  },
  "permissions": [
    "activeTab" // 視你的需求添加其他權限
  ]
}
  

background.js (Service Worker)


// 監聽來自其他部分的連接
chrome.runtime.onConnect.addListener(function(port) {
    if (port.name === "popup-channel") {
        console.log("Popup 已連接!");
        popupPort = port; // 儲存這個 port

        // 監聽來自 popup 的訊息
        popupPort.onMessage.addListener(function(message) {
            console.log("收到來自 popup 的訊息:", message);
            if (message.type === "POPUP_READY") {
                // Popup 已準備好,可以回傳訊息給它
                console.log("Popup 說它已經準備好了。");
                // 假設 background 想要傳送一些初始數據給 popup
                popupPort.postMessage({ type: "INITIAL_DATA", data: "Hello from background!" });
            }
            // 處理來自 popup 的其他訊息...
        });

        // 監聽 popup 斷開連接(即 popup 被關閉)
        popupPort.onDisconnect.addListener(function() {
            console.log("Popup 已斷開連接 (關閉)。");
            popupPort = null; // 清除 port 引用
        });
    }
});

// background.js 可以在任何時候向 popup 發送訊息,只要 popupPort 存在
function sendMessageToPopup(message) {
    if (popupPort) {
        console.log("向 popup 發送訊息:", message);
        popupPort.postMessage(message);
    } else {
        console.log("Popup 未開啟,無法傳送訊息。");
    }
}

// 範例:在 background 某些事件發生時向 popup 發送訊息
// 例如,當用戶點擊了擴充功能圖標以外的某個內容菜單項
chrome.contextMenus.create({
    id: "send-message-to-popup",
    title: "傳訊息給 Popup",
    contexts: ["selection"]
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
    if (info.menuItemId === "send-message-to-popup") {
        sendMessageToPopup({ type: "NEW_MESSAGE", content: "這是從 background 發送的新訊息!" });
    }
});

// 你也可以在其他地方調用 sendMessageToPopup,比如定時器或網路請求回調
// setTimeout(() => {
//     sendMessageToPopup({ type: "REMINDER", text: "該喝水了!" });
// }, 5000);
  

popup.js (由 popup.html 載入)

const backgroundPort = chrome.runtime.connect({ name: "popup-channel" });

// 當 popup 載入完成時,向 background 發送訊息表示準備就緒
document.addEventListener('DOMContentLoaded', () => {
    console.log("Popup DOM 已載入。");
    backgroundPort.postMessage({ type: "POPUP_READY" });
});

// 監聽來自 background 的訊息
backgroundPort.onMessage.addListener(function(message) {
    console.log("收到來自 background 的訊息:", message);
    if (message.type === "INITIAL_DATA") {
        document.getElementById('message-display').textContent = message.data;
    } else if (message.type === "NEW_MESSAGE" || message.type === "REMINDER") {
        const messageList = document.getElementById('message-list');
        const listItem = document.createElement('li');
        listItem.textContent = message.content || message.text;
        messageList.appendChild(listItem);
    }
    // 處理其他來自 background 的訊息...
});

// 為了測試,假設 popup.html 裡有一個顯示訊息的元素
// popup.html 應該有類似 <div id="message-display"></div> 和 <ul id="message-list"></ul>
  

popup.html

<html>
<head>
    <title>My Popup</title>
    <style>
        body { width: 300px; padding: 10px; font-family: sans-serif; }
        ul { list-style: none; padding: 0; }
        li { margin-bottom: 5px; background-color: #f0f0f0; padding: 5px; border-radius: 3px; }
    </style>
</head>
<body>
    <h1>Popup 介面</h1>
    <p>初始訊息: <span id="message-display">等待訊息...</span></p>
    <h2>來自 Background 的訊息歷史:</h2>
    <ul id="message-list"></ul>
    <script src="popup.js"></script>
</body>
</html>
  

總結 background 判斷 popup 狀態的邏輯:

  1. popupPort = null 初始狀態:在 background.js 中,維護一個 popupPort 變數,初始為 null

  2. onConnect 偵測:當 popup.js 執行 chrome.runtime.connect() 時,background.js 的 chrome.runtime.onConnect 事件會被觸發。此時,popupPort 會被賦值為該連接的 port 物件。這表示 popup 已開啟。

  3. onDisconnect 偵測:當用戶關閉彈出視窗時,popup 的 port 會自動斷開連接,background.js 的 popupPort.onDisconnect 事件會被觸發。此時將 popupPort 設置回 null

  4. 發送訊息前檢查:當 background.js 想要向 popup 發送訊息時,只需檢查 popupPort 是否為 null。如果不是 null,則 popup 正在運行,可以安全地使用 popupPort.postMessage() 發送訊息。

這樣,background.js 就「知道」popup 是否開啟中,並能在開啟時與之通訊。

留言

這個網誌中的熱門文章

Offscreen Canvas

外部網頁新增 Google 日曆行程(URL模板)

計算 Canvas 文字最大字體