## 開始動手
### 最簡單的 manifest.json
```json
{
"manifest_version": 3,
"name": "Extension Lab",
"version": "0.0.1"
}
```
### default popup
用來指定 popup 的 UI
```json
{
"action": {
"default_popup": "index.html"
}
}
```
### 其他可設置的 action
#### default_icon
設定擴充功能按鈕的圖示。可以是一個檔案路徑或物件,像這樣:
```json
{
"default_icon": {
"128": "icon-128.png"
}
}
```
> 要注意的是,這只設定 tabbar 上的按鈕 icon。還要設定`icons` 才能給擴充功能管理頁(`chrome://extensions/`)、通知、權限提示、Chrome Web Store 這些地方作顯示。
#### default_title
當滑鼠懸停在擴充功能按鈕上時,顯示的提示文字(tooltip)。
#### browser_style
控制彈出視窗是否使用瀏覽器的樣式。
### icon 的尺寸
#### 16x16
工具列上的擴充功能按鈕。
#### 32x32
高 DPI 顯示器上的工具列按鈕。
#### 48x48
擴充功能管理頁面的圖示(`chrome://extensions/`)。
#### 128x128
Chrome 應用程式啟動器或 Web Store 的圖示。
> 至少設定 128x128,如果可以還是四個尺寸都設定。另外 chrome://extensions/ 要先 remove 再重 load 才會看到新的 icon。
### Options Page
讓使用者設定擴充功能的行為或偏好。
```json
{
"options_ui": {
"page": "options.html",
"open_in_tab": false
}
}
```
### Side Panel
> 要移除 extension 重新載入。


