[入門] Chrome Extension 入門 #1

在過去的將近十年間, 我陸續在不同的公司裡寫了幾十個 Chrome Extension (以下簡稱 ChrExt)。不過, 由於這幾個 ChrExt 都是任務型的專案, 寫好之後就放著了, 而且很少需要修改。正因為如此, 每次我又需要寫 ChExt 時, 幾乎都要重頭開始。

每次都重頭開始, 其實是蠻痛苦的事。所以, 不如我來寫個入門文章, 當做 ChrExt 的懶人包。當我下次又要重頭開始時, 學習曲線可以快速縮短。這個入門文章不只供我自己看, 也在此分享給大家。

ChrExt 既然稱為 Chrome Extension, 當然是因為它只能在 Chrome 瀏覽器中使用, 不能在其它瀏覽器使用; 所以這個限制就是它最大的缺點。這也是為何我只會把寫 ChrExt 當做任務型專案的原因。如果你希望把這種專案當作商業軟體來開發, 並不是不可以, 只是市場會有很大的侷限。

然而, ChrExt 有它的迷人之處。例如, 當你把 ChrExt 部署到公司內部客戶的瀏覽器上面之後, 他只需要在工具列上按個圖型按鈕, 就能觸發某個動作:

當然, 你也可以把它寫成不需按任何按鈕, 就在背景裡做好其它動作。你也可以限制只在哪個網頁做哪個動作。這就是 ChrExt 最方便也是最迷人之處。

一旦你把 ChrExt 學到純熟之後, 你將會發現它簡直是一把網頁上最便利的瑞士刀。基本上, 你可以使用它來操控幾乎所有網頁上的 DOM 元素, 差不多可以把人家的整個前端改寫了。由於它的功能太過強大, 所以後來 Google 給它加上了很大的限制。不過, 即便如此, 只要你把它使用在正常的、正當的、光明的方面, 它還是極為強大而且方便的。

Chrome Extension 是什麼?

基本上, ChrExt 的組成就像是一個 SPA 專案, 由幾個 HTML、CSS、JavaScript 、JSON和圖形等等檔案所組成:

除了 JavaScript 原本就提供的功能外, Chrome 也提供了一組 API 供你使用。不過, 由於這組 API 過於龐大, 我在這篇入門文章裡不會做詳細介紹。讀者們可以自行前往瀏覽。

在繼續進行之前, 有幾個基本觀念先說明一下:

  • 在 ChrExt 中, 如果我們想要在 Chrome 的工具列中出現按鈕以進行什麼動作, 我們會把執行的單位稱為某個 "Action"。最常使用的 Action 是所謂的 "Browser Action"。我們可以把 "Browser Action" 當做一個元件, 定義在 menifest.json 檔案中, 決定在網址列(Omnibox)右邊的工具列要使用哪一個專案按鈕(見最上方圖片), 以及按下那個按鈕之後要執行哪個檔案:
  • 此外, 還有一種 "Page Action", 其用法和 "Browser Action" 很類似, 但是略為複雜, 使用時機也不同。但是相對的, 如果我們不需要在 Chrome 的工具列中出現按鈕, 就不需要定義 browser_action 和 page_action。
  • 當你的 ChrExt 在 Chrome 瀏覽器中被載入時, 在這個 ChrExt 裡面定義的 "Background" 就會自動執行。同樣的, 其動作也是在 menifest.json 檔案裡定義:
  • 另一種可以自動執行的稱為 "content_scripts"。顧名思義, 這種 scripts 會在目標網頁上執行。有趣的是, "background" 和 "content_scripts" 並不是互斥的; 它們可以同時存在, 也可以只存在一個; 視需要而定。但是不管怎樣, 在這類 scripts 中定義的動作, 不需要按下專案按鈕就會自動執行。至於詳細做法, 將在下面說明。

建立專案目錄

要建立一個 ChrExt, 我們首先必須建立一個專案目錄。所謂的「專案目錄」其實只是一個普通的資料夾, 隨便你要放在什麼地方(例如桌面)。這個專案目錄也是這個 ChtExt 的包裝主體。

現在假設我們在桌面上建立一個叫做「SampleChrExt」的子目錄好了。在這個子目錄裡面, 至少必須有以下幾個檔案存在:

  • manifest.json
  • background.js 或 content_scripts.js (或二者)

其中, manifest.json 這個檔名不能隨便改。它是整個 ChrExt 包裝裡唯一必須使用固定名稱的檔案, 而且必須放置在目錄中的最外層。其餘檔案 (例如 background.js) 則可以取作其它名字。不過為了後續維護方便, 除非真的有必要, 否則不建議你把這些檔案改名。

此外, 我們其實可以在這個子目錄中放置任何其它檔案, 視需要而定。

manifest.json 是一個標準的 JSON 檔案, 其內容長得像如下的樣子:

{
  // 以下三行不能省略
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.1",

  // 以下四項最好不要省略
  "action": {...},
  "default_locale": "en",
  "description": "A plain text description",
  "icons": {...},

  // 以下諸項可以省略 (視需要)
  "author": "developer@example.com",  
  "automation": {...},
  "background": {...},
  "chrome_settings_overrides": {...},
  "chrome_url_overrides": {...},
  "commands": {...},
  "content_scripts": [{...}],
  "content_security_policy": {...},
  "cross_origin_embedder_policy": {...},
  "cross_origin_opener_policy": {...},
  "declarative_net_request": {...},
  "devtools_page": "devtools.html",
  "event_rules": [{...}],
  "export": {...},
  "externally_connectable": {...},
  "file_browser_handlers": [...],
  "file_system_provider_capabilities": {...},
  "homepage_url": "https://path/to/homepage",
  "host_permissions": [...],
  "import": [{...}],
  "incognito": "spanning, split, or not_allowed",
  "input_components": [{...}],
  "key": "publicKey",
  "minimum_chrome_version": "107",
  "nacl_modules": [...],
  "oauth2": {...},
  "offline_enabled": true,
  "omnibox": {...},
  "optional_host_permissions": ["..."],
  "optional_permissions": ["..."],
  "options_page": "options.html",
  "options_ui": {...},
  "permissions": ["..."],
  "replacement_web_app": "https://example.com",
  "requirements": {...},
  "sandbox": {...},
  "short_name": "Short Name",
  "storage": {...},
  "tts_engine": {...},
  "update_url": "https://path/to/updateInfo.xml",
  "version_name": "1.0 beta",
  "web_accessible_resources": [...]
}

我們通常不會無中生有地自己去建立一個新的 manifest.json (但不是不可以); 我們一般都拿既有的 manifest.json 來改。在 Chrome 的 Sample Extensions 網頁裡有一些範例, 其中每一個 Sample 裡一定有一個 manifest.json, 可以拿來參考。

在這個 Sample Extensions 網頁裡, 你可以看到很多不同的 ChrExt 應用方式。基本上, 光是參考這些範例的寫法, 你就能夠學會 ChrExt 的精髓了。不過, 如果你比較懶的話, 請繼續往下讀, 可以再省下你一點時間。

值得注意的是, 網路上 (包括上述網站裡) 有些 sample 可能是較舊的版本。如果你看到專案裡 manifest 裡宣告的 "manifest_version" 是 2 而不是 3, 那麼請特別注意這兩個版本裡有些寫法是不相容的。在本系列文章裡將會大部份採用第 3 版的寫法。

開始動手

我們可以從 Sample Extensions 網頁找一個最簡單的 ChrExt 來參考: Page Redder。它只有兩個檔案: manifest.json 和 background.js。以下我們就使用這個最簡單的範例來示範一遍從如何建立到執行 ChrExt 的過程。

它的 manifest.json 這個檔案我們上面已經介紹過了。它是 ChrExt 的核心, 是整個擴充功能的主控檔:

{
  "name": "Page Redder",
  "action": {},
  "manifest_version": 3,
  "version": "0.1",
  "description": "Turns the page red when you click the icon",
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  }
}

其中, "permissions" 區段中的 "activeTab" 這一項指的是這個 ChrExt 只能有存取目前分頁(Active Tab)的能力。在上面範例中, 項目只有兩個; 但實際上項目可以不只兩個; 例如:

"permissions": [
	"tts",
    "tabs",
    "storage",
    "clipboardRead",
    "clipboardWrite"
]

如果你對這個主題有興趣, 可以參考 API 的相關說明頁。可惜這些 API 的網頁都沒有中文版本, 只有英文版。限於篇幅, 在這裡就不一一介紹了。

接著, 我們來看 "background" 這一段。

這裡的 "background" 指的是「在背景執行」的意思。標注為 "service_worker": "background.js", 指的是一旦這個 ChrExt 啟用後, 這個 "background.js" 就會在背景中自動執行。

"background.js" 的內容很簡單:

function reddenPage() {
  document.body.style.backgroundColor = 'red';
}

chrome.action.onClicked.addListener((tab) => {
  if(!tab.url.includes("chrome://")) {
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      function: reddenPage
    });
  }
});

當這個 ChrExt 一啟用的時候, 這個檔案就會執行一次。它先把專案圖示按鈕加上一個 onClicked 的 listener; 一旦這個按鈕被按下, 只要當前分頁不是 Chrome 設定頁, 它就會把當前分頁的背景色設定為紅色。

