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 設定頁面!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!