Content Scripts 網頁 DOM 操作與注入
如果你想寫一個「把網頁上所有價格換算成台幣」的工具,或是像 Grammarly 一樣在輸入框旁邊長出一個檢查按鈕,你就必須使用 Content Scripts。
Content Scripts 是擴充套件中唯一能夠直接讀取和修改使用者當前瀏覽網頁 (DOM) 的部分。
1. 隔離環境 (Isolated World)
在開始寫 Code 之前,你必須先了解一個非常重要的安全機制:Isolated World (隔離環境)。
當你的 Content Script 被注入到網頁(例如 facebook.com)時,它雖然可以讀取和修改 facebook.com 的 DOM 節點(例如新增一個按鈕),但它無法與該網頁原本的 JavaScript 變數互相溝通。
也就是說,如果 facebook.com 原本有定義一個全域變數 window.currentUser,你的 Content Script 是讀不到它的。同樣地,你在 Content Script 宣告的變數,也不會污染到原本網頁的環境。這保證了極高的安全性與穩定性。
2. 在 Manifest 中註冊 Content Scripts
你可以選擇「靜態宣告」或「動態注入」兩種方式來使用 Content Scripts。最常見的是靜態宣告:
在 manifest.json 中加入以下設定:
{
"content_scripts": [
{
"matches": ["https://*.github.com/*", "https://github.com/*"],
"css": ["styles/content.css"],
"js": ["scripts/content.js"],
"run_at": "document_end"
}
]
}
解析:
"matches":這是一個陣列,定義了你的腳本只在哪些網址生效。為了避免浪費效能和資安問題,請盡量精準指定網址,避免使用<all_urls>。"css"/"js":要注入的樣式表與腳本路徑。"run_at":決定腳本什麼時候執行。"document_end"表示在網頁 DOM 解析完畢後執行,這是最安全的選擇,確保你要找的按鈕或文字已經存在於畫面上。
3. 實戰:在 GitHub 頁面注入自訂按鈕
假設我們想在 GitHub 的每個 Repository 頁面上,加上一個我們自己設計的「快速複製 Repo URL」大按鈕。
在 scripts/content.js 中寫入:
// 尋找 GitHub 原本用來放 "Star" 或 "Fork" 按鈕的區域
const actionHeader = document.querySelector('.pagehead-actions');
if (actionHeader) {
// 建立我們自己的 ListItem
const li = document.createElement('li');
// 建立按鈕
const myBtn = document.createElement('button');
myBtn.className = 'btn btn-sm my-custom-btn'; // 套用我們自己的 CSS class,或是借用 GitHub 的 class
myBtn.innerText = '⚡️ 複製 Repo';
// 綁定點擊事件
myBtn.addEventListener('click', () => {
const currentUrl = window.location.href;
navigator.clipboard.writeText(currentUrl).then(() => {
myBtn.innerText = '✅ 已複製!';
setTimeout(() => { myBtn.innerText = '⚡️ 複製 Repo'; }, 2000);
});
});
li.appendChild(myBtn);
// 將按鈕插入到清單的最前面
actionHeader.insertBefore(li, actionHeader.firstChild);
}
搭配 styles/content.css:
.my-custom-btn {
background-color: #8b5cf6 !important;
color: white !important;
border-color: #7c3aed !important;
}
.my-custom-btn:hover {
background-color: #7c3aed !important;
}
就這麼簡單!你已經成功地改變了 GitHub 的外觀與功能。
4. Message Passing:與 Background 溝通
Content Scripts 有一個致命的缺點:它幾乎不能呼叫大部分的 chrome.* API。
例如,Content Script 無法直接打跨網域的 API (會被 CORS 擋下),也無法存取某些需要高權限的 Chrome 核心功能。
這時候,我們就需要請在後台的 Service Worker (background.js) 幫忙了!
在 Content Script (content.js) 發送求救訊號:
// content.js
const currentTitle = document.title;
// 發送訊息給 background.js
chrome.runtime.sendMessage(
{ action: "saveWebPage", title: currentTitle, url: window.location.href },
(response) => {
// 接收 background.js 的回覆
console.log("後台回應:", response.status);
}
);
在 Service Worker (background.js) 接收並處理:
// background.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "saveWebPage") {
// 取得資料
const { title, url } = request;
console.log(`準備將 ${title} 存入資料庫...`);
// 執行跨網域的 API 請求或資料庫寫入 (此處略)
// ...
// 處理完畢,回覆給 content.js
sendResponse({ status: "Success! 資料已儲存。" });
// 如果有非同步的 API 請求,必須 return true 告訴 Chrome 我們會稍後再送出 sendResponse
// return true;
}
});
這就是擴充套件最經典的前後端分離通訊模式。Content Script 是「前端」,負責處理畫面的呈現與點擊事件;Service Worker 則是「後端」,負責處理資料存取與 API 溝通。
下一章,我們將來看看如何設計你的 Popup 小視窗與使用者的 Options 設定頁面!