專案布署

現在, 請在你的桌面中建立一個新的資料夾, 命名為 Page Redder:

接著, 把剛才範例頁中兩個檔案拷貝過去:

當然, 如果你直接把範例程式直接拷貝過去也是可以的。

到這裡, 我們的預備動作已經完成了。不過, 在我們正式可以使用這個 ChrExt 之前, 我們必須開啟 Chrome 的開發人員模式。請從 Chrome 選單裡選擇「擴充功能」:

在畫面右上角, 把「開發人員模式」核選方塊上打勾。

接著, 在畫面上方, 按下「載入未封裝擴充功能」, 然後選擇桌面上的 "Page Redder" 資料夾。

載入後, 確定這個擴充功能已啟用:

以上是舊版 Chrome 的畫面, 新版畫面有些不同, 加入了一些其它的選項。

請特別注意, 建議你將「允許在無痕模式中執行」這個項目打勾, 否則你的 ChrExt 在無痕模式中不會生效。然後, 請務必把「收集錯誤資訊」取消打勾。如果這個項目打勾, 一旦你的程式出現例外狀況時, Chrome 可能會把當下的畫面截圖傳回去, 這可能會造成貴公司機密或者個資外洩的問題。

如果你的 ChrExt 載入成功, 你應該在網址列右邊看到一個通用型的擴充圖示:

離開 "chrome://extensions/", 開啟任何一個你熟悉的網頁, 然後按下剛才那個按鈕, 看看發生了什麼。如果畫面上沒有任何變化, 請試試其它網頁。

你可以自訂自己的圖示。同樣是寫在 manifest.json 裡, 寫法如下:

"icons": {
  "16": "logo.png",
  "48": "logo.png",
  "128": "logo.png",
  "256": "logo.png"
}

建議你使用 256 X 256 pixel 以上的正方形圖片。

現在我們回頭來說明 "background.js" (請在編輯器裡打開這個檔案)。前面提到, 這個程式會在 ChrExt 啟用時執行一次。在這裡, "background.js" 程式裡只會執行一個指令, 也就是 "chrome.browserAction.onClicked.addListener()" 這個動作; 前面已經介紹過, 它的目的就是為剛才講到的擴充圖示加上一個 onClicked 的 Listener。意思是, 每當使用者按下那個專案按鈕時, 就觸發包在裡面的那個匿名的 function(), 它會執行 document.body.style.backgroundColor="red" 這段指令, 把目前分頁的背景色設為紅色。不過有些網頁可能整個把背景遮住, 使你看不到效果; 並不是指令有誤。

注意事項

前面提到, ChrExt 只能在 Chrome 使用。我想, 如果你這篇文章能夠看到這裡, 你應該已經很清楚這一點了。不過, 它還有另一個問題。

當你把你的 ChrExt 寫好, 若依正常程序, 你可以把它封裝, 然後發行到「Chrome 線上應用程式商店」上面去。但是, 一旦你公開發行之後, 你寫的 ChrExt 程式基本上就等於是供大家欣賞了。如果你好奇的話, 你的機器上所有已安裝的擴充套件其實通通都放在 C:\Documents and Settings\username\Local Settings\Application Data\Google\Chrome\User Data\Default\Extensions 子目錄裡面。

所以我一開始不是說我寫的 ChrExt 全部都是任務型的工具嗎? 意思是說我所寫的都是只有公司會用到的工具, 裡面有很多公司機密, 而我一點都不想讓公司以外的人知道; 而且我很確定別人也不大會有興趣知道這些商業邏輯 (當然, 商業間諜例外)。

在這種前題之下, 我就更不可能把這類任務型工具「發行」出去了。

此外, 如果你打算修改你的 ChrExt, 原則上你可以直接修改專案子目錄裡面的任何檔案; 改完後存檔。然後, 回到 Chrome 擴充功能頁, 按「重新載入」即可。

至於開發 ChrExt 的工具, 我個人推薦使用 Sublime Text。你可以一次把最常用的 json、js 和 html 檔案通通載入, 而且, 當你下次再開啟時, 那些檔案都會繼續開啟著 (即使你尚未存檔)。

此外, VS Code 也是個不錯的工具。

Menifest 版本

前面提到, ChrExt 有版本區分, 最新的版本是 3。版本 2 的寫法有些將在 2023 年不再支援, 所以如果你在網路上找到的範例程式是版本 2, 建議盡量將它改成版本 3 的寫法。讀者們可以自行參考 Manifest V3 migration checklist 一文。

參考:


Dev 2Share @ 點部落