```json
{
"permissions": ["sidePanel"],
"side_panel": {
"default_path": "sidepanel.html"
}
}
```
> 如果你想用程式打開 Side Panel,可以在擴充功能的背景腳本或 popup 裡呼叫這個 API:
> ```js
> chrome.sidePanel.open({ tabId: YOUR_TAB_ID });
> ```
>
> 或確保在當前分頁中 Side Panel 可用:
> ```js
> chrome.sidePanel.setOptions({
> tabId: YOUR_TAB_ID,
> path: "sidepanel.html",
> enabled: true
> });
> ```
### chrome_url_overrides.newtab
把 Chrome 預設的新分頁改成你的擴充功能頁面。
```json
{
"chrome_url_overrides": {
"newtab": "newtab.html"
}
}
```
### chrome_url_overrides.bookmarks
用你的 UI 替代 Chrome 的書籤管理介面。
```json
{
"chrome_url_overrides": {
"bookmarks": "bookmarks.html"
}
}
```
### chrome_url_overrides.history
自訂 Chrome 的瀏覽歷史頁面。
```json
{
"chrome_url_overrides": {
"history": "history.html"
}
}
```
### chrome_url_overrides.history
自訂 Chrome 的瀏覽歷史頁面。
```json
{
"chrome_url_overrides": {
"bookmarks": "bookmarks.html"
}
}
```
### devtools_page
自訂 Chrome 的瀏覽歷史頁面。
```json
{
"devtools_page": "devtools.html"
}
```
### 通知
```js
chrome.notifications.create({
type: "basic",
iconUrl: "icon.png",
title: "通知標題",
message: "這是通知內容"
})
```
### Content Scripts
這些是在網頁上插入的 UI 元件,例如浮動按鈕、側邊欄等。
```json
{
"content_scripts": [
{
"matches": [
"https://*.example.com/*"
],
"js": [
"content.js"
],
"css": [
"styles.css"
],
"run_at": "document_end",
"all_frames": false
}
]
}
```
解說各欄位:
matches:定義哪些網址會執行這個腳本,支援通配符(*)。
- js:執行的 JavaScript 腳本檔。
- css(選用):要插入的 CSS 樣式表。
- run_at(選用):腳本執行時機(預設是 "document_idle"):
- "document_start":在網頁開始載入時執行(DOM 尚未生成)。
- "document_end":在網頁 DOM 生成後執行。
- "document_idle"(預設):在頁面資源大致載入後執行。
- all_frames(選用):是否要在所有 iframe 裡執行(預設 false)。
注意事項:
1. content_scripts 無法直接存取擴充功能的背景腳本或 popup。如果需要溝通,請使用 `chrome.runtime.sendMessage()` 和 `chrome.runtime.onMessage.addListener()`。
2. content_scripts 只能在符合 matches 條件的網頁執行,無法直接操作擴充功能內部頁面。
3. Chrome 擴充功能只認得 JavaScript,所以 content_scripts 無法直接執行 .ts 檔案,但你可以用 TypeScript 開發,透過編譯產出 JavaScript,最後讓擴充功能執行。
4. content_scripts 中的 CSS 是在網頁 DOM 加載前就會被插入,不受 run_at 的影響。如果你需要延遲插入 CSS(像是在某些互動後才動態加樣式),就不能用 manifest.json。這時要靠你的 content.js 動態加載。
5. 引用的內容必須在本地端,不能是 CDN 版本。
### 除了 content scripts 以外還有什麼 scripts 可以寫?
#### background script
用來處理長時間執行的邏輯,像事件監聽、API 呼叫、狀態管理等。背景腳本可以一直存在(persistent),或在需要時啟動(非 persistent)。
```json
{
"background": {
"service_worker": "service_worker.js"
}
}
```
> 背景腳本不能直接操作網頁 DOM,但可以與 content_scripts 溝通。
#### popup
在 popup.html 裡寫的 script。
它只在 popup 開啟時執行,關閉後就會被卸載。
> 還有諸如 Options Page、Side Panel、Devtools 以及其他各種 chrome_url_overrides 頁面的 scripts
---
### 哪些腳本可以呼叫 `chrome.*` API?
除了 content_scripts 以外,大部分都可以直接呼叫 chrome api(當然是為了安全性考量)。
| **腳本類型** | **可以呼叫 Chrome API?** | **備註** |
|-------------------|-------------------------|-------------------------------|
| `background.js` | ✅ 全部 | 最完整權限,常用來管理邏輯 |
| `service_worker.js`| ✅ 全部 | Manifest V3 的背景腳本 |
| `popup.js` | ✅ 大部分 | 必須在 `manifest.json` 中聲明 |
| `options.js` | ✅ 大部分 | 用於儲存設定、互動 UI |
| `sidepanel.js` | ✅ 大部分 | 側邊欄畫面的互動腳本 |
| `content_scripts` | 🚫 **部分 API** | 只能呼叫少數 API(像 `chrome.storage`),無法直接呼叫背景腳本專屬 API(如 `chrome.tabs`) |
| `newtab.js` | ✅ 大部分 | 新分頁覆蓋的 UI 腳本 |
| **網頁上的 script**| 🚫 無法直接呼叫 | 需要用 `chrome.runtime.sendMessage()` 跟擴充功能溝通 |
---
### Chrome API 分類 & 功能(最常用的 API)
| **API 名稱** | **用途** | **可用腳本類型** |
|------------------------|-------------------------------|-------------------------------|
| `chrome.tabs` | 控制、管理分頁(建立、關閉、切換)| `background.js`, `popup.js` |
| `chrome.runtime` | 擴充功能生命周期、訊息傳遞 | **全部腳本** |
| `chrome.storage` | 擴充功能內部儲存資料 | **全部腳本** |
| `chrome.action` | 控制擴充功能圖示(圖示、badge) | `background.js`, `popup.js` |
| `chrome.scripting` | 在網頁中動態注入腳本和樣式 | `background.js`, `service_worker.js` |
| `chrome.windows` | 控制瀏覽器視窗(建立、管理) | `background.js`, `popup.js` |
| `chrome.bookmarks` | 存取與管理書籤 | `background.js`, `popup.js` |
| `chrome.history` | 存取與管理瀏覽記錄 | `background.js`, `popup.js` |
| `chrome.notifications` | 發送系統通知 | `background.js`, `popup.js` |
| `chrome.contextMenus` | 建立右鍵選單 | `background.js` |
| `chrome.alarms` | 定時執行程式 | `background.js`, `service_worker.js` |
| `chrome.webRequest` | 攔截、修改網路請求 | `background.js`, `service_worker.js`(需要權限) |
| `chrome.devtools` | 自訂 Chrome DevTools 面板 | `devtools.js` |
### hooks
#### chrome.runtime
- onInstalled
- onMessage
- onStartup
當瀏覽器啟動時觸發。
- onSuspend
當擴充功能的背景頁面即將被卸載時觸發。
- onUpdateAvailable
- onConnect
#### chrome.action
- onClicked
chrome.action 主要提供了 onClicked 事件,當使用者點擊擴充功能的圖示時觸發。
### chrome.scripting
chrome.scripting API 允許開發者動態地將 JavaScript 或 CSS 注入到網頁中,這與在 manifest.json 中定義的 content_scripts 功能相似。主要區別在於,chrome.scripting 允許在程式執行期間根據需要動態注入腳本,提供更大的靈活性:
```js
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content.js']
})
})
```
### popup、content_scripts、side pannel 和 chrome_url_overrides 它們的 origin 是不一樣的嗎?他們能共用 cookie / session storage / local storage 嗎?
popup、content_scripts、side panel 和 chrome_url_overrides 的 origin(來源)確實是不一樣的,這會直接影響它們能不能共用像 cookie、localStorage、sessionStorage 這類的瀏覽器儲存。
- Popup / Side Panel / URL Overrides 都是擴充功能自己的網域(`chrome-extension://<extension-id>`),所以彼此之間可以共用 cookie、localStorage、sessionStorage。
- Content Script 是跑在網頁的 context(環境),所以它用的是該網頁自己的 origin。因此:
- Cookie、localStorage、sessionStorage 都是屬於該網頁的(像 google.com 就是 google.com 的儲存)
- 無法直接共用擴充功能自己的儲存
### origin 比較
| 元件 | Origin | 能不能共用 Cookie? | localStorage / sessionStorage 共用? |
|----------------------|-----------------------------------------|--------------------|------------------------------------|
| **Popup(彈出視窗)** | `chrome-extension://<extension-id>` | ✅(擴充功能自己的)| ✅(共用) |
| **Side Panel(側邊欄)**| `chrome-extension://<extension-id>` | ✅(擴充功能自己的)| ✅(共用) |
| **chrome_url_overrides(像新分頁)** | `chrome-extension://<extension-id>` | ✅(擴充功能自己的)| ✅(共用) |
| **Content Script(內容腳本)** | **注入到網頁的 origin**(像 `https://google.com`) | 🌐(看網頁的 cookie) | 🌐(看網頁的儲存) |
### 那要怎麼讓 Content Script 跟擴充功能共用資料?
因為 Content Script 和擴充功能有不同的 origin,所以不能直接共用 localStorage、sessionStorage、cookie。
不過你可以:
#### 靠 `chrome.runtime` API 做溝通橋樑
用訊息傳遞(`chrome.runtime.sendMessage()`)來存取擴充功能儲存
```javascript
chrome.runtime.sendMessage({ action: "getStorageData" }, (response) => {
console.log(response.data)
})
```
```javascript
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "getStorageData") {
const data = localStorage.getItem("myData")
sendResponse({ data })
}
})
```
#### 或用 `chrome.storage` 這個擴充功能 API
```javascript
chrome.storage.local.set({ key: "value" })
chrome.storage.local.get("key", (result) => {
console.log(result.key); // "value"
})
```
> chrome.storage 是即便在 content_scripts 裡都可以使用的。但有一個小小的前提是,擴充功能須要在 manifest.json 中宣告權限(permissions)。
### chrome.storage 的兩種儲存區
- chrome.storage.local:只儲存在本機,速度快、用量大(最大約 10MB,但可在選項中要求無限儲存)。
- chrome.storage.sync:同步到 Google 帳號,在多台裝置間共用,但容量較小(最大約 100KB,並有限制每小時寫入次數)。
### chrome.storage 支援監聽
```js
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === "local" && changes.theme) {
console.log("Theme changed:", changes.theme.newValue)
}
})
```
### 測試 html 注入
```js
const span = document.createElement("span")
span.textContent = "here we go"
span.style.cssText = "color: red; font-size: 20px; font-weight: bold; margin: 10px;"
document.body.appendChild(span)
```
---
## 用 vite 組織程式碼
### 初始化專案
```bash
npm create vite@latest . -- --template vue
npm install
```
### 安裝依賴
```bash
npm install --save jquery # content scripts 可以用到
npm install --save-dev @types/jquery
npm install --save tailwindcss @tailwindcss/vite # 記得在 assets/css/style.css 先 import
npm install --save-dev @crxjs/vite-plugin@beta
```
### 設定範例
#### vite.config.js
```js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from "url"
import tailwindcss from '@tailwindcss/vite'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
tailwindcss(),
crx({ manifest }),
],
resolve: {
alias: [
{ find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) },
]
},
build: {
rollupOptions: {
input: {
popup: "popup.html",
options: "options.html",
},
},
}
})
```
#### tsconfig.json
```json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
"esModuleInterop": true,
"lib": ["ES2020", "DOM"],
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"content.ts",
"service-worker-loader.ts"
],
"exclude": ["node_modules"]
}
```
### 目錄結構
```txt
.
├── README.md
├── content.ts
├── dist
│ ├── assets
│ │ ├── content.ts-DoTJBfw_.js
│ │ ├── content.ts-loader-CD2_Y7Py.js
│ │ └── service-worker-loader.ts-l0sNRNKZ.js
│ ├── icons
│ │ ├── extension_lab_icon_128x128.png
│ │ ├── extension_lab_icon_16x16.png
│ │ ├── extension_lab_icon_32x32.png
│ │ └── extension_lab_icon_48x48.png
│ ├── manifest.json
│ ├── options.html
│ ├── popup.html
│ └── service-worker-loader.js
├── manifest.json
├── options.html
├── package-lock.json
├── package.json
├── popup.html
├── public
│ └── icons
│ ├── extension_lab_icon_128x128.png
│ ├── extension_lab_icon_16x16.png
│ ├── extension_lab_icon_32x32.png
│ └── extension_lab_icon_48x48.png
├── service-worker-loader.ts
├── src
│ ├── assets
│ │ ├── main.ts
│ │ └── style.css
│ ├── components
│ │ └── content.vue
│ └── pages
│ ├── options.vue
│ └── popup.vue
├── tsconfig.json
└── vite.config.js
```