透過服務工作站處理事件

教學課程,說明擴充功能服務工作者概念

總覽

本教學課程將介紹 Chrome 擴充功能服務工作站。隨心所欲 您會建構一項擴充功能,方便使用者快速前往 Chrome API 參考資料頁面 您將學習下列內容:

  • 註冊 Service Worker 並匯入模組。
  • 對擴充功能服務 worker 進行偵錯。
  • 管理狀態及處理事件。
  • 觸發定期事件。
  • 與內容指令碼通訊。

事前準備

本指南假設您具備基本的網站開發經驗。建議您詳閱 擴充功能 101Hello World, 。

建構擴充功能

請先建立名為 quick-api-reference 的新目錄來存放副檔名檔案,或 從 GitHub 範例存放區下載原始碼。

步驟 1:註冊 Service Worker

在專案的根目錄中建立 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");

如要瞭解在 Service Worker 中匯入多個檔案的其他方法,請參閱匯入指令碼

選用:偵錯服務工作者

我會說明如何找出服務工作單元記錄,並瞭解服務工作單元何時終止。首先,請按照操作說明載入未解壓縮的擴充功能

30 秒後,您會看到「service worker (inactive)」,表示服務工作站已終止。按一下「service worker (inactive)」連結即可檢查。下方動畫展示了這一點。

您發現檢查 Service Worker 將其喚醒嗎?在 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.

如要進一步瞭解擴充功能服務 worker 的偵錯方式,請參閱「偵錯擴充功能」。

步驟 4:初始化狀態

Chrome 將會視需要關閉不需要的服務工作人員。我們會使用 chrome.storage API 保留 Service Worker 工作階段的狀態。為了存取儲存空間,我們必須在資訊清單中要求權限:

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 來儲存值。此外,Service Worker 是短期的執行環境。 而是在使用者的瀏覽器工作階段內反覆終止,因此與應用程式不相容 全域變數請改用 chrome.storage.local,因為這項工具會將資料儲存在本機。

請參閱「儲存資料,而非使用全域變數」,瞭解擴充功能服務 worker 的其他儲存空間選項。

步驟 5:註冊事件

所有事件監聽器都需要以靜態方式在 Service Worker 的全域範圍內註冊。換句話說,事件監聽器不應在非同步函式中巢狀。這樣一來,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;
  }
});

測試是否能正常運作

請確認專案的檔案結構如下所示:

擴充功能資料夾的內容:images 資料夾、manifest.json、service-worker.js、sw-omnibox.js、sw-tips.js 和 content.js

在本機載入擴充功能

如要在開發人員模式下載入未封裝的擴充功能,請按照「Hello World」中的步驟操作。

開啟參考資料頁面

  1. 在瀏覽器網址列中輸入關鍵字「api」。
  2. 按下「Tab」或「空格」鍵。
  3. 輸入 API 的全名。
    • 或從過往搜尋結果清單中選擇
  4. 系統會開啟新頁面,並顯示 Chrome API 參考資料頁面。

看起來應該像這樣:

開啟執行階段 API 參考資料的快速 API 參考資料
Quick API 擴充功能開啟 Runtime API。

開啟今日提示

按一下導覽列上的提示按鈕,開啟擴充功能提示。

開啟每日提示
快速 API 擴充功能,開啟每日提示。
,瞭解如何調查及移除這項存取權。

🎯? 改進空間

請根據您目前學到的內容,嘗試完成下列任一操作:

  • 探索導入萬用搜尋建議的其他方式。
  • 建立自訂對話方塊,用於顯示擴充功能提示。
  • 開啟另一個頁面,瞭解 MDN 的 Web Extensions 參考資料 API 頁面。

繼續建構!

恭喜您完成本教學課程 🎉。請繼續完成其他初學者教學課程,提升技能:

擴充功能 學習目標
閱讀時間 如要在一組特定網頁中自動插入元素,
分頁管理工具 建立可管理瀏覽器分頁的彈出式視窗。
專注模式 點選擴充功能動作後,在目前頁面執行程式碼。

繼續探索

如要延續擴充功能 Service Worker 學習路徑,建議你參閱下列文章: