後臺指令碼
後臺指令碼或後臺頁面允許你監控並響應瀏覽器中的事件,例如導航到新頁面、刪除書籤或關閉標籤頁。
後臺指令碼或頁面分為:
- 持久型 - 擴充套件啟動時載入,擴充套件停用或解除安裝時解除安裝。
- 非持久型(也稱為事件頁面) - 僅在需要響應事件時載入,空閒時解除安裝。然而,後臺頁面在所有可見檢視和訊息埠關閉之前不會解除安裝。開啟檢視不會導致後臺頁面載入,但會阻止其關閉。
注意:在 Firefox 中,如果擴充套件程序崩潰
- 崩潰時執行的持久型後臺指令碼會自動重新載入。
- 崩潰時執行的非持久型後臺指令碼(也稱為“事件頁面”)不會重新載入。但是,當 Firefox 呼叫其 WebExtensions API 事件監聽器之一時,它們會自動重新啟動。
- 崩潰時載入在標籤頁中的擴充套件頁面不會自動恢復。每個標籤頁中的警告訊息會通知使用者頁面已崩潰,並允許使用者關閉或恢復標籤頁。
你可以透過開啟新標籤頁並導航到 about:crashextensions來測試此情況,這將靜默觸發擴充套件程序崩潰。
在 Manifest V2 中,後臺指令碼或頁面可以是持久型或非持久型。建議使用非持久型後臺指令碼,因為它們可以減少擴充套件的資源消耗。在 Manifest V3 中,只支援非持久型後臺指令碼或頁面。
如果你在 Manifest V2 中有持久型後臺指令碼或頁面,並希望為擴充套件遷移到 Manifest V3 做準備,請參閱轉換為非持久型,其中提供了將指令碼或頁面轉換為非持久型模型的建議。
後臺指令碼環境
DOM API
後臺指令碼在名為後臺頁面的特殊頁面的上下文中執行。這為它們提供了 window 全域性物件,以及該物件提供的所有標準 DOM API。
WebExtension API
只要擴充套件具有必要的許可權,後臺指令碼就可以使用任何WebExtension API。
跨域訪問
後臺指令碼可以向其擁有主機許可權的主機發出 XHR 請求。
Web 內容
後臺指令碼無法直接訪問網頁。但是,它們可以將內容指令碼載入到網頁中,並使用訊息傳遞 API 與這些內容指令碼通訊。
內容安全策略
透過內容安全策略,後臺指令碼被限制執行某些潛在危險的操作,例如使用 eval()。
有關詳細資訊,請參閱內容安全策略。
實現後臺指令碼
本節描述瞭如何實現非持久型後臺指令碼。
指定後臺指令碼
在你的擴充套件中,如果需要,可以使用 manifest.json 中的 "background" 鍵包含一個或多個後臺指令碼。對於 Manifest V2 擴充套件,persistent 屬性必須為 false 才能建立非持久型指令碼。對於 Manifest V3 擴充套件,可以省略該屬性或必須將其設定為 false,因為 Manifest V3 中的指令碼始終是非持久型的。包含 "type": "module" 會將後臺指令碼作為 ES 模組載入。
"background": {
"scripts": ["background-script.js"],
"persistent": false,
"type": "module"
}
這些指令碼在擴充套件的後臺頁面中執行,因此它們在與載入到網頁中的指令碼相同的上下文中執行。
但是,如果後臺頁面中需要特定內容,你可以指定一個。然後,你可以從頁面而不是使用 "scripts" 屬性指定指令碼。在將 "type" 屬性引入 "background" 鍵之前,這是包含 ES 模組的唯一選項。你可以這樣指定後臺頁面
-
manifest.json
json"background": { "page": "background-page.html", "persistent": false } -
background-page.html
html<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <script type="module" src="background-script.js"></script> </head> </html>
你不能同時指定後臺指令碼和後臺頁面。
初始化擴充套件
監聽 runtime.onInstalled 以在安裝時初始化擴充套件。使用此事件來設定狀態或進行一次性初始化。
對於帶有事件頁面的擴充套件,應該在此處使用有狀態 API,例如使用 menus.create 建立的上下文選單。這是因為有狀態 API 不需要每次事件頁面重新載入時都執行;它們只需要在安裝擴充套件時執行。
browser.runtime.onInstalled.addListener(() => {
browser.contextMenus.create({
id: "sampleContextMenu",
title: "Sample Context Menu",
contexts: ["selection"],
});
});
新增監聽器
圍繞擴充套件依賴的事件來構建後臺指令碼。定義相關事件使後臺指令碼能夠保持休眠狀態,直到這些事件觸發,並防止擴充套件錯過重要的觸發器。
監聽器必須從頁面開始處同步註冊。
browser.runtime.onInstalled.addListener(() => {
browser.contextMenus.create({
id: "sampleContextMenu",
title: "Sample Context Menu",
contexts: ["selection"],
});
});
// This will run when a bookmark is created.
browser.bookmarks.onCreated.addListener(() => {
// do something
});
不要非同步註冊監聽器,因為它們將無法正確觸發。因此,不要這樣做
window.onload = () => {
// WARNING! This event is not persisted, and will not restart the event page.
browser.bookmarks.onCreated.addListener(() => {
// do something
});
};
而是這樣做
browser.tabs.onUpdated.addListener(() => {
// This event is run in the top level scope of the event page, and will persist, allowing
// it to restart the event page if necessary.
});
擴充套件可以透過呼叫 removeListener(例如,與 runtime.onMessage 的 removeListener 一起使用)從其後臺指令碼中刪除監聽器。如果某個事件的所有監聽器都被刪除,瀏覽器將不再為該事件載入擴充套件的後臺指令碼。
browser.runtime.onMessage.addListener(
function messageListener(message, sender, sendResponse) {
browser.runtime.onMessage.removeListener(messageListener);
},
);
過濾事件
使用支援事件過濾的 API 將監聽器限制在擴充套件關心的特定情況。如果一個擴充套件正在監聽 tabs.onUpdated,請改為使用帶過濾器的 webNavigation.onCompleted 事件,因為 tabs API 不支援過濾器。
browser.webNavigation.onCompleted.addListener(
() => {
console.log("This is my favorite website!");
},
{ url: [{ urlMatches: "https://www.mozilla.org/" }] },
);
響應監聽器
監聽器的作用是在事件觸發後觸發功能。要響應事件,請在監聽器事件內部構建所需的響應。
當在特定標籤頁或框架的上下文中響應事件時,請使用事件詳細資訊中的 tabId 和 frameId,而不是依賴於“當前標籤頁”。指定目標可確保當“當前標籤頁”在喚醒事件頁面時發生更改時,你的擴充套件不會在錯誤的目標上呼叫擴充套件 API。
例如,runtime.onMessage 可以響應 runtime.sendMessage 呼叫,如下所示
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.data === "setAlarm") {
browser.alarms.create({ delayInMinutes: 5 });
} else if (message.data === "runLogic") {
browser.scripting.executeScript({
target: {
tabId: sender.tab.id,
frameIds: [sender.frameId],
},
files: ["logic.js"],
});
} else if (message.data === "changeColor") {
browser.scripting.executeScript({
target: {
tabId: sender.tab.id,
frameIds: [sender.frameId],
},
func: () => {
document.body.style.backgroundColor = "orange";
},
});
}
});
解除安裝後臺指令碼
資料應定期持久化,以避免在擴充套件崩潰而未收到 runtime.onSuspend 時丟失重要資訊。使用儲存 API 來輔助此操作。
// Or storage.session if the variable does not need to persist pass browser shutdown.
browser.storage.local.set({ variable: variableInformation });
訊息埠不能阻止事件頁面關閉。如果擴充套件使用訊息傳遞,當事件頁面空閒時,埠將關閉。監聽 runtime.Port 的 onDisconnect 允許你發現何時關閉開啟的埠,但是該監聽器受到與 runtime.onSuspend 相同的時間限制。
browser.runtime.onConnect.addListener((port) => {
port.onMessage.addListener((message) => {
if (message === "hello") {
let response = { greeting: "welcome!" };
port.postMessage(response);
} else if (message === "goodbye") {
console.log("Disconnecting port from this end");
port.disconnect();
}
});
port.onDisconnect.addListener(() => {
console.log("Port was disconnected from the other end");
});
});
後臺指令碼在幾秒鐘不活動後解除安裝。但是,如果在後臺指令碼暫停期間另一個事件喚醒了後臺指令碼,則會呼叫 runtime.onSuspendCanceled,並且後臺指令碼會繼續執行。如果需要任何清理,請監聽 runtime.onSuspend。
browser.runtime.onSuspend.addListener(() => {
console.log("Unloading.");
browser.browserAction.setBadgeText({ text: "" });
});
然而,應該優先選擇持久化資料,而不是依賴 runtime.onSuspend。它不允許進行所需的那麼多清理,並且在崩潰的情況下也無濟於事。
轉換為非持久型
如果你有一個持久型後臺指令碼,本節提供了將其轉換為非持久型模型的說明。
更新 manifest.json 檔案
在擴充套件的 manifest.json 檔案中,將 "background" 鍵的 persistent 屬性更改為 false,適用於你的指令碼或頁面。
"background": {
…,
"persistent": false
}
移動事件監聽器
監聽器必須位於頂級,以便在事件觸發時啟用後臺指令碼。註冊的監聽器可能需要重新組織為同步模式並移動到頂級。
browser.runtime.onStartup.addListener(() => {
// run startup function
});
記錄狀態更改
指令碼現在根據需要開啟和關閉。因此,不要依賴全域性變數。
var count = 101;
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message === "count") {
++count;
sendResponse(count);
}
});
而是使用儲存 API 來設定和返回狀態和值
- 使用
storage.session進行記憶體儲存,該儲存在擴充套件或瀏覽器關閉時清除。預設情況下,storage.session僅適用於擴充套件上下文,不適用於內容指令碼。 - 使用
storage.local獲取更大的儲存區域,該區域在瀏覽器和擴充套件重新啟動後仍然存在。
browser.runtime.onMessage.addListener(async (message, sender) => {
if (message === "count") {
let items = await browser.storage.session.get({ myStoredCount: 101 });
let count = items.myStoredCount;
++count;
await browser.storage.session.set({ myStoredCount: count });
return count;
}
});
前面的示例使用 Promise 傳送非同步響應,這在 Chrome 中直到Chrome bug 1185241 解決才支援。一個跨瀏覽器替代方案是返回 true 並使用 sendResponse。
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message === "count") {
browser.storage.session.get({ myStoredCount: 101 }).then(async (items) => {
let count = items.myStoredCount;
++count;
await browser.storage.session.set({ myStoredCount: count });
sendResponse(count);
});
return true;
}
});
將定時器更改為警報
基於 DOM 的定時器,例如 setTimeout(),在事件頁面空閒後不再保持活動狀態。相反,如果你需要定時器來喚醒事件頁面,請使用 alarms API。
browser.alarms.create({ delayInMinutes: 3.0 });
然後新增一個監聽器。
browser.alarms.onAlarm.addListener(() => {
alert("Hello, world!");
});
更新後臺指令碼函式呼叫
擴充套件通常將其主要功能託管在後臺指令碼中。一些擴充套件透過 extension.getBackgroundPage 返回的 window 訪問後臺頁面中定義的函式和變數。該方法在以下情況下返回 null
- 擴充套件頁面是隔離的,例如隱私瀏覽模式或容器標籤頁中的擴充套件頁面。
- 後臺頁面未執行。這在持久型後臺頁面中不常見,但在使用事件頁面時很可能發生,因為事件頁面可能會被暫停。
注意:建議透過 runtime.sendMessage() 或 runtime.connect() 與後臺指令碼通訊來呼叫其功能。本節討論的 getBackgroundPage() 方法不能用於跨瀏覽器擴充套件,因為 Chrome 中的 Manifest V3 擴充套件不能使用後臺或事件頁面。
如果你的擴充套件需要後臺頁面的 window 引用,請使用 runtime.getBackgroundPage 以確保事件頁面正在執行。如果呼叫是可選的(即,僅在事件頁面處於活動狀態時才需要),則使用 extension.getBackgroundPage。
document.getElementById("target").addEventListener("click", async () => {
let backgroundPage = browser.extension.getBackgroundPage();
// Warning: backgroundPage is likely null.
backgroundPage.backgroundFunction();
});
document.getElementById("target").addEventListener("click", async () => {
// runtime.getBackgroundPage() wakes up the event page if it was not running.
let backgroundPage = await browser.runtime.getBackgroundPage();
backgroundPage.backgroundFunction();
});