內容指令碼是在網頁環境中執行的檔案,這些指令碼可使用標準文件物件模型 (DOM),讀取瀏覽器造訪的網頁詳細資料、進行變更,並將資訊傳遞至上層擴充功能。
瞭解內容指令碼功能
內容指令碼可直接存取下列擴充功能 API:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
內容指令碼無法直接存取其他 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 框架,其中父項或開啟器框架符合 matches 中宣告的其中一個模式。預設值為 false。 |
match_origin_as_fallback |
布林值 |
選填。指令碼是否應插入由相符來源建立的框架,但這些框架的網址或來源可能不會直接與模式相符。包括具有不同架構的影格,例如 about: 、data: 、blob: 和 filesystem: 。另請參閱「在相關框架中插入」。 |
world |
ExecutionWorld |
選填。指令碼要在其中執行的 JavaScript 世界。預設值為 ISOLATED 。另請參閱「在隔離世界中工作」。 |
使用動態宣告注入
如果內容指令碼的相符模式不明,或不應一律在已知主機上插入內容指令碼,動態內容指令碼就非常實用。
Chrome 96 推出的動態宣告與靜態宣告類似,但內容指令碼物件是使用 chrome.scripting
命名空間中的方法向 Chrome 註冊,而不是在 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"
授予主機權限。
以下是 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_matches
或exclude_globs
模式。 由於matches
屬性為必填,因此exclude_matches
、include_globs
和exclude_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.html
和 https://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.com
和 https://.nytimes.com/history
,但不會插入 https://science.nytimes.com
或 https://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 之後執行,擴充功能可以使用 document.readyState 屬性檢查 onload 是否已觸發。 |
document_start |
字串 | 系統會在 css 中的任何檔案之後,但會在建構任何其他 DOM 或執行任何其他指令碼之前,插入指令碼。 |
document_end |
字串 | 指令碼會在 DOM 完成後立即插入,但會在圖片和影格等子資源載入前插入。 |
指定影格
如果是資訊清單中指定的宣告式內容指令碼,擴充功能可透過 "all_frames"
欄位指定是否要將 JavaScript 和 CSS 檔案插入符合指定網址條件的所有框架,或只插入分頁中的最頂層框架:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
使用 chrome.scripting.registerContentScripts(...)
以程式輔助方式註冊內容指令碼時,可以使用 allFrames
參數指定內容指令碼應插入符合指定網址規定的所有框架,還是只插入分頁中的最上層框架。這項功能只能搭配 tabId 使用,如果指定 frameIds 或 documentIds,就無法使用這項功能:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
插入相關影格
擴充功能可能會想在與相符框架相關的框架中執行指令碼,但這些框架本身並不相符。如果框架的網址是由相符的框架建立,但網址本身不符合指令碼指定的模式,就會發生這種情況。
如果擴充功能想在含有 about:
、data:
、blob:
和 filesystem:
架構的網址中插入影格,就會發生這種情況。在這些情況下,網址不會與內容指令碼的模式相符 (如果是 about:
和 data:
,甚至完全不會在網址中加入父項網址或來源,如 about:blank
或 data: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 會查看影格發起者的來源,判斷影格是否相符,而不是查看影格本身的網址。請注意,這也可能與目標影格的來源不同 (例如 data:
網址的來源為空值)。
框架的發起者是建立或導覽目標框架的框架。雖然這通常是直接父項或開啟器,但可能不是 (例如,在框架導覽 iframe 內的 iframe 時)。
因為這會比較啟動器影格的「來源」,所以啟動器影格可能位於該來源的任何路徑。為明確指出這項影響,Chrome 會要求以 "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/*" ]
}
],
...
}
內容安全政策
在獨立世界中執行的內容指令碼具有下列內容安全政策 (CSP):
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
與套用至其他擴充功能環境的限制類似,這會禁止使用 eval()
,以及載入外部指令碼。
如果是未封裝的擴充功能,CSP 也會包含 localhost:
script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';
將內容指令碼注入主要世界時,系統會套用網頁的 CSP。
保障資料安全
雖然獨立世界提供一層保護,但使用內容指令碼可能會在擴充功能和網頁中產生安全漏洞。如果內容指令碼從其他網站接收內容 (例如呼叫 fetch()
),請務必先針對跨網站指令碼攻擊篩選內容,再注入內容。請務必透過 HTTPS 通訊,以免遭受"man-in-the-middle"攻擊。
請務必篩選出惡意網頁。舉例來說,下列模式很危險,且 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);