Vì tập lệnh nội dung chạy trong bối cảnh của một trang web chứ không phải tiện ích, nên chúng thường cần một cách nào đó để giao tiếp với phần còn lại của tiện ích. Ví dụ: tiện ích trình đọc RSS có thể sử dụng tập lệnh nội dung để phát hiện sự hiện diện của nguồn cấp dữ liệu RSS trên một trang, sau đó thông báo cho trang nền để hiển thị biểu tượng thao tác trên trang cho trang đó.
Tiện ích và tập lệnh nội dung của tiện ích giao tiếp với nhau bằng cách truyền thông báo. Cả hai bên đều có thể lắng nghe các thông báo được gửi từ đầu kia và phản hồi trên cùng một kênh. Thông báo có thể chứa bất kỳ đối tượng JSON hợp lệ nào (giá trị rỗng, boolean, số, chuỗi, mảng hoặc đối tượng). Có một API đơn giản cho các yêu cầu một lần và một API phức tạp hơn cho phép bạn có các kết nối lâu dài để trao đổi nhiều thông báo với một bối cảnh chung. Bạn cũng có thể gửi thông báo đến một tiện ích khác nếu biết mã nhận dạng của tiện ích đó. Nội dung này sẽ được đề cập trong phần thông báo giữa các tiện ích.
Yêu cầu đơn giản một lần
Nếu chỉ cần gửi một thông báo đến một phần khác của tiện ích (và tuỳ chọn nhận lại phản hồi), bạn nên sử dụng runtime.sendMessage hoặc tabs.sendMessage đơn giản . Điều này cho phép bạn gửi một thông báo có thể chuyển đổi tuần tự thành JSON một lần từ một tập lệnh nội dung đến tiện ích hoặc ngược lại. Tham số gọi lại không bắt buộc cho phép bạn xử lý phản hồi từ phía bên kia (nếu có).
Việc gửi yêu cầu từ một tập lệnh nội dung sẽ diễn ra như sau:
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
Việc gửi yêu cầu từ tiện ích đến một tập lệnh nội dung có vẻ rất giống nhau, ngoại trừ việc bạn cần chỉ định thẻ nào để gửi yêu cầu đến. Ví dụ này minh hoạ cách gửi thông báo đến tập lệnh nội dung trong thẻ đã chọn.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
Ở phía nhận, bạn cần thiết lập một trình nghe sự kiện runtime.onMessage để xử lý thông báo. Điều này trông giống nhau từ tập lệnh nội dung hoặc trang tiện ích.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
}
);
Trong ví dụ trên, sendResponse được gọi đồng bộ. Nếu bạn muốn sử dụng sendResponse không đồng bộ, hãy thêm return true; vào trình xử lý sự kiện onMessage.
sendResponse chỉ hợp lệ nếu được dùng đồng bộ hoặc nếu trình xử lý sự kiện trả về true để cho biết rằng trình xử lý sẽ phản hồi không đồng bộ. Lệnh gọi lại của hàm sendMessage sẽ tự động được gọi nếu không có trình xử lý nào trả về giá trị true hoặc nếu lệnh gọi lại sendResponse được thu gom rác.Kết nối duy trì lâu dài
Đôi khi, bạn cần có một cuộc trò chuyện kéo dài hơn một yêu cầu và phản hồi. Trong trường hợp này, bạn có thể mở một kênh tồn tại lâu dài từ tập lệnh nội dung đến một trang tiện ích hoặc ngược lại, bằng cách sử dụng runtime.connect hoặc tabs.connect, tương ứng . Kênh có thể có tên (không bắt buộc), giúp bạn phân biệt giữa các loại kết nối.
Một trường hợp sử dụng có thể là tiện ích tự động điền biểu mẫu. Tập lệnh nội dung có thể mở một kênh đến trang tiện ích cho một lần đăng nhập cụ thể và gửi một thông báo đến tiện ích cho từng phần tử đầu vào trên trang để yêu cầu điền dữ liệu biểu mẫu. Kết nối dùng chung cho phép tiện ích duy trì trạng thái dùng chung liên kết nhiều thông báo đến từ tập lệnh nội dung.
Khi thiết lập một kết nối, mỗi đầu sẽ được cung cấp một đối tượng runtime.Port dùng để gửi và nhận thông báo thông qua kết nối đó.
Sau đây là cách mở một kênh từ tập lệnh nội dung, cũng như cách gửi và nhận tin nhắn:
var port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
if (msg.question == "Who's there?")
port.postMessage({answer: "Madame"});
else if (msg.question == "Madame who?")
port.postMessage({answer: "Madame... Bovary"});
});
Việc gửi yêu cầu từ tiện ích đến một tập lệnh nội dung có vẻ rất giống nhau, ngoại trừ việc bạn cần chỉ định thẻ nào để kết nối. Bạn chỉ cần thay thế lệnh gọi để kết nối trong ví dụ trên bằng tabs.connect.
Để xử lý các kết nối đến, bạn cần thiết lập một trình nghe sự kiện runtime.onConnect. Điều này không có gì khác biệt giữa tập lệnh nội dung và trang tiện ích. Khi một phần khác của tiện ích gọi "connect()", sự kiện này sẽ được kích hoạt, cùng với đối tượng runtime.Port mà bạn có thể dùng để gửi và nhận tin nhắn thông qua kết nối. Sau đây là cách phản hồi các kết nối đến:
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "knockknock");
port.onMessage.addListener(function(msg) {
if (msg.joke == "Knock knock")
port.postMessage({question: "Who's there?"});
else if (msg.answer == "Madame")
port.postMessage({question: "Madame who?"});
else if (msg.answer == "Madame... Bovary")
port.postMessage({question: "I don't get it."});
});
});
Thời gian tồn tại của cổng
Cổng được thiết kế như một phương thức giao tiếp hai chiều giữa các phần khác nhau của tiện ích, trong đó khung (cấp cao nhất) được xem là phần nhỏ nhất. Khi gọi tabs.connect, runtime.connect hoặc runtime.connectNative, một Port sẽ được tạo. Bạn có thể dùng cổng này ngay lập tức để gửi tin nhắn đến đầu kia thông qua postMessage.
Nếu có nhiều khung trong một thẻ, việc gọi tabs.connect sẽ dẫn đến nhiều lệnh gọi sự kiện runtime.onConnect (mỗi khung trong thẻ một lần). Tương tự, nếu runtime.connect được dùng, thì sự kiện onConnect có thể được kích hoạt nhiều lần (mỗi lần cho mọi khung hình trong quy trình của tiện ích).
Bạn có thể muốn biết thời điểm một kết nối bị đóng, chẳng hạn như nếu bạn đang duy trì trạng thái riêng cho từng cổng mở. Để làm việc này, bạn có thể theo dõi sự kiện runtime.Port.onDisconnect. Sự kiện này sẽ kích hoạt khi không có cổng hợp lệ ở phía bên kia của kênh. Điều này xảy ra trong các trường hợp sau:
- Không có trình nghe nào cho runtime.onConnect ở đầu kia.
- Thẻ chứa cổng này sẽ được huỷ tải (ví dụ: nếu thẻ được điều hướng).
- Khung mà từ đó
connectđược gọi đã được huỷ tải. - Tất cả các khung nhận được cổng (thông qua runtime.onConnect) đều đã được gỡ tải.
- runtime.Port.disconnect được gọi bởi đầu kia. Xin lưu ý rằng nếu một lệnh gọi
connectdẫn đến nhiều cổng ở phía người nhận vàdisconnect()được gọi trên bất kỳ cổng nào trong số này, thì sự kiệnonDisconnectchỉ được kích hoạt tại cổng của người gửi chứ không phải tại các cổng khác.
Nhắn tin trên nhiều tiện ích
Ngoài việc gửi thông báo giữa các thành phần khác nhau trong tiện ích, bạn có thể dùng API nhắn tin để giao tiếp với các tiện ích khác. Điều này cho phép bạn hiển thị một API công khai mà các tiện ích khác có thể tận dụng.
Việc theo dõi các yêu cầu và kết nối đến cũng tương tự như trường hợp nội bộ, ngoại trừ việc bạn sử dụng các phương thức runtime.onMessageExternal hoặc runtime.onConnectExternal. Sau đây là ví dụ về từng loại:
// For simple requests:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id == blocklistedExtension)
return; // don't allow this extension access
else if (request.getTargetData)
sendResponse({targetData: targetData});
else if (request.activateLasers) {
var success = activateLasers();
sendResponse({activateLasers: success});
}
});
// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
Tương tự, việc gửi tin nhắn đến một tiện ích khác cũng tương tự như việc gửi tin nhắn trong tiện ích của bạn. Điểm khác biệt duy nhất là bạn phải truyền mã nhận dạng của tiện ích mà bạn muốn giao tiếp. Ví dụ:
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// Start a long-running conversation:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
Gửi tin nhắn từ các trang web
Tương tự như nhắn tin trên nhiều tiện ích, ứng dụng hoặc tiện ích của bạn có thể nhận và phản hồi tin nhắn từ các trang web thông thường. Để sử dụng tính năng này, trước tiên, bạn phải chỉ định trong manifest.json những trang web mà bạn muốn giao tiếp. Ví dụ:
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
Thao tác này sẽ hiển thị Messaging API cho mọi trang khớp với các mẫu URL mà bạn chỉ định. Mẫu URL phải chứa ít nhất một miền cấp hai, tức là các mẫu tên máy chủ như "*", "*.com", "*.co.uk" và "*.appspot.com" đều bị cấm. Từ trang web, hãy sử dụng API runtime.sendMessage hoặc runtime.connect để gửi một thông báo đến một ứng dụng hoặc tiện ích cụ thể. Ví dụ:
// The ID of the extension we want to talk to.
var editorExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// Make a simple request:
chrome.runtime.sendMessage(editorExtensionId, {openUrlInEditor: url},
function(response) {
if (!response.success)
handleError(url);
});
Từ ứng dụng hoặc tiện ích của mình, bạn có thể nghe tin nhắn từ các trang web thông qua API runtime.onMessageExternal hoặc runtime.onConnectExternal, tương tự như nhắn tin giữa các tiện ích. Chỉ trang web mới có thể bắt đầu một kết nối. Dưới đây là ví dụ:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url == blocklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
Nhắn tin bằng ứng dụng gốc
Các tiện ích và ứng dụng có thể trao đổi thông báo với các ứng dụng gốc được đăng ký làm máy chủ nhắn tin gốc. Để tìm hiểu thêm về tính năng này, hãy xem phần Nhắn tin qua ứng dụng gốc.
Lưu ý về bảo mật
Tập lệnh nội dung ít đáng tin cậy hơn
Tập lệnh nội dung ít đáng tin cậy hơn so với trang nền của tiện ích (ví dụ: một trang web độc hại có thể xâm nhập vào quy trình kết xuất nơi tập lệnh nội dung chạy). Giả sử rằng các thông báo từ một tập lệnh nội dung có thể do kẻ tấn công tạo ra và đảm bảo xác thực và dọn dẹp tất cả dữ liệu đầu vào. Giả sử mọi dữ liệu được gửi đến tập lệnh nội dung có thể bị rò rỉ sang trang web. Giới hạn phạm vi của các hành động đặc quyền có thể được kích hoạt bằng các thông báo nhận được từ tập lệnh nội dung.
Tập lệnh trên nhiều trang web
Khi nhận được thông báo từ một tập lệnh nội dung hoặc một tiện ích khác, tập lệnh của bạn phải cẩn thận để không trở thành nạn nhân của tấn công tập lệnh trên nhiều trang web. Lời khuyên này áp dụng cho các tập lệnh chạy bên trong trang nền của tiện ích cũng như các tập lệnh nội dung chạy bên trong các nguồn gốc web khác. Cụ thể, hãy tránh sử dụng các API nguy hiểm, chẳng hạn như những API dưới đây:
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be evaluating an evil script!
var resp = eval("(" + response.farewell + ")");
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = response.farewell;
});
Thay vào đó, hãy ưu tiên các API an toàn hơn không chạy tập lệnh:
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// JSON.parse does not evaluate the attacker's scripts.
var resp = JSON.parse(response.farewell);
});
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
// innerText does not let the attacker inject HTML elements.
document.getElementById("resp").innerText = response.farewell;
});
Ví dụ
Bạn có thể tìm thấy các ví dụ đơn giản về việc giao tiếp qua tin nhắn trong thư mục examples/api/messaging. Mẫu tin nhắn gốc minh hoạ cách một ứng dụng Chrome có thể giao tiếp với một ứng dụng gốc. Để xem thêm ví dụ và được trợ giúp xem mã nguồn, hãy xem phần Mẫu.