Messaging API ช่วยให้คุณสื่อสารระหว่างสคริปต์ต่างๆ ที่ทำงานใน บริบทที่เชื่อมโยงกับส่วนขยายได้ ซึ่งรวมถึงการสื่อสารระหว่าง Service Worker, chrome-extension://pages และ Content Script ตัวอย่างเช่น ส่วนขยายโปรแกรมอ่าน RSS อาจใช้ Content Script เพื่อตรวจหาฟีด RSS ในหน้าเว็บ จากนั้นแจ้ง Service Worker ให้อัปเดตไอคอนการดำเนินการสำหรับหน้าเว็บนั้น
API การส่งผ่านข้อความมี 2 แบบ ได้แก่ แบบสำหรับคำขอแบบครั้งเดียว และแบบที่ซับซ้อนกว่าสำหรับการเชื่อมต่อที่ใช้งานได้นานซึ่งอนุญาตให้ส่งข้อความได้หลายรายการ
ดูข้อมูลเกี่ยวกับการส่งข้อความระหว่างส่วนขยายได้ที่ส่วนข้อความข้ามส่วนขยาย
คำขอแบบครั้งเดียว
หากต้องการส่งข้อความเดียวไปยังส่วนอื่นของส่วนขยาย และรับการตอบกลับหรือไม่ก็ได้ ให้เรียกใช้ runtime.sendMessage()
หรือ tabs.sendMessage()
วิธีการเหล่านี้ช่วยให้คุณส่งข้อความที่แปลงเป็น JSON ได้แบบครั้งเดียวจาก Content Script ไปยัง
ส่วนขยาย หรือจากส่วนขยายไปยัง Content Script ทั้ง 2 API จะแสดงผล Promise
ซึ่งจะเปลี่ยนเป็นการตอบกลับที่ผู้รับให้ไว้
การส่งคำขอจาก Content Script จะมีลักษณะดังนี้
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
คำตอบ
หากต้องการฟังข้อความ ให้ใช้เหตุการณ์ chrome.runtime.onMessage
// Event listener
function handleMessages(message, sender, sendResponse) {
fetch(message.url)
.then((response) => sendResponse({statusCode: response.status}))
// Since `fetch` is asynchronous, must return an explicit `true`
return true;
}
chrome.runtime.onMessage.addListener(handleMessages);
// From the sender's context...
const {statusCode} = await chrome.runtime.sendMessage({
url: 'https://example.com'
});
เมื่อมีการเรียก Listener เหตุการณ์ ระบบจะส่งsendResponse
ฟังก์ชันเป็นพารามิเตอร์ที่สาม
นี่คือฟังก์ชันที่เรียกใช้เพื่อแสดงคำตอบได้ โดย
ค่าเริ่มต้น ระบบจะเรียกใช้การเรียกกลับ sendResponse
แบบพร้อมกัน หากต้องการ
ทำงานแบบไม่พร้อมกันเพื่อรับค่าที่ส่งไปยัง sendResponse
คุณต้อง
ส่งคืนค่าตามตัวอักษร true
(ไม่ใช่แค่ค่าที่เป็นจริง) จากเครื่องมือฟังเหตุการณ์ การทำเช่นนี้จะทำให้ช่องข้อความเปิดอยู่กับปลายทางอีกด้านจนกว่าจะมีการเรียกใช้ sendResponse
หากคุณเรียกใช้ sendResponse
โดยไม่มีพารามิเตอร์ ระบบจะส่ง null
เป็นการตอบกลับ
หากหลายหน้าเว็บกำลังรอรับฟังเหตุการณ์ onMessage
จะมีเพียงหน้าเว็บแรกที่เรียกใช้ sendResponse()
สำหรับ
เหตุการณ์หนึ่งๆ เท่านั้นที่จะส่งการตอบกลับได้สำเร็จ ระบบจะเพิกเฉยต่อการตอบกลับอื่นๆ ทั้งหมดสำหรับกิจกรรมนั้น
การเชื่อมต่อที่ใช้งานได้นาน
หากต้องการสร้างช่องการส่งข้อความแบบมีอายุยาวนานที่ใช้ซ้ำได้ ให้เรียกใช้
runtime.connect()
เพื่อส่งข้อความจากสคริปต์เนื้อหา ไปยังหน้าส่วนขยายtabs.connect()
เพื่อส่งข้อความจากหน้าส่วนขยายไปยัง Content Script
คุณตั้งชื่อช่องได้โดยส่งพารามิเตอร์ options ที่มีคีย์ name
เพื่อ
แยกความแตกต่างระหว่างการเชื่อมต่อประเภทต่างๆ ดังนี้
const port = chrome.runtime.connect({name: "example"});
กรณีการใช้งานที่เป็นไปได้ของการเชื่อมต่อที่มีอายุการใช้งานยาวนานคือส่วนขยายการกรอกแบบฟอร์มอัตโนมัติ Content Script อาจเปิดช่องทางไปยังหน้าส่วนขยายสำหรับการเข้าสู่ระบบที่เฉพาะเจาะจง และ ส่งข้อความไปยังส่วนขยายสำหรับองค์ประกอบอินพุตแต่ละรายการในหน้าเว็บเพื่อขอข้อมูล แบบฟอร์มที่จะกรอก การเชื่อมต่อที่แชร์ช่วยให้ส่วนขยายแชร์สถานะระหว่างคอมโพเนนต์ของส่วนขยายได้
เมื่อสร้างการเชื่อมต่อ ระบบจะกำหนดออบเจ็กต์ runtime.Port
ให้แต่ละปลายทางเพื่อ
ส่งและรับข้อความผ่านการเชื่อมต่อนั้น
ใช้โค้ดต่อไปนี้เพื่อเปิดแชแนลจาก Content Script รวมถึงส่งและรับฟังข้อความ
content-script.js:
const port = chrome.runtime.connect({name: "knockknock"});
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"});
}
});
port.postMessage({joke: "Knock knock"});
หากต้องการส่งคำขอจากส่วนขยายไปยัง Content Script ให้แทนที่การเรียกใช้ runtime.connect()
ในตัวอย่างก่อนหน้าด้วย tabs.connect()
หากต้องการจัดการการเชื่อมต่อขาเข้าสำหรับ Content Script หรือหน้าส่วนขยาย ให้ตั้งค่า Listener เหตุการณ์ runtime.onConnect
เมื่อส่วนอื่นของส่วนขยายเรียกใช้ connect()
ระบบจะเปิดใช้งานเหตุการณ์นี้และออบเจ็กต์ runtime.Port
โค้ดสำหรับการตอบสนองต่อการเชื่อมต่อขาเข้ามีลักษณะดังนี้
service-worker.js:
chrome.runtime.onConnect.addListener(function(port) {
if (port.name !== "knockknock") {
return;
}
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."});
}
});
});
การเรียงอันดับ
ใน Chrome API การส่งข้อความจะใช้การซีเรียลไลซ์ JSON ซึ่งหมายความว่าข้อความ (และการตอบกลับที่ผู้รับระบุ) สามารถมีค่า JSON ที่ถูกต้อง (null บูลีน ตัวเลข สตริง อาร์เรย์ หรือออบเจ็กต์) ระบบจะบังคับให้ค่าอื่นๆ เป็นค่าที่ทำให้เป็นอนุกรมได้
โปรดทราบว่าการดำเนินการนี้แตกต่างจากเบราว์เซอร์อื่นๆ ที่ใช้ API เดียวกันกับอัลกอริทึมการโคลนที่มีโครงสร้าง
อายุการใช้งานของพอร์ต
พอร์ตได้รับการออกแบบให้เป็นกลไกการสื่อสารแบบ 2 ทางระหว่างส่วนต่างๆ
ของส่วนขยาย เมื่อส่วนหนึ่งของส่วนขยายเรียกใช้
tabs.connect()
, runtime.connect()
หรือ
runtime.connectNative()
ระบบจะสร้าง
พอร์ตที่ส่งข้อความได้ทันทีโดยใช้
postMessage()
หากมีหลายเฟรมในแท็บ การเรียก tabs.connect()
จะเรียกใช้เหตุการณ์ runtime.onConnect
1 ครั้งสำหรับแต่ละเฟรมในแท็บ ในทํานองเดียวกัน หากมีการเรียกใช้
runtime.connect()
เหตุการณ์ onConnect
จะทํางานได้ 1 ครั้งต่อเฟรมในกระบวนการของส่วนขยาย
คุณอาจต้องการทราบเมื่อการเชื่อมต่อปิดลง เช่น หากคุณรักษาสถานะแยกต่างหากสำหรับแต่ละพอร์ตที่เปิดอยู่ โดยให้ฟังเหตุการณ์ runtime.Port.onDisconnect
เหตุการณ์นี้จะทริกเกอร์เมื่อไม่มีพอร์ตที่ถูกต้องที่ปลายอีกด้านของแชแนล ซึ่งอาจเกิดจากสาเหตุต่อไปนี้
- ไม่มีผู้ฟังสำหรับ
runtime.onConnect
ที่ปลายสาย - ระบบจะยกเลิกการโหลดแท็บที่มีพอร์ต (เช่น หากมีการไปยังส่วนต่างๆ ในแท็บ)
- เฟรมที่เรียกใช้
connect()
ได้เลิกโหลดแล้ว - เฟรมทั้งหมดที่ได้รับพอร์ต (ผ่าน
runtime.onConnect
) ได้ยกเลิกการโหลดแล้ว runtime.Port.disconnect()
จะถูกเรียกใช้โดยปลายทางอีกฝั่ง หากการโทรผ่านconnect()
ทำให้มีการโอนหลายครั้งที่ฝั่งผู้รับ และมีการโทรหาdisconnect()
ในพอร์ตใดพอร์ตหนึ่งเหล่านี้ เหตุการณ์onDisconnect
จะทริกเกอร์เฉพาะที่พอร์ตส่งเท่านั้น ไม่ใช่ที่พอร์ตอื่นๆ
การรับส่งข้อความข้ามส่วนขยาย
นอกเหนือจากการส่งข้อความระหว่างคอมโพเนนต์ต่างๆ ในส่วนขยายแล้ว คุณยังใช้ Messaging API เพื่อสื่อสารกับส่วนขยายอื่นๆ ได้ด้วย ซึ่งจะช่วยให้คุณเปิดเผย API สาธารณะเพื่อให้ส่วนขยายอื่นๆ ใช้ได้
หากต้องการฟังคำขอและการเชื่อมต่อขาเข้าจากส่วนขยายอื่นๆ ให้ใช้วิธี
runtime.onMessageExternal
หรือ runtime.onConnectExternal
ตัวอย่างของแต่ละรายการมีดังนี้
service-worker.js
// For a single request:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id !== allowlistedExtension) {
return; // don't allow this extension access
}
if (request.getTargetData) {
sendResponse({ targetData: targetData });
} else if (request.activateLasers) {
const 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.
});
});
หากต้องการส่งข้อความไปยังส่วนขยายอื่น ให้ส่งรหัสของส่วนขยายที่ต้องการสื่อสารด้วยดังนี้
service-worker.js
// The ID of the extension we want to talk to.
const laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// For a minimal request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// For a long-lived connection:
const port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
ส่งข้อความจากหน้าเว็บ
ส่วนขยายยังรับและตอบกลับข้อความจากหน้าเว็บได้ด้วย หากต้องการส่งข้อความจากหน้าเว็บไปยังส่วนขยาย ให้ระบุใน manifest.json
ว่าต้องการอนุญาตให้เว็บไซต์ใดส่งข้อความโดยใช้คีย์ไฟล์ Manifest "externally_connectable"
เช่น
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
ซึ่งจะเปิดเผย API การรับส่งข้อความไปยังหน้าเว็บใดก็ตามที่ตรงกับรูปแบบ URL ที่คุณระบุ รูปแบบ URL ต้องมีโดเมนระดับที่ 2 อย่างน้อย 1 รายการ นั่นคือระบบไม่รองรับรูปแบบชื่อโฮสต์ เช่น "*" "*.com" "*.co.uk" และ "*.appspot.com" คุณใช้
<all_urls>
เพื่อเข้าถึงโดเมนทั้งหมดได้
ใช้ API runtime.sendMessage()
หรือ runtime.connect()
เพื่อส่ง
ข้อความไปยังส่วนขยายที่เฉพาะเจาะจง เช่น
webpage.js
// The ID of the extension we want to talk to.
const editorExtensionId = 'abcdefghijklmnoabcdefhijklmnoabc';
// Check if extension is installed
if (chrome && chrome.runtime) {
// Make a request:
chrome.runtime.sendMessage(
editorExtensionId,
{
openUrlInEditor: url
},
(response) => {
if (!response.success) handleError(url);
}
);
}
จากส่วนขยาย ให้รับฟังข้อความจากหน้าเว็บโดยใช้ API runtime.onMessageExternal
หรือ runtime.onConnectExternal
ตามที่ระบุไว้ในการรับส่งข้อความระหว่างส่วนขยาย เช่น
service-worker.js
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);
});
คุณไม่สามารถส่งข้อความจากส่วนขยาย ไปยังหน้าเว็บได้
การรับส่งข้อความแบบเดิม
ส่วนขยายแลกเปลี่ยนข้อความกับแอปพลิเคชันเนทีฟที่ลงทะเบียนเป็นโฮสต์การรับส่งข้อความเนทีฟได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่การรับส่งข้อความเนทีฟ
ข้อควรพิจารณาด้านความปลอดภัย
ข้อควรพิจารณาด้านความปลอดภัยบางประการที่เกี่ยวข้องกับการรับส่งข้อความมีดังนี้
สคริปต์เนื้อหาน่าเชื่อถือน้อยกว่า
สคริปต์เนื้อหาน่าเชื่อถือน้อยกว่า Service Worker ของส่วนขยาย ตัวอย่างเช่น หน้าเว็บที่เป็นอันตรายอาจทำให้กระบวนการแสดงผลที่เรียกใช้สคริปต์เนื้อหาถูกบุกรุก พึงระลึกว่าข้อความจาก Content Script อาจถูกสร้างขึ้นโดยผู้โจมตี และอย่าลืมตรวจสอบและล้างข้อมูลอินพุตทั้งหมด พึงระลึกว่าข้อมูลใดก็ตามที่ส่งไปยัง Content Script อาจรั่วไหลไปยังหน้าเว็บ จำกัดขอบเขตของการดำเนินการที่มีสิทธิ์ซึ่งทริกเกอร์ได้โดยข้อความที่ได้รับจาก Content Scripts
Cross-site Scripting
อย่าลืมปกป้องสคริปต์จากการเขียนสคริปต์ข้ามเว็บไซต์ เมื่อได้รับข้อมูลจากแหล่งที่ไม่น่าเชื่อถือ เช่น ข้อมูลที่ผู้ใช้ป้อน เว็บไซต์อื่นๆ ผ่าน Content Script หรือ API ให้ระมัดระวังอย่าตีความข้อมูลนี้เป็น HTML หรือใช้ในลักษณะที่อาจอนุญาตให้โค้ดที่ไม่คาดคิดทำงานได้
ใช้ API ที่ไม่เรียกใช้สคริปต์เมื่อเป็นไปได้
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse doesn't evaluate the attacker's scripts. const resp = JSON.parse(response.farewell); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // innerText does not let the attacker inject HTML elements. document.getElementById("resp").innerText = response.farewell; });
หลีกเลี่ยงการใช้วิธีการต่อไปนี้ซึ่งจะทำให้ส่วนขยายของคุณมีความเสี่ยง
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be evaluating a malicious script! const resp = eval(`(${response.farewell})`); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be injecting a malicious script! document.getElementById("resp").innerHTML = response.farewell; });