教學課程,說明擴充功能服務工作者概念
總覽
本教學課程將介紹 Chrome 擴充功能服務工作站。在本教學課程中,您將建立擴充功能,讓使用者能透過萬用盒快速前往 Chrome API 參考頁面。您將學習下列內容:
- 註冊 Service Worker 並匯入模組。
- 對擴充功能 Service Worker 進行偵錯。
- 管理狀態及處理事件。
- 觸發定期事件。
- 與內容指令碼通訊。
事前準備
本指南假設您具備基本的網路開發經驗。如要瞭解擴充功能開發作業,建議您參閱「擴充功能 101」和「Hello World」。
建構擴充功能
請先建立名為 quick-api-reference
的新目錄來儲存擴充功能檔案,或是從 GitHub 範例存放區下載原始碼。
步驟 1:註冊服務工作者
在專案根目錄中建立manifest檔案,然後加入以下程式碼:
manifest.json:
{
"manifest_version": 3,
"name": "Open extension API reference",
"version": "1.0.0",
"icons": {
"16": "images/icon-16.png",
"128": "images/icon-128.png"
},
"background": {
"service_worker": "service-worker.js"
}
}
擴充功能會在資訊清單中註冊服務 worker,而資訊清單只需要單一 JavaScript 檔案。您不需要像在網頁中一樣呼叫 navigator.serviceWorker.register()
。
建立 images
資料夾,然後下載圖示至其中。
請參閱「閱讀時間」教學課程的第一個步驟,進一步瞭解擴充功能的中繼資料和資訊清單中的圖示。
步驟 2:匯入多個服務工作程式模組
我們的服務工作架構會實作兩項功能。為確保維護性,我們會在個別模組中實作各項功能。首先,我們需要在資訊清單中將服務工作者宣告為 ES 模組,這樣才能在服務工作者中匯入模組:
manifest.json:
{
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
}
建立 service-worker.js
檔案並匯入兩個模組:
import './sw-omnibox.js';
import './sw-tips.js';
建立這些檔案,並在每個檔案中新增控制台記錄。
sw-omnibox.js:
console.log("sw-omnibox.js");
sw-tips.js:
console.log("sw-tips.js");
請參閱「匯入指令碼」,瞭解在服務工作者中匯入多個檔案的其他方式。
選用:偵錯服務工作者
我會說明如何找出服務工作單元記錄,並瞭解服務工作單元何時終止。首先,請按照這篇文章的操作說明,載入未解壓縮的擴充功能。
30 秒後會顯示「service Worker (閒置)」,這表示 Service Worker 已終止。按一下「service worker (inactive)」連結即可檢查。下方動畫顯示這項功能。
您是否注意到檢查服務 worker 會喚醒它?在 devtools 中開啟服務工作者,可讓服務工作者保持啟用狀態。如要確保服務工作代碼終止時,擴充功能的運作方式正確無誤,請務必關閉 DevTools。
接下來,請中斷擴充功能,瞭解錯誤所在位置。其中一種方法是在 service-worker.js
檔案中,從 './sw-omnibox.js'
匯入作業中刪除「.js」。Chrome 無法註冊 Service Worker。
返回 chrome://extensions 並重新整理擴充功能。您會看到兩個錯誤:
Service worker registration failed. Status code: 3.
An unknown error occurred when fetching the script.
如要進一步瞭解擴充功能 Service Worker 的偵錯方式,請參閱偵錯擴充功能。
步驟 4:初始化狀態
Chrome 將會視需要關閉不需要的 Service Worker。我們使用 chrome.storage
API 在服務工作者工作階段中保留狀態。為了存取儲存空間,我們必須在資訊清單中要求權限:
manifest.json:
{
...
"permissions": ["storage"],
}
首先,請將預設建議儲存至儲存空間。我們可以監聽 runtime.onInstalled()
事件,在擴充功能首次安裝時初始化狀態:
sw-omnibox.js:
...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
if (reason === 'install') {
chrome.storage.local.set({
apiSuggestions: ['tabs', 'storage', 'scripting']
});
}
});
服務工作站無法直接存取 window 物件,因此無法使用 window.localStorage
來儲存值。此外,服務工作者是短暫的執行環境,會在使用者的瀏覽器工作階段中重複終止,因此無法與全域變數相容。請改用 chrome.storage.local
,因為這項工具會將資料儲存在本機。
請參閱「儲存資料,而非使用全域變數」,瞭解擴充功能服務 worker 的其他儲存空間選項。
步驟 5:註冊活動
所有事件事件監聽器都必須在服務工作者的全域範圍內靜態註冊。換句話說,事件監聽器不應以巢狀結構嵌入非同步函式。這樣一來,Chrome 就能確保在服務工作站重新啟動時,所有事件處理常式都會還原。
在這個範例中,我們將使用 chrome.omnibox
API,但首先必須在資訊清單中宣告萬用搜尋方塊關鍵字觸發條件:
manifest.json:
{
...
"minimum_chrome_version": "102",
"omnibox": {
"keyword": "api"
},
}
接著,請在指令碼的頂層註冊萬用搜尋框事件監聽器。當使用者在網址列中輸入萬用搜尋框關鍵字 (api
) 後接著按下分頁鍵或空格鍵時,Chrome 會根據儲存空間中的關鍵字,顯示建議清單。onInputChanged()
事件會採用目前的使用者輸入內容和 suggestResult
物件,負責填入這些建議。
sw-omnibox.js:
...
const URL_CHROME_EXTENSIONS_DOC =
'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;
// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
await chrome.omnibox.setDefaultSuggestion({
description: 'Enter a Chrome API or choose from past searches'
});
const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
const suggestions = apiSuggestions.map((api) => {
return { content: api, description: `Open chrome.${api} API` };
});
suggest(suggestions);
});
使用者選取建議後,onInputEntered()
就會開啟相應的 Chrome API 參考資料頁面。
sw-omnibox.js:
...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
// Save the latest keyword
updateHistory(input);
});
updateHistory()
函式會採用萬用途徑輸入內容,並將其儲存至 storage.local
。以便之後將最新的搜尋字詞當做網址列建議使用。
sw-omnibox.js:
...
async function updateHistory(input) {
const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
apiSuggestions.unshift(input);
apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
return chrome.storage.local.set({ apiSuggestions });
}
步驟 6:設定週期性活動
setTimeout()
或 setInterval()
方法通常用於執行延遲或週期性工作。不過,這些 API 可能會失敗,因為排程器會在服務 worker 終止時取消計時器。可以改為使用 chrome.alarms
API。
請先在資訊清單中要求 "alarms"
權限。此外,如要從遠端代管位置擷取擴充功能提示,您必須要求主機權限:
manifest.json:
{
...
"permissions": ["storage"],
"permissions": ["storage", "alarms"],
"host_permissions": ["https://extension-tips.glitch.me/*"],
}
擴充功能會擷取所有提示,隨機挑選一個並儲存到儲存空間。我們建立鬧鐘,每天會觸發一次更新小費。關閉 Chrome 後,鬧鐘不會儲存。因此我們必須確認鬧鐘是否存在,如果沒有,再建立。
sw-tips.js:
// Fetch tip & save in storage
const updateTip = async () => {
const response = await fetch('https://extension-tips.glitch.me/tips.json');
const tips = await response.json();
const randomIndex = Math.floor(Math.random() * tips.length);
return chrome.storage.local.set({ tip: tips[randomIndex] });
};
const ALARM_NAME = 'tip';
// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
const alarm = await chrome.alarms.get(ALARM_NAME);
if (typeof alarm === 'undefined') {
chrome.alarms.create(ALARM_NAME, {
delayInMinutes: 1,
periodInMinutes: 1440
});
updateTip();
}
}
createAlarm();
// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);
步驟 7:與其他內容互動
擴充功能會使用內容指令碼來讀取及修改網頁內容。使用者造訪 Chrome API 參考頁面時,擴充功能的內容指令碼會更新該頁面,並顯示當日提示。系統會傳送訊息,向服務工作人員要求當日小費。
首先,請在資訊清單中宣告內容指令碼,然後新增與 Chrome API 參考說明文件相對應的比對模式。
manifest.json:
{
...
"content_scripts": [
{
"matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
"js": ["content.js"]
}
]
}
建立新的內容檔案。以下程式碼會傳送訊息給服務工作者,要求提供提示。接著,新增一個按鈕,開啟含有擴充功能提示的彈出式視窗。這個程式碼會使用新的網路平台 Popover API。
content.js:
(async () => {
// Sends a message to the service worker and receives a tip in response
const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });
const nav = document.querySelector('.upper-tabs > nav');
const tipWidget = createDomElement(`
<button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
<span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
</button>
`);
const popover = createDomElement(
`<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
);
document.body.append(popover);
nav.append(tipWidget);
})();
function createDomElement(html) {
const dom = new DOMParser().parseFromString(html, 'text/html');
return dom.body.firstElementChild;
}
最後一個步驟是將訊息處理常式新增至服務工作站,以便向內容指令碼傳送每日提示的回覆。
sw-tips.js:
...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.greeting === 'tip') {
chrome.storage.local.get('tip').then(sendResponse);
return true;
}
});
測試是否能正常運作
請確認專案的檔案結構如下所示:
在本機載入擴充功能
如要在開發人員模式中載入未封裝的擴充功能,請按照「Hello World」的步驟操作。
開啟參考頁面
- 在瀏覽器網址列中輸入關鍵字「api」。
- 按下「Tab」或「空格」鍵。
- 輸入 API 的全名。
- 或從過往搜尋結果清單中選擇
- 系統會開啟新頁面,並顯示 Chrome API 參考資料頁面。
看起來應該像這樣:
開啟今日提示
按一下導覽列上的「提示」按鈕,即可開啟擴充功能提示。
🎯? 可能的改善項目
請根據您目前學到的內容,嘗試完成下列操作:
- 探索導入萬用搜尋建議的其他方式。
- 建立自訂互動視窗,顯示擴充功能提示。
- 開啟另一個頁面,瞭解 MDN 的 Web Extensions 參考資料 API 頁面。
繼續建構!
恭喜您完成本教學課程 🎉?。如要進一步提陞技能,請完成其他新手教學課程:
擴充功能 | 學習目標 |
---|---|
閱讀時間 | 自動在特定網頁組合中插入元素。 |
分頁管理工具 | 建立可管理瀏覽器分頁的彈出式視窗。 |
專注模式 | 在按下擴充功能動作後,在目前網頁上執行程式碼。 |
繼續探索
如要繼續學習擴充功能服務工作者,建議您參閱下列文章: