Chuyển tin nhắn

Vì tập lệnh nội dung chạy trong ngữ cảnh của một trang web chứ không phải trong tiện ích, nên các tập lệnh này thường cần một số cách giao tiếp với phần còn lại của tiện ích. Ví dụ: một tiện ích trình đọc RSS có thể 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, rồi thông báo cho trang nền để hiển thị biểu tượng hành động trên trang cho trang đó.

Hoạt động giao tiếp giữa tiện ích và tập lệnh nội dung hoạt động bằng cách sử dụng tính năng truyền thông báo. Một trong hai bên có thể nghe tin nhắn được gửi từ đầu bên kia và trả lời trên cùng một kênh. Một thông báo có thể chứa bất kỳ đối tượng JSON hợp lệ nào (null, 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 trong bối cảnh dùng chung. Bạn cũng có thể gửi thông báo cho một tiện ích khác nếu biết mã nhận dạng của tiện ích đó, như đề cập trong phần thông báo trên nhiều tiện ích.

Yêu cầu một lần đơn giản

Nếu chỉ cần gửi một thông báo duy nhất đến một phần khác của tiện ích (và không bắt buộc nhận lại phản hồi), bạn nên sử dụng runtime.sendMessage hoặc tabs.sendMessage đã được đơn giản hoá . Việc này cho phép bạn gửi thông báo chuyển đổi tuần tự JSON một lần từ tập lệnh nội dung đến tiện ích hoặc ngược lại . Thông 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ẽ có dạng 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 tập lệnh nội dung trông rất giống nhau, ngoại trừ việc bạn cần chỉ định thẻ sẽ gửi yêu cầu đó. Ví dụ này minh hoạ việc 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);
  });
});

Ở bên nhận, bạn cần thiết lập trình nghe sự kiện runtime.onMessage để xử lý tin nhắn. URL này trông giống như 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 một cách đồng bộ. Nếu bạn muốn sử dụng không đồng bộ sendResponse, hãy thêm return true; vào trình xử lý sự kiện onMessage.

Lưu ý: Nếu nhiều trang đang theo dõi các sự kiện onMessage, thì chỉ người đầu tiên gọi sendResponse() cho một sự kiện cụ thể mới gửi phản hồi thành công. Tất cả phản hồi khác cho sự kiện đó sẽ bị bỏ qua.
Lưu ý: Lệnh gọi lại sendResponse chỉ hợp lệ nếu được sử dụng đồng bộ hoặc nếu trình xử lý sự kiện trả về true để cho biết sẽ phản hồi không đồng bộ. Lệnh gọi lại của hàm sendMessage sẽ được tự động 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 thập rác.

Kết nối lâu dài

Đôi khi, việc trò chuyện kéo dài lâu hơn một yêu cầu và phản hồi sẽ rất hữu ích. 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 . Bạn có thể tuỳ ý đặt tên cho kênh để phân biệt các loại kết nối.

Một trường hợp sử dụng có thể là tiện ích điền biểu mẫu tự động. Tập lệnh nội dung có thể mở một kênh đến trang tiện ích cho một thông tin đăng nhập cụ thể, đồng thời gửi thông báo tới tiện ích cho từng phần tử đầu vào trên trang để yêu cầu dữ liệu biểu mẫu cần điền vào. Kết nối được chia sẻ cho phép tiện ích duy trì trạng thái đã chia sẻ liên kết các thông báo đến từ tập lệnh nội dung.

Khi thiết lập kết nối, mỗi đầu được cấp một đối tượng runtime.Port, dùng để gửi và nhận thông báo qua kết nối đó.

Dưới đây là cách bạn mở kênh bằng kịch bản nội dung rồi gửi và nghe 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 tập lệnh nội dung trông rất giống nhau, ngoại trừ việc bạn cần chỉ định thẻ để 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 trình nghe sự kiện runtime.onConnect. URL này trông giống như một tập lệnh nội dung hoặc 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 thông báo thông qua kết nối. Dưới đây là ví dụ minh hoạ quá trình 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."});
  });
});

Tuổi thọ 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. Sau khi gọi tabs.connect, runtime.connect hoặc runtime.connectNative, một Cổng sẽ được tạo. Cổng này có thể được sử dụng ngay để gửi thông báo tới đầu bên 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 của sự kiện runtime.onConnect (một lần cho mỗi khung trong thẻ). Tương tự, nếu bạn sử dụng runtime.connect, thì sự kiện onConnect có thể được kích hoạt nhiều lần (một lần cho mỗi khung hình trong quá trình tiện ích).

Bạn có thể muốn tìm hiểu thời điểm kết nối bị đóng, chẳng hạn như bạn có đang duy trì trạng thái riêng biệt cho mỗi cổng mở hay không. Để làm được việc này, bạn có thể nghe sự kiện runtime.Port.onDisconnect. Sự kiện này được 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 bên kia.
  • Thẻ chứa cổng đã bị huỷ tải (ví dụ: thẻ đang điều hướng).
  • Khung nơi connect được gọi đã bị huỷ tải.
  • Tất cả khung hình nhận được cổng (thông qua runtime.onConnect) đều đã được huỷ tải.
  • đầu kia sẽ gọi runtime.Port.disconnect. Lưu ý rằng nếu lệnh gọi connect dẫn đến nhiều cổng ở cuối của trình thu nhận và disconnect() được gọi trên bất kỳ cổng nào trong số này, thì sự kiện onDisconnect chỉ được kích hoạt ở cổng của người gửi chứ không được kích hoạt ở các cổng khác.

Thông báo 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 trong tiện ích, bạn có thể sử dụng API nhắn tin để giao tiếp với các tiện ích khác. Việc 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 phương thức runtime.onMessageExternal hoặc runtime.onConnectExternal. Sau đây là ví dụ về từng phương thức:

// 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ư 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 chuyể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 thư từ trang web

Tương tự như tính năng gửi tin nhắn trên nhiều tiện ích, ứng dụng hoặc tiện ích của bạn có thể nhận và trả lờ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 trang web nào mà bạn muốn giao tiếp. Ví dụ:

"externally_connectable": {
  "matches": ["*://*.example.com/*"]
}

Thao tác này sẽ hiển thị API thông báo cho mọi trang khớp với 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. Trên trang web, hãy dùng các API runtime.sendMessage hoặc runtime.connect để gửi 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);
  });

Trên ứng dụng hoặc tiện ích, bạn có thể nghe thông báo từ các trang web qua API runtime.onMessageExternal hoặc runtime.onConnectExternal, tương tự như tính năng nhắn tin trên nhiều tiện ích. Chỉ trang web mới có thể bắt đầu 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 tin nhắn với các ứng dụng gố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 bài viết Nhắn tin gốc.

Lưu ý về bảo mật

Tập lệnh nội dung không đáng tin cậy

Tập lệnh nội dung không đáng tin cậy hơn so với trang nền của tiện ích (ví dụ: trang web độc hại có thể xâm phạm quy trình kết xuất nơi tập lệnh nội dung chạy). Giả sử các thông báo từ tập lệnh nội dung có thể do kẻ tấn công tạo ra và hãy nhớ 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ỉ đến trang web. Giới hạn phạm vi các thao tác đặ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ừ tập lệnh nội dung hoặc 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ập lệnh chéo 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ể, bạn nên tránh sử dụng các API nguy hiểm, chẳng hạn như các 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 mà 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 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 thông báo gốc minh hoạ cách ứng dụng Chrome có thể giao tiếp với một ứng dụng gốc. Để biết thêm ví dụ và được trợ giúp về cách xem mã nguồn, hãy xem phần Mẫu.