修改網頁

擴充套件最常見的用例之一就是修改網頁。例如,一個擴充套件可能希望更改應用於頁面的樣式、隱藏特定的 DOM 節點或將額外的 DOM 節點注入頁面。

使用 WebExtensions API 可以透過兩種方式實現此目的:

  • 宣告式地:定義一個匹配一組 URL 的模式,並將一組指令碼載入到 URL 與該模式匹配的頁面中。
  • 程式設計式地:使用 JavaScript API,將指令碼載入到由特定標籤頁託管的頁面中。

無論哪種方式,這些指令碼都稱為內容指令碼,並且與構成擴充套件的其他指令碼不同。

  • 它們只能訪問 WebExtension API 的一小部分。
  • 它們可以直接訪問載入它們的網頁。
  • 它們使用訊息傳遞 API 與擴充套件的其餘部分進行通訊。

在本文中,我們將介紹載入指令碼的這兩種方法。

修改匹配 URL 模式的頁面

首先,建立一個名為“modify-page”的新目錄。在該目錄中,建立一個名為“manifest.json”的檔案,內容如下:

json
{
  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",

  "content_scripts": [
    {
      "matches": ["https://mdn.club.tw/*"],
      "js": ["page-eater.js"]
    }
  ]
}

content_scripts 鍵是如何將指令碼載入到匹配 URL 模式的頁面中。在這種情況下,content_scripts 指示瀏覽器將名為“page-eater.js”的指令碼載入到 https://mdn.club.tw/ 下的所有頁面中。

注意:由於 content_scripts"js" 屬性是一個數組,因此您可以使用它將多個指令碼注入到匹配的頁面中。如果這樣做,頁面將共享相同的範圍,就像頁面載入的多個指令碼一樣,並且它們會按照在陣列中列出的順序載入。

注意:content_scripts 鍵還有一個 "css" 屬性,您可以使用它來注入 CSS 樣式表。

接下來,在“modify-page”目錄中建立一個名為“page-eater.js”的檔案,併為其新增以下內容:

js
document.body.textContent = "";

let header = document.createElement("h1");
header.textContent = "This page has been eaten";
document.body.appendChild(header);

現在 安裝擴充套件,然後訪問 https://mdn.club.tw/。頁面應該如下所示:

developer.mozilla.org page "eaten" by the script

以程式設計方式修改頁面

如果您仍然想“吃掉”頁面,但只在使用者請求時才這樣做,該怎麼辦?讓我們更新此示例,以便在使用者單擊上下文選單項時注入內容指令碼。

首先,將“manifest.json”更新為包含以下內容:

json
{
  "manifest_version": 2,
  "name": "modify-page",
  "version": "1.0",

  "permissions": ["activeTab", "contextMenus"],

  "background": {
    "scripts": ["background.js"]
  }
}

在這裡,我們刪除了 content_scripts 鍵,並添加了兩個新鍵:

  • permissions:要將指令碼注入頁面,我們需要修改頁面的許可權。 activeTab 許可權是一種臨時獲取當前活動標籤頁許可權的方法。我們還需要 contextMenus 許可權才能新增上下文選單項。
  • background:我們使用它來載入一個持久的 “後臺指令碼”,名為 background.js,我們將在其中設定上下文選單並注入內容指令碼。

讓我們建立這個檔案。在 modify-page 目錄中建立一個名為 background.js 的新檔案,併為其新增以下內容:

js
browser.contextMenus.create({
  id: "eat-page",
  title: "Eat this page",
});

browser.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "eat-page") {
    browser.tabs.executeScript({
      file: "page-eater.js",
    });
  }
});

在此指令碼中,我們建立了一個 上下文選單項,併為其指定了一個特定的 id 和 title(將在上下文選單中顯示的文字)。然後,我們設定了一個事件監聽器,以便當使用者單擊上下文選單項時,我們檢查它是否是我們 eat-page 項。如果是,我們使用 tabs.executeScript() API 將“page-eater.js”注入當前標籤頁。此 API 可選地接受標籤頁 ID 作為引數:我們省略了標籤頁 ID,這意味著指令碼被注入到當前活動標籤頁中。

此時,擴充套件應該如下所示:

modify-page/
    background.js
    manifest.json
    page-eater.js

現在 重新載入擴充套件,開啟一個頁面(這次是任何頁面),啟用上下文選單,然後選擇“Eat this page”。

Option to eat a page on the context menu

訊息

內容指令碼和後臺指令碼無法直接訪問彼此的狀態。但是,它們可以透過傳送訊息進行通訊。一方設定訊息監聽器,另一方可以向其傳送訊息。下表總結了雙方涉及的 API:

在內容指令碼中 在後臺指令碼中
傳送訊息 browser.runtime.sendMessage() browser.tabs.sendMessage()
接收訊息 browser.runtime.onMessage browser.runtime.onMessage

注意:除了這種傳送一次性訊息的通訊方法外,您還可以使用 基於連線的方法來交換訊息。有關選擇建議,請參閱 選擇一次性訊息還是基於連線的訊息

讓我們更新我們的示例,以展示如何從後臺指令碼傳送訊息。

首先,編輯 background.js,使其包含以下內容:

js
browser.contextMenus.create({
  id: "eat-page",
  title: "Eat this page",
});

function messageTab(tabs) {
  browser.tabs.sendMessage(tabs[0].id, {
    replacement: "Message from the extension!",
  });
}

function onExecuted(result) {
  let querying = browser.tabs.query({
    active: true,
    currentWindow: true,
  });
  querying.then(messageTab);
}

browser.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "eat-page") {
    let executing = browser.tabs.executeScript({
      file: "page-eater.js",
    });
    executing.then(onExecuted);
  }
});

現在,在注入 page-eater.js 後,我們使用 tabs.query() 來獲取當前活動標籤頁,然後使用 tabs.sendMessage() 將訊息傳送到該標籤頁中載入的內容指令碼。訊息的有效負載為 {replacement: "Message from the extension!"}

接下來,將 page-eater.js 更新如下:

js
function eatPageReceiver(request, sender, sendResponse) {
  document.body.textContent = "";
  let header = document.createElement("h1");
  header.textContent = request.replacement;
  document.body.appendChild(header);
}
browser.runtime.onMessage.addListener(eatPageReceiver);

現在,內容指令碼不再僅僅立即“吃掉”頁面,而是使用 runtime.onMessage 監聽訊息。當訊息到達時,內容指令碼執行與之前基本相同的程式碼,只是替換文字取自 request.replacement

由於 tabs.executeScript() 是一個非同步函式,為了確保在 page-eater.js 中新增監聽器後才傳送訊息,我們使用 onExecuted(),它將在 page-eater.js 執行後被呼叫。

注意:Ctrl+Shift+J(macOS 上為 Cmd+Shift+J)或 web-ext run --bc 開啟 瀏覽器控制檯以檢視後臺指令碼中的 console.log

或者,使用 附加元件偵錯程式,它可以讓您設定斷點。目前沒有辦法 直接從 web-ext 啟動附加元件偵錯程式

如果我們想從內容指令碼向後臺頁面傳送訊息,我們將使用 runtime.sendMessage() 而不是 tabs.sendMessage(),例如:

js
browser.runtime.sendMessage({
  title: "from page-eater.js",
});

注意:這些示例都注入了 JavaScript;您也可以使用 tabs.insertCSS() 函式以程式設計方式注入 CSS。

瞭解更多