Tập lệnh nội dung là các tệp chạy trong bối cảnh của trang web. Bằng cách sử dụng Mô hình đối tượng tài liệu (DOM) tiêu chuẩn, các tiện ích này có thể đọc thông tin chi tiết về các trang web mà trình duyệt truy cập, thực hiện các thay đổi đối với các trang web đó và chuyển thông tin đến tiện ích mẹ.
Tìm hiểu về các chức năng của tập lệnh nội dung
Tập lệnh nội dung có thể truy cập trực tiếp vào các API tiện ích sau:
dom
i18n
storage
runtime.connect()
runtime.getManifest()
runtime.getURL()
runtime.id
runtime.onConnect
runtime.onMessage
runtime.sendMessage()
Tập lệnh nội dung không thể truy cập trực tiếp vào các API khác. Tuy nhiên, các phần này có thể truy cập gián tiếp bằng cách trao đổi thông báo với các phần khác của tiện ích.
Bạn cũng có thể truy cập vào các tệp khác trong tiện ích của mình từ tập lệnh nội dung bằng cách sử dụng các API như fetch()
. Để làm việc này, bạn cần khai báo các tài nguyên đó là tài nguyên có thể truy cập trên web. Xin lưu ý rằng việc này cũng hiển thị các tài nguyên cho mọi tập lệnh của bên thứ nhất hoặc bên thứ ba đang chạy trên cùng một trang web.
Làm việc trong các thế giới riêng biệt
Tập lệnh nội dung hoạt động trong một môi trường riêng biệt, cho phép tập lệnh nội dung thực hiện các thay đổi đối với môi trường JavaScript mà không xung đột với trang hoặc tập lệnh nội dung của các tiện ích khác.
Một tiện ích có thể chạy trong trang web có mã tương tự như ví dụ sau.
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>
Tiện ích đó có thể chèn tập lệnh nội dung sau bằng một trong các kỹ thuật được nêu trong phần Chèn tập lệnh.
content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click", () => alert(greeting + button.person_name + "."), false);
Với thay đổi này, cả hai cảnh báo sẽ xuất hiện theo trình tự khi người dùng nhấp vào nút.
Chèn tập lệnh
Bạn có thể khai báo tập lệnh nội dung một cách tĩnh, khai báo một cách linh động hoặc chèn theo phương thức lập trình.
Chèn bằng nội dung khai báo tĩnh
Sử dụng nội dung khai báo tập lệnh tĩnh trong tệp manifest.json cho các tập lệnh sẽ tự động chạy trên một nhóm trang đã biết.
Các tập lệnh được khai báo tĩnh được đăng ký trong tệp kê khai theo khoá "content_scripts"
.
Các tệp này có thể bao gồm tệp JavaScript, tệp CSS hoặc cả hai. Tất cả tập lệnh nội dung tự động chạy phải chỉ định mẫu khớp.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"css": ["my-styles.css"],
"js": ["content-script.js"]
}
],
...
}
Tên | Loại | Mô tả |
---|---|---|
matches |
mảng chuỗi | Bắt buộc. Chỉ định những trang sẽ được chèn tập lệnh nội dung này. Hãy xem phần Mẫu khớp để biết thông tin chi tiết về cú pháp của các chuỗi này và phần Mẫu khớp và glob để biết thông tin về cách loại trừ các URL. |
css |
mảng chuỗi | Không bắt buộc. Danh sách các tệp CSS cần chèn vào các trang trùng khớp. Các thành phần này được chèn theo thứ tự xuất hiện trong mảng này, trước khi bất kỳ DOM nào được tạo hoặc hiển thị cho trang. |
js |
|
Không bắt buộc. Danh sách các tệp JavaScript cần chèn vào các trang trùng khớp. Các tệp được chèn theo thứ tự xuất hiện trong mảng này. Mỗi chuỗi trong danh sách này phải chứa một đường dẫn tương đối đến một tài nguyên trong thư mục gốc của tiện ích. Dấu gạch chéo đầu dòng ("/") sẽ được tự động cắt bớt. |
run_at |
RunAt | Không bắt buộc. Chỉ định thời điểm chèn tập lệnh vào trang. Giá trị mặc định là document_idle . |
match_about_blank |
boolean | Không bắt buộc. Liệu tập lệnh có nên chèn vào khung about:blank hay không, trong đó khung mẹ hoặc khung mở khớp với một trong các mẫu được khai báo trong matches . Giá trị mặc định là sai. |
match_origin_as_fallback |
boolean |
Không bắt buộc. Liệu tập lệnh có nên chèn vào các khung do một nguồn gốc phù hợp tạo ra hay không, nhưng URL hoặc nguồn gốc của các khung đó có thể không khớp trực tiếp với mẫu. Các khung này bao gồm các khung có nhiều lược đồ, chẳng hạn như about: , data: , blob: và filesystem: . Xem thêm phần Chèn vào các khung liên quan.
|
world |
ExecutionWorld |
Không bắt buộc. Môi trường JavaScript để một tập lệnh thực thi trong đó. Giá trị mặc định là ISOLATED . Xem thêm phần Làm việc trong các thế giới riêng biệt.
|
Chèn bằng nội dung khai báo động
Tập lệnh nội dung động rất hữu ích khi không biết rõ mẫu so khớp cho tập lệnh nội dung hoặc khi không phải lúc nào tập lệnh nội dung cũng được chèn vào máy chủ đã biết.
Ra mắt trong Chrome 96, nội dung khai báo động tương tự như nội dung khai báo tĩnh, nhưng đối tượng tập lệnh nội dung được đăng ký với Chrome bằng các phương thức trong không gian tên chrome.scripting
thay vì trong manifest.json. API tập lệnh cũng cho phép nhà phát triển tiện ích:
- Đăng ký tập lệnh nội dung.
- Nhận danh sách tập lệnh nội dung đã đăng ký.
- Cập nhật danh sách tập lệnh nội dung đã đăng ký.
- Xoá tập lệnh nội dung đã đăng ký.
Giống như nội dung khai báo tĩnh, nội dung khai báo động có thể bao gồm tệp JavaScript, tệp CSS hoặc cả hai.
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"));
Chèn theo phương thức lập trình
Sử dụng tính năng chèn theo phương thức lập trình cho các tập lệnh nội dung cần chạy để phản hồi các sự kiện hoặc vào các dịp cụ thể.
Để chèn tập lệnh nội dung theo phương thức lập trình, tiện ích của bạn cần có quyền lưu trữ cho trang mà tiện ích đó đang cố gắng chèn tập lệnh. Bạn có thể cấp quyền cho máy chủ lưu trữ bằng cách yêu cầu quyền đó trong tệp kê khai của tiện ích hoặc tạm thời sử dụng "activeTab"
.
Sau đây là các phiên bản khác nhau của tiện ích dựa trên activeTab.
manifest.json:
{
"name": "My extension",
...
"permissions": [
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "Action Button"
}
}
Bạn có thể chèn tập lệnh nội dung dưới dạng tệp.
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"]
});
});
Hoặc bạn có thể chèn và thực thi phần nội dung của hàm dưới dạng tập lệnh nội dung.
service-worker.js:
function injectedFunction() {
document.body.style.backgroundColor = "orange";
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target : {tabId : tab.id},
func : injectedFunction,
});
});
Xin lưu ý rằng hàm được chèn là một bản sao của hàm được tham chiếu trong lệnh gọi chrome.scripting.executeScript()
, chứ không phải chính hàm ban đầu. Do đó, phần nội dung của hàm phải tự chứa; các tệp tham chiếu đến biến bên ngoài hàm sẽ khiến tập lệnh nội dung gửi một ReferenceError
.
Khi chèn dưới dạng hàm, bạn cũng có thể truyền đối số vào hàm đó.
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" ],
});
});
Loại trừ các kết quả trùng khớp và glob
Để tuỳ chỉnh tính năng so khớp trang được chỉ định, hãy đưa các trường sau vào quy trình đăng ký khai báo.
Tên | Loại | Mô tả |
---|---|---|
exclude_matches |
mảng chuỗi | Không bắt buộc. Loại trừ các trang mà tập lệnh nội dung này sẽ được chèn vào. Hãy xem phần Mẫu khớp để biết thông tin chi tiết về cú pháp của các chuỗi này. |
include_globs |
mảng chuỗi | Không bắt buộc. Được áp dụng sau matches để chỉ bao gồm những URL cũng khớp với glob này. Mục đích của việc này là mô phỏng từ khoá @include của Greasemonkey. |
exclude_globs |
mảng chuỗi | Không bắt buộc. Áp dụng sau matches để loại trừ các URL khớp với biểu thức chính quy này. Dùng để mô phỏng từ khoá @exclude của Greasemonkey. |
Tập lệnh nội dung sẽ được chèn vào trang nếu cả hai điều sau đều đúng:
- URL của tệp này khớp với mọi mẫu
matches
và mọi mẫuinclude_globs
. - URL cũng không khớp với mẫu
exclude_matches
hoặcexclude_globs
. Vì thuộc tínhmatches
là bắt buộc, nên bạn chỉ có thể sử dụngexclude_matches
,include_globs
vàexclude_globs
để giới hạn những trang sẽ bị ảnh hưởng.
Tiện ích sau đây sẽ chèn tập lệnh nội dung vào https://www.nytimes.com/health
nhưng không chèn vào 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" ],
}]);
Thuộc tính glob tuân theo cú pháp khác, linh hoạt hơn so với mẫu khớp. Chuỗi glob được chấp nhận là các URL có thể chứa dấu hoa thị "ký tự đại diện" và dấu chấm hỏi. Dấu hoa thị (*
) khớp với mọi chuỗi có độ dài bất kỳ, bao gồm cả chuỗi trống, trong khi dấu chấm hỏi (?
) khớp với mọi ký tự đơn.
Ví dụ: biểu thức chính quy https://???.example.com/foo/\*
khớp với bất kỳ nội dung nào sau đây:
https://www.example.com/foo/bar
https://the.example.com/foo/
Tuy nhiên, mã này không khớp với những mã sau:
https://my.example.com/foo/bar
https://example.com/foo/
https://www.example.com/foo
Tiện ích này chèn tập lệnh nội dung vào https://www.nytimes.com/arts/index.html
và
https://www.nytimes.com/jobs/index.htm*
, nhưng không chèn vào
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"]
}
],
...
}
Tiện ích này chèn tập lệnh nội dung vào https://history.nytimes.com
và
https://.nytimes.com/history
, nhưng không chèn vào https://science.nytimes.com
hoặc
https://www.nytimes.com/science
:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"exclude_globs": ["*science*"],
"js": ["contentScript.js"]
}
],
...
}
Bạn có thể đưa một, tất cả hoặc một số trong số này vào để đạt được phạm vi chính xác.
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"]
}
],
...
}
Thời gian chạy
Trường run_at
kiểm soát thời điểm chèn tệp JavaScript vào trang web. Giá trị ưu tiên và mặc định là "document_idle"
. Hãy xem loại RunAt để biết các giá trị có thể có khác.
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" ],
}]);
Tên | Loại | Mô tả |
---|---|---|
document_idle |
chuỗi | Nên ưu tiên. Sử dụng "document_idle" bất cứ khi nào có thể.Trình duyệt chọn thời điểm chèn tập lệnh giữa "document_end" và ngay sau khi sự kiện window.onload kích hoạt. Thời điểm chèn chính xác phụ thuộc vào độ phức tạp của tài liệu và thời gian tải tài liệu, đồng thời được tối ưu hoá cho tốc độ tải trang.Các tập lệnh nội dung chạy ở "document_idle" không cần theo dõi sự kiện window.onload , các tập lệnh này được đảm bảo sẽ chạy sau khi DOM hoàn tất. Nếu một tập lệnh chắc chắn cần chạy sau window.onload , thì tiện ích có thể kiểm tra xem onload đã kích hoạt hay chưa bằng cách sử dụng thuộc tính document.readyState . |
document_start |
chuỗi | Các tập lệnh được chèn sau mọi tệp từ css , nhưng trước khi tạo bất kỳ DOM nào khác hoặc chạy bất kỳ tập lệnh nào khác. |
document_end |
chuỗi | Các tập lệnh được chèn ngay sau khi DOM hoàn tất, nhưng trước khi các tài nguyên phụ như hình ảnh và khung tải. |
Chỉ định khung
Đối với tập lệnh nội dung khai báo được chỉ định trong tệp kê khai, trường "all_frames"
cho phép tiện ích chỉ định xem có nên chèn các tệp JavaScript và CSS vào tất cả các khung khớp với các yêu cầu về URL đã chỉ định hay chỉ vào khung trên cùng trong thẻ:
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.nytimes.com/*"],
"all_frames": true,
"js": ["contentScript.js"]
}
],
...
}
Khi đăng ký tập lệnh nội dung theo phương thức lập trình bằng chrome.scripting.registerContentScripts(...)
, bạn có thể sử dụng tham số allFrames
để chỉ định xem có nên chèn tập lệnh nội dung vào tất cả các khung khớp với yêu cầu URL đã chỉ định hay chỉ chèn vào khung trên cùng trong một thẻ. Bạn chỉ có thể sử dụng thuộc tính này với tabId và không thể sử dụng nếu chỉ định frameIds hoặc documentIds:
service-worker.js
chrome.scripting.registerContentScripts([{
id: "test",
matches : [ "https://*.nytimes.com/*" ],
allFrames : true,
js : [ "contentScript.js" ],
}]);
Chèn vào các khung liên quan
Tiện ích có thể muốn chạy tập lệnh trong các khung liên quan đến một khung khớp, nhưng bản thân các khung đó không khớp. Một trường hợp phổ biến là khi các khung có URL được tạo bởi một khung phù hợp, nhưng URL của các khung đó không khớp với mẫu được chỉ định của tập lệnh.
Đây là trường hợp một tiện ích muốn chèn vào các khung có URL có giao thức about:
, data:
, blob:
và filesystem:
. Trong những trường hợp này, URL sẽ không khớp với mẫu của tập lệnh nội dung (và trong trường hợp about:
và data:
, thậm chí không bao gồm URL gốc hoặc nguồn gốc trong URL, như trong about:blank
hoặc data:text/html,<html>Hello, World!</html>
). Tuy nhiên, các khung này vẫn có thể được liên kết với khung tạo.
Để chèn vào các khung này, các tiện ích có thể chỉ định thuộc tính "match_origin_as_fallback"
trên quy cách tập lệnh nội dung trong tệp kê khai.
manifest.json
{
"name": "My extension",
...
"content_scripts": [
{
"matches": ["https://*.google.com/*"],
"match_origin_as_fallback": true,
"js": ["contentScript.js"]
}
],
...
}
Khi được chỉ định và đặt thành true
, Chrome sẽ xem xét nguồn gốc của trình khởi tạo khung để xác định xem khung có khớp hay không, thay vì xem xét URL của chính khung đó. Xin lưu ý rằng giá trị này cũng có thể khác với gốc của khung mục tiêu (ví dụ: URL data:
có nguồn gốc rỗng).
Trình khởi tạo của khung là khung đã tạo hoặc điều hướng khung mục tiêu. Mặc dù đây thường là thành phần mẹ hoặc thành phần mở trực tiếp, nhưng có thể không phải vậy (như trong trường hợp một khung điều hướng một iframe trong một iframe).
Vì việc này so sánh nguồn gốc của khung khởi tạo, nên khung khởi tạo có thể nằm trên bất kỳ đường dẫn nào từ nguồn gốc đó. Để làm rõ điều này, Chrome yêu cầu mọi tập lệnh nội dung được chỉ định bằng "match_origin_as_fallback"
được đặt thành true
cũng phải chỉ định đường dẫn của *
.
Khi bạn chỉ định cả "match_origin_as_fallback"
và "match_about_blank"
, "match_origin_as_fallback"
sẽ được ưu tiên.
Giao tiếp với trang nhúng
Mặc dù môi trường thực thi của tập lệnh nội dung và các trang lưu trữ tập lệnh nội dung được tách biệt với nhau, nhưng các môi trường này có quyền truy cập chung vào DOM của trang. Nếu muốn giao tiếp với tập lệnh nội dung hoặc với tiện ích thông qua tập lệnh nội dung, trang phải thực hiện việc này thông qua DOM dùng chung.
Bạn có thể thực hiện ví dụ này bằng cách sử dụng 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);
Trang không phải tiện ích, example.html, đăng tin nhắn cho chính nó. Thông báo này được tập lệnh nội dung chặn và kiểm tra, sau đó được đăng lên quy trình mở rộng. Bằng cách này, trang này sẽ thiết lập một đường liên lạc đến quy trình mở rộng. Bạn có thể thực hiện thao tác ngược lại thông qua các phương thức tương tự.
Truy cập vào tệp tiện ích
Để truy cập vào tệp tiện ích từ tập lệnh nội dung, bạn có thể gọi chrome.runtime.getURL()
để lấy URL tuyệt đối của thành phần tiện ích như trong ví dụ sau (content.js
):
content-script.js
let image = chrome.runtime.getURL("images/my_image.png")
Để sử dụng phông chữ hoặc hình ảnh trong tệp CSS, bạn có thể sử dụng @@extension_id
để tạo URL như trong ví dụ sau (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');
}
Tất cả thành phần phải được khai báo là tài nguyên có thể truy cập trên web trong tệp manifest.json
:
manifest.json
{
...
"web_accessible_resources": [
{
"resources": [ "images/*.png" ],
"matches": [ "https://example.com/*" ]
},
{
"resources": [ "fonts/*.woff" ],
"matches": [ "https://example.com/*" ]
}
],
...
}
Bảo mật
Mặc dù các thế giới riêng biệt cung cấp một lớp bảo vệ, nhưng việc sử dụng tập lệnh nội dung có thể tạo ra các lỗ hổng trong tiện ích và trang web. Nếu tập lệnh nội dung nhận nội dung từ một trang web riêng biệt, chẳng hạn như bằng cách gọi fetch()
, hãy cẩn thận lọc nội dung để chống lại các cuộc tấn công tập lệnh trên nhiều trang web trước khi chèn nội dung đó. Chỉ giao tiếp qua HTTPS để tránh các cuộc tấn công "man-in-the-middle".
Hãy nhớ lọc các trang web độc hại. Ví dụ: các mẫu sau đây là nguy hiểm và không được phép trong Tệp kê khai 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);
Thay vào đó, hãy ưu tiên các API an toàn hơn không chạy tập lệnh:
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);