內容指令碼

內容指令碼是在網頁環境下執行的檔案,使用標準文件 物件模型 (DOM),就能讀取瀏覽器造訪網頁的詳細資料, 並將資訊傳遞至父項擴充功能

瞭解內容指令碼功能

內容指令碼可直接存取下列擴充功能 API:

內容指令碼無法直接存取其他 API。但只要與擴充功能的其他部分互傳訊息,就能間接存取這些郵件。

您也可以透過內容指令碼存取擴充功能中的其他檔案,方法是使用 fetch() 等 API。方法是將這些參數宣告為 可在網路上存取的資源。請注意,這麼做也會讓資源 同一個網站上的第一方或第三方指令碼執行

在孤島工作

內容腳本在隔離環境中,可讓內容指令碼修改 JavaScript 環境沒有與網頁或其他擴充功能相衝突內容指令碼

擴充功能可能會在程式碼類似以下範例的網頁中執行。

webPage.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

該擴充功能可能會使用 插入指令碼部分。

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

此後,當使用者按下按鈕時,這兩種快訊都會依序顯示。

插入指令碼

內容指令碼可以靜態方式宣告 動態透過程式插入

透過靜態宣告插入

針對應自動執行的指令碼,在 manifest.json 中使用靜態內容指令碼宣告 刊登在一系列知名網頁上

靜態宣告的指令碼已在資訊清單的 "content_scripts" 鍵下方註冊。 當中可能包含 JavaScript 檔案和/或 CSS 檔案。所有自動執行的內容指令碼都必須指定 比對模式

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

名稱 類型 說明
matches 字串陣列 必要。指定要將此內容指令碼插入哪些網頁。如要進一步瞭解這些字串的語法,請參閱比對模式。 和比對模式和 glob 等工具瞭解如何排除 網址。
css 字串陣列 選用。要插入相符頁面的 CSS 檔案清單。這些 在建構或顯示任何 DOM 之前,會按照它們在這個陣列中的顯示順序插入 網頁。
js 字串陣列 選用。指定要插入到相符網頁的 JavaScript 檔案清單。檔案 會依照此陣列中的顯示順序插入。這份清單中的每個字串都必須包含 擴充功能根目錄中資源的相對路徑。開頭斜線 (`/`) 是 就會自動剪輯。
run_at RunAt 選用。指定何時應將指令碼插入網頁。預設為 document_idle
match_about_blank 布林值 選用。指令碼是否應插入 about:blank 影格中 上層頁框或 Opener 頁框與 matches。預設值為 false。
match_origin_as_fallback 布林值 選用。指令碼是否應在下列 由相符來源建立,但網址或來源不得直接建立 符合模式。包括採用不同配置的影格,例如 about:data:blob:filesystem:。其他參考資訊 插入相關影格
world ExecutionWorld 選用。要在其中執行指令碼的 JavaScript 世界。預設值為 ISOLATED。其他參考資訊 在隔離環境中工作

透過動態宣告插入

當內容指令碼的比對模式相符時,動態內容指令碼就能派上用場 ,或者內容指令碼不應一律插入已知主機的情形。

於 Chrome 96 版推出,動態宣告類似於 Static 宣告 宣告但內容指令碼物件卻是在 Chrome 註冊,使用 chrome.scripting 命名空間建立方法,而非 manifest.json.Scripting API 也允許擴充功能開發人員 改為:

  • 註冊內容指令碼。
  • 取得已註冊的內容指令碼清單。
  • 更新已註冊的內容指令碼清單。
  • 移除已註冊的內容指令碼。

和靜態宣告一樣,動態宣告可包含 JavaScript 檔案和/或 CSS 檔案。

service-worker.js

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

service-worker.js

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

service-worker.js

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

service-worker.js

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

透過程式輔助方式插入

針對需要因應事件或針對特定事件執行的內容指令碼,使用程式輔助插入功能

如要透過程式插入內容指令碼,你的擴充功能必須具備下列使用者的主機權限: 與試圖插入指令碼的網頁主機權限可由以下兩種方式授予: 要求將這些檔案納入擴充功能的資訊清單,或暫時使用 "activeTab"

以下是有效分頁擴充功能的不同版本,

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

內容指令碼可插入為檔案。

content-script.js


document.body.style.backgroundColor = "orange";

service-worker.js:

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

或者,函式主體也可以插入並以內容指令碼的形式執行。

service-worker.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

請注意,插入的函式是 chrome.scripting.executeScript() 呼叫,而非原始函式本身。因此,函式的 主體必須自我遮蔽如果參照函式以外的變數,就會導致 擲回 ReferenceError 的指令碼。

當插入為函式時,您也可以將引數傳遞至函式。

service-worker.js

function injectedFunction(color) {
  document.body.style.backgroundColor = color;
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

排除相符項目和 glob

如要自訂指定網頁比對功能,請在宣告式中加入下列欄位 註冊。

名稱 類型 說明
exclude_matches 字串陣列 選用。排除原本會插入這個內容指令碼的網頁 進入。請參閱比對模式,進一步瞭解 這些字串。
include_globs 字串陣列 選用。套用於 matches後,只納入同時符合以下情況的網址: 跟這顆 glob 的人配對此用意是模擬 @include Greasemonkey 關鍵字。
exclude_globs 字串陣列 選用。matches後套用以排除符合此項目的網址 glob。專用於模擬 @exclude Greasemonkey 關鍵字。

要在網頁中插入內容指令碼時,必須同時符合下列兩項條件:

  • 網址符合任何 matches 模式和任何 include_globs 模式。
  • 網址也不符合 exclude_matchesexclude_globs 模式。 因為 matches 為必要屬性,所以 exclude_matchesinclude_globsexclude_globs 只能用於限制哪些網頁會受到影響

下列擴充功能會將內容指令碼插入 https://www.nytimes.com/health 但不包含在 https://www.nytimes.com/business

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

Glob 屬性遵循不同於比對模式的語法。可接受的 glob 字串是可能包含「萬用字元」的網址星號和問號。星號 (*) 符合任何長度的任何字串,包括空字串,而問號 (?) 則會與 單一字元。

舉例來說,glob https://???.example.com/foo/\* 符合下列任一條件:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

但是並「不」符合下列項目:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

這個擴充功能會將內容指令碼插入 https://www.nytimes.com/arts/index.htmlhttps://www.nytimes.com/jobs/index.htm*,但不包含 https://www.nytimes.com/sports/index.html:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

這個擴充功能會將內容指令碼插入 https://history.nytimes.comhttps://.nytimes.com/history,但不會在 https://science.nytimes.comhttps://www.nytimes.com/science

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

只要納入全部或其中之一,即可設定正確的範圍。

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

執行時間

run_at 欄位可控制在網頁插入 JavaScript 檔案的時機。我們建議的 預設值為 "document_idle"。其他可能情況請參閱 RunAt 類型 輕鬆分配獎金

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
名稱 類型 說明
document_idle 字串 建議採用。盡可能使用 "document_idle"

瀏覽器 選擇在 "document_end" 之間和緊接後立即插入指令碼的時間 window.onload 事件。插入的確切時間點取決於文件的複雜程度和 且能加快網頁載入速度。

內容指令碼 在 "document_idle" 執行不需要監聽 window.onload 事件,這些物件保證會在 DOM 完成後執行。如果 指令碼絕對必須在 window.onload 之後執行,擴充功能可以檢查 onload 已透過 document.readyState 觸發 資源。
document_start 字串 指令碼會插入來自 css 的任何檔案,但在其他 DOM 之前 這個 Pod 可用來執行
document_end 字串 指令碼會在 DOM 完成後立即插入,但在子資源 (例如 已載入圖片及頁框

指定影格

"all_frames" 欄位可讓擴充功能指定是否應使用 JavaScript 和 CSS 檔案 插入符合指定網址規定的所有頁框,或僅插入至 分頁。

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);
名稱 類型 說明
all_frames 布林值 選用。預設值為 false,表示只有頂端影格

如果指定 true,系統會插入所有頁框,即使 並非分頁最頂端的頁框每個頁框都會個別檢查網址 Google Cloud 就是最佳選擇如果不符合網址規定,則不會插入子頁框中。

擴充功能可能需要在與比對相符的頁框中執行指令碼 但本身卻不匹配這種情況常發生在 適用於含有相符頁框,但網址未 本身符合指令碼的指定模式

例如,當擴充功能想要在頁框中插入含有 有 about:data:blob:filesystem: 配置。在這種情況下, 網址不會與內容指令碼的格式不符 (若為 about:data:,不在網址中加入上層網址或來源 例如 about:blankdata:text/html,<html>Hello, World!</html>)。 不過,這些影格仍然可以與製作影格建立關聯。

為了插入這些頁框,擴充功能可以指定 "match_origin_as_fallback" 屬性中,位於 資訊清單。

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

如果指定 且設為 true,Chrome 就會查詢 影格的啟動者來判斷影格是否相符,而非 頁框本身的網址請注意,這個值可能與 目標影格的 origin (例如data: 個網址的來源為空值)。

影格的啟動者是指建立或瀏覽目標的影格 相框。雖然這通常是直接父項或開放原始碼,但也可能不是 ( 頁框瀏覽 iframe 內 iframe 的情況)。

這麼做會比較發起人框架的來源,因此發起人框架 可能位於該來源的任何路徑。為了清楚告知使用者這項行為 需要任何使用 "match_origin_as_fallback" 指定的內容指令碼 設為 true 即可同時指定 * 的路徑。

如果同時指定 "match_origin_as_fallback""match_about_blank" "match_origin_as_fallback"擁有優先權。

與嵌入頁面的通訊

雖然內容指令碼和代管這些指令碼的網頁是隔離的 存取該頁面的 DOM 存取權如果網頁希望與 內容指令碼或透過內容指令碼使用擴充功能,都必須透過共用 DOM 進行。

您可以使用 window.postMessage() 完成範例:

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

example.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

非擴充功能網頁 example.html 會張貼訊息。系統已攔截此郵件, ,然後發布至擴充功能程序。這麼一來 建立一條與延伸程序的溝通管道。反向 類似的運作原理

存取擴充功能檔案

如要從內容指令碼存取擴充功能檔案,請呼叫 chrome.runtime.getURL() 可取得擴充功能素材資源的絕對網址,如以下範例 (content.js) 所示:

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

如要在 CSS 檔案中使用字型或圖片,您可以使用 @@extension_id 建構網址,如以下範例 (content.css) 所示:

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

manifest.json 檔案中,所有資產都必須宣告為可透過網路存取的資源

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

保障資料安全

隔離環境可提供多一層保護,但使用內容指令碼 安全漏洞和網頁中的安全漏洞如果內容指令碼收到來自 例如呼叫 fetch() 時,請小心篩選內容 跨網站指令碼攻擊攻擊,才能將其插入只能透過 HTTPS 通訊 避免&quot;man-in-the-middle&quot;攻擊。

請務必濾除惡意網頁。舉例來說,下列模式屬於危險模式, 不允許在 Manifest V3 中發布:

錯誤做法

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
錯誤做法

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

請改為使用不會執行指令碼的更安全的 API:

正確做法

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
正確做法

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);