Tập lệnh nội dung

Tập lệnh nội dung là các tệp chạy trong ngữ cảnh của các trang web. Khi sử dụng Mô hình đối tượng tài liệu (DOM), trình duyệt 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 đó và chuyển thông tin tới tiện ích gốc.

Tìm hiểu 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 vào tệp tiện ích sau khi khai báo chúng là tài nguyên có thể truy cập trên web. Họ có thể truy cập trực tiếp vào các API tiện ích sau đây:

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, họ có thể truy cập gián tiếp bằng cách trao đổi tin nhắn với các phần khác trong tiện ích của bạn.

Làm việc trong các thế giới tách biệt

Tập lệnh nội dung nằm trong một thế giới tách 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 tập lệnh nội dung của trang hoặc của các tiện ích khác.

Một tiện ích có thể chạy trong một 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 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 đều 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

Tập lệnh nội dung có thể được khai báo tĩnh, khai báo động hoặc chèn theo phương thức lập trình.

Chèn nội dung khai báo tĩnh

Sử dụng các khai báo tập lệnh nội dung tĩnh trong manifest.json cho các tập lệnh sẽ được tự động chạy trên một nhóm trang phổ biến.

Các tập lệnh khai báo tĩnh sẽ được đăng ký trong tệp kê khai bằng khoá "content_scripts". Chúng 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 chạy tự động phải chỉ định mẫu so 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 Nội dung mô tả
matches mảng chuỗi Bắt buộc. Chỉ định những trang mà tập lệnh nội dung này sẽ được chèn vào. Hãy xem phần Mẫu so khớp để biết thông tin chi tiết về cú pháp của các chuỗi này cũng như Mẫu so khớp và cụm cầu để biết thông tin về cách loại trừ URL.
css mảng chuỗi Không bắt buộc. Danh sách các tệp CSS sẽ được đưa vào các trang phù hợp. Các mã này được chèn theo thứ tự xuất hiện trong mảng này, trước khi tạo hoặc hiển thị bất kỳ DOM nào cho trang.
js mảng chuỗi Không bắt buộc. Danh sách các tệp JavaScript được đưa vào các trang phù hợ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 (`/`) 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, trong đó khung mẹ hoặc khung mở khớp với một trong các mẫu được khai báo trong matches hay không. Giá trị mặc định là false.
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 nhưng có URL hoặc nguồn gốc có thể không khớp trực tiếp với mẫu hay không. Các giao thức này bao gồm các khung có giao thức khác nhau, chẳng hạn như about:, data:, blob:filesystem:. Hãy xem thêm phần Chèn vào các khung liên quan.
world ExecutionWorld Không bắt buộc. Thế giới JavaScript để thực thi một tập lệnh. Giá trị mặc định là ISOLATED. Hãy xem thêm bài viết Làm việc trong môi trường tách biệt.

Chèn nội dung khai báo động

Tập lệnh nội dung động rất hữu ích khi mẫu so khớp tập lệnh nội dung không rõ ràng hoặc khi tập lệnh nội dung không phải lúc nào cũng được chèn vào các máy chủ đã biết.

Ra mắt trong Chrome 96, nội dung khai báo động cũng tương tự như 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 Scripting cũng cho phép các nhà phát triển tiện ích:

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 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 những dịp cụ thể.

Để chèn một 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ữ đối với trang mà tiện ích đang cố chèn tập lệnh vào. Bạn có thể cấp quyền từ máy chủ bằng cách yêu cầu những quyền này 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 của tiện ích dựa trên Tab đang hoạt động.

manifest.json:

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

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 nội dung hàm có thể được chèn và thực thi 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à bản sao của hàm được tham chiếu trong lệnh gọi chrome.scripting.executeScript(), chứ không phải là chính hàm gốc. Do đó, phần nội dung của hàm phải tự chứa; việc tham chiếu đến các biến bên ngoài hàm sẽ khiến tập lệnh nội dung gửi ReferenceError.

Khi chèn dưới dạng một hàm, bạn cũng có thể truyền các đố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ừ kết quả phù hợp và toàn bộ khối

Để tuỳ chỉnh việc so khớp trang đã chỉ định, hãy đưa các trường sau vào quy trình đăng ký khai báo.

Tên Loại Nội dung mô tả
exclude_matches mảng chuỗi Không bắt buộc. Không bao gồm những trang mà tập lệnh nội dung này sẽ được chèn vào. Hãy xem phần Mẫu so 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. Áp dụng sau matches để chỉ bao gồm các URL cũng khớp với toàn cầu này. Tính năng này dùng để mô phỏng từ khoá @include Gr bịPercent.
exclude_globs mảng chuỗi Không bắt buộc. Được áp dụng sau matches để loại trừ các URL khớp với toàn bộ toàn bộ này. Nhằm mục đích mô phỏng từ khoá @exclude GrfusionPercent.

Tập lệnh nội dung sẽ được chèn vào trang nếu cả hai điều sau đây là đúng:

  • URL của lớp đó khớp với mọi mẫu matches và mọi mẫu include_globs.
  • URL này cũng không khớp với mẫu exclude_matches hoặc exclude_globs. Vì thuộc tính matches là bắt buộc, nên bạn chỉ có thể sử dụng exclude_matches, include_globsexclude_globs để giới hạn những trang sẽ bị ảnh hưởng.

Tiện ích sau chèn tập lệnh nội dung vào https://www.nytimes.com/health nhưng không chèn tập lệnh nội dung 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" ],
}]);

Các thuộc tính Glob tuân theo cú pháp khác, linh hoạt hơn so với mẫu so khớp. Chuỗi toàn cầu đượ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ỳ, kể cả chuỗi trống, trong khi dấu chấm hỏi (?) khớp với bất kỳ ký tự đơn nào.

Ví dụ: https://???.example.com/foo/\* toàn cầu 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 yêu cầu sau:

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

Tiện ích này sẽ chèn tập lệnh nội dung vào https://www.nytimes.com/arts/index.htmlhttps://www.nytimes.com/jobs/index.htm*, nhưng không chèn tập lệnh nội dung 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 sẽ chèn tập lệnh nội dung vào https://history.nytimes.comhttps://.nytimes.com/history, nhưng không chèn tập lệnh nội dung 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"]
    }
  ],
  ...
}

Một, tất cả hoặc một vài trong số đó có thể được đưa 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 các tệp JavaScript được đưa 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 Nội dung mô tả
document_idle string Ưa thích. Sử dụng "document_idle" bất cứ khi nào có thể.

Trình duyệt chọn khoảng thời gian để chèn tập lệnh trong khoảng thời gian từ "document_end" đến 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 cũng như thời gian tải và thời gian tải, đồng thời được tối ưu hoá cho tốc độ tải trang.

Tập lệnh nội dung chạy ở "document_idle" không cần theo dõi sự kiện window.onload, chúng đả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, tiện ích này 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 string Các tập lệnh được chèn vào sau bất kỳ tệp nào từ css, nhưng trước khi bất kỳ DOM nào khác được tạo hoặc bất kỳ tập lệnh nào khác chạy.
document_end string 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 được tải.

Chỉ định khung

Trường "all_frames" cho phép tiện ích chỉ định xem tệp JavaScript và CSS sẽ được chèn vào tất cả các khung phù hợp với yêu cầu của URL đã chỉ định hay chỉ được chèn vào khung trên cùng trong một thẻ.

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" ],
}]);
Tên Loại Nội dung mô tả
all_frames boolean Không bắt buộc. Giá trị mặc định là false, nghĩa là chỉ có khung trên cùng được so khớp.

Nếu bạn chỉ định true, thì tất cả khung hình sẽ được chèn vào, ngay cả khi khung hình này không phải là khung trên cùng trong thẻ. Mỗi khung sẽ được kiểm tra độc lập theo các yêu cầu về URL. URL này sẽ không chèn vào khung con nếu không đáp ứng các yêu cầu về URL.

Các tiện ích có thể muốn chạy tập lệnh trong các khung liên quan đến một khung phù hợp, nhưng không khớp với nhau. Một trường hợp phổ biến xảy ra với trường hợp này là đối với các khung có URL được tạo bởi một khung phù hợp nhưng URL có URL không khớp với mẫu chỉ định của tập lệnh.

Đây là trường hợp mà một tiện ích muốn chèn vào khung bằng các URL có giao thức about:, data:, blob: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:data:, thậm chí không bao gồm URL mẹ hoặc nguồn gốc trong URL, như trong about:blank hoặc data:text/html,<html>Hello, World!</html>). Tuy nhiên, những 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, tiện ích có thể chỉ định thuộc tính "match_origin_as_fallback" trên thông số kỹ thuật của 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 tạo khung để xác định xem khung hình có khớp hay không, thay vì tại URL của chính khung hình đó. Xin lưu ý rằng giá trị này cũng có thể khác với nguồn gốc của khung mục tiêu (ví dụ: data: URL có nguồn gốc rỗng).

Phương thức 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 trình mở trực tiếp, nhưng có thể không (như trong trường hợp một khung điều hướng iframe trong iframe).

Vì so sánh này so sánh nguồn gốc của khung trình khởi tạo, khung của trình 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 với "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""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à trang lưu trữ tập lệnh đó tách biệt với nhau, nhưng các môi trường này có chung quyền truy cập 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 một ví dụ 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, sẽ đăng thông báo lên chính trang đó. Tập lệnh nội dung chặn và kiểm tra thông báo này, sau đó được đăng lên quy trình tiện ích. Bằng cách này, trang thiết lập một kênh giao tiếp với quy trình tiện ích. Điều ngược lại cũng có thể xảy ra 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 tệp tiện ích từ tập lệnh nội dung, bạn có thể gọi hàm 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ả tài sả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/*" ]
   }
 ],
 ...
}

Luôn an toàn

Mặc dù các thế giới tách 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 được 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 khỏi các cuộc tấn công viết 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".

Đảm bảo lọc các trang web độc hại. Ví dụ: các mẫu sau đây rất nguy hiểm và không được phép sử dụng trong Manifest V3:

Không nên

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
Không nên

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 mà không chạy tập lệnh:

Nên

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
Nên

content-script.js

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