เนื่องจากสคริปต์เนื้อหาทำงานในบริบทของหน้าเว็บ ไม่ใช่ส่วนขยาย จึงมักต้องมีวิธีสื่อสารกับส่วนอื่นๆ ของส่วนขยาย ตัวอย่างเช่น ส่วนขยายโปรแกรมอ่าน RSS อาจใช้ Content Script เพื่อตรวจหาฟีด RSS ในหน้าเว็บ จากนั้นจะแจ้งหน้าพื้นหลัง เพื่อแสดงไอคอนการดำเนินการในหน้าสำหรับหน้านั้น
การสื่อสารระหว่างส่วนขยายกับสคริปต์เนื้อหาจะทำงานโดยใช้การส่งข้อความ ทั้ง 2 ฝ่ายสามารถรอฟังข้อความที่ส่งจากอีกฝ่าย และตอบกลับในช่องทางเดียวกันได้ ข้อความมีออบเจ็กต์ JSON ที่ถูกต้อง (null, บูลีน, ตัวเลข, สตริง, อาร์เรย์ หรือออบเจ็กต์) ได้ มี API แบบง่ายสำหรับคำขอแบบครั้งเดียว และ API ที่ซับซ้อนกว่าซึ่งช่วยให้คุณมีการเชื่อมต่อที่ใช้งานได้นานสำหรับการแลกเปลี่ยนข้อความหลายรายการที่มีบริบทที่ใช้ร่วมกัน นอกจากนี้ คุณยังส่งข้อความไปยังส่วนขยายอื่นได้หากทราบรหัสของส่วนขยายนั้น ซึ่งจะกล่าวถึงในส่วนข้อความข้ามส่วนขยาย
คำขอแบบครั้งเดียวอย่างง่าย
หากคุณเพียงต้องการส่งข้อความเดียวไปยังส่วนอื่นของส่วนขยาย (และรับการตอบกลับด้วยหรือไม่ก็ได้) คุณควรใช้ runtime.sendMessage หรือ tabs.sendMessage แบบง่าย ซึ่งช่วยให้คุณส่งข้อความที่แปลงเป็น JSON ได้แบบครั้งเดียวจาก Content Script ไปยังส่วนขยาย หรือในทางกลับกัน พารามิเตอร์การเรียกกลับที่ไม่บังคับช่วยให้คุณจัดการการตอบกลับจากอีกฝั่งได้ หากมี
การส่งคำขอจาก Content Script จะมีลักษณะดังนี้
chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
การส่งคำขอจากส่วนขยายไปยัง Content Script จะคล้ายกันมาก เพียงแต่คุณต้อง ระบุแท็บที่จะส่งคำขอไป ตัวอย่างนี้แสดงการส่งข้อความไปยัง Content Script ในแท็บที่เลือก
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
ในฝั่งที่รับ คุณต้องตั้งค่า Listener เหตุการณ์ runtime.onMessage เพื่อจัดการข้อความ ซึ่งจะดูเหมือนกันจากสคริปต์เนื้อหาหรือหน้าส่วนขยาย
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"});
}
);
ในตัวอย่างด้านบน ระบบจะเรียกใช้ sendResponse แบบพร้อมกัน หากต้องการใช้
sendResponseแบบอะซิงโครนัส ให้เพิ่ม return true; ลงในเครื่องจัดการเหตุการณ์ onMessage
sendResponse Callback จะใช้ได้ก็ต่อเมื่อใช้แบบซิงโครนัส หรือหากตัวแฮนเดิลเหตุการณ์แสดงผล true เพื่อระบุว่าจะตอบกลับแบบไม่พร้อมกัน ระบบจะเรียกใช้ Callback ของฟังก์ชัน sendMessage โดยอัตโนมัติหากไม่มีแฮนเดิลใดแสดงผลเป็นจริง หรือหากมีการเก็บขยะใน Callback ของ sendResponseการเชื่อมต่อที่มีอายุยาวนาน
บางครั้งการสนทนาที่ยาวนานกว่าคำขอและการตอบกลับครั้งเดียวก็มีประโยชน์ ในกรณีนี้ คุณสามารถเปิดแชแนลที่มีอายุการใช้งานยาวนานจากสคริปต์เนื้อหาไปยังหน้าส่วนขยาย หรือในทางกลับกันได้โดยใช้ runtime.connect หรือ tabs.connect ตามลำดับ ช่องอาจมีชื่อ (ไม่บังคับ) ซึ่งช่วยให้คุณแยกความแตกต่างระหว่างการเชื่อมต่อประเภทต่างๆ ได้
กรณีการใช้งานหนึ่งอาจเป็นส่วนขยายการกรอกแบบฟอร์มอัตโนมัติ Content Script สามารถเปิดช่องทางไปยังหน้าส่วนขยายสำหรับการเข้าสู่ระบบที่เฉพาะเจาะจง และส่งข้อความไปยังส่วนขยายสำหรับองค์ประกอบอินพุตแต่ละรายการในหน้าเว็บเพื่อขอข้อมูลแบบฟอร์มที่จะกรอก การเชื่อมต่อที่แชร์ช่วยให้ส่วนขยาย รักษาสถานะที่แชร์ซึ่งเชื่อมโยงข้อความหลายรายการที่มาจาก Content Script ได้
เมื่อสร้างการเชื่อมต่อ ปลายทางแต่ละรายการจะได้รับออบเจ็กต์ runtime.Port ซึ่งใช้สำหรับ การส่งและรับข้อความผ่านการเชื่อมต่อนั้น
วิธีเปิดแชแนลจากสคริปต์เนื้อหา รวมถึงวิธีส่งและฟังข้อความมีดังนี้
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"});
});
การส่งคำขอจากส่วนขยายไปยัง Content Script จะมีลักษณะคล้ายกันมาก เพียงแต่คุณต้อง ระบุแท็บที่จะเชื่อมต่อ เพียงแทนที่การเรียกใช้เพื่อเชื่อมต่อในตัวอย่างด้านบนด้วย tabs.connect
หากต้องการจัดการการเชื่อมต่อขาเข้า คุณต้องตั้งค่า Listener เหตุการณ์ runtime.onConnect ซึ่งจะดูเหมือนกันจากสคริปต์เนื้อหาหรือหน้าส่วนขยาย เมื่อส่วนอื่นของส่วนขยายเรียกใช้ "connect()" ระบบจะทริกเกอร์เหตุการณ์นี้พร้อมกับออบเจ็กต์ runtime.Port ที่คุณใช้เพื่อส่งและรับข้อความผ่านการเชื่อมต่อได้ การตอบกลับการเชื่อมต่อขาเข้ามีลักษณะดังนี้
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."});
});
});
อายุการใช้งานของพอร์ต
พอร์ตได้รับการออกแบบให้เป็นวิธีการสื่อสารแบบ 2 ทางระหว่างส่วนต่างๆ ของส่วนขยาย โดยที่เฟรม (ระดับบนสุด) จะถือเป็นส่วนที่เล็กที่สุด เมื่อเรียกใช้ tabs.connect, runtime.connect หรือ runtime.connectNative ระบบจะสร้าง Port คุณสามารถใช้พอร์ตนี้เพื่อส่งข้อความไปยังปลายทางอื่นผ่าน postMessage ได้ทันที
หากมีหลายเฟรมในแท็บ การเรียกใช้ tabs.connect จะส่งผลให้มีการเรียกใช้เหตุการณ์ runtime.onConnect หลายครั้ง (ครั้งละ 1 เฟรมในแท็บ) ในทำนองเดียวกัน หากใช้ runtime.connect ระบบอาจเรียกใช้เหตุการณ์ onConnect หลายครั้ง (ครั้งละ 1 ครั้งสำหรับทุกเฟรมในกระบวนการของส่วนขยาย)
คุณอาจต้องการทราบเมื่อมีการปิดการเชื่อมต่อ เช่น หากคุณดูแลสถานะแยกต่างหากสำหรับแต่ละพอร์ตที่เปิดอยู่ คุณสามารถฟังเหตุการณ์ runtime.Port.onDisconnect เพื่อดำเนินการนี้ ระบบจะทริกเกอร์ เหตุการณ์นี้เมื่อไม่มีพอร์ตที่ถูกต้องที่อีกด้านของแชแนล ซึ่งจะเกิดขึ้นในสถานการณ์ต่อไปนี้
- ไม่มี Listener สำหรับ runtime.onConnect ที่ปลายทางอีกด้าน
- ระบบจะยกเลิกการโหลดแท็บที่มีพอร์ต (เช่น หากมีการไปยังส่วนต่างๆ ในแท็บ)
- เฟรมที่เรียกใช้
connectได้โหลดออกแล้ว - เฟรมทั้งหมดที่ได้รับพอร์ต (ผ่าน runtime.onConnect) ได้ยกเลิกการโหลดแล้ว
- runtime.Port.disconnect จะเรียกใช้โดยอีกฝั่ง โปรดทราบว่าหาก
connectการโทรส่งผล ให้มีการย้ายหลายครั้งที่ฝั่งผู้รับ และมีการเรียกใช้disconnect()ในพอร์ตใดพอร์ตหนึ่งเหล่านี้ เหตุการณ์onDisconnectจะทริกเกอร์เฉพาะที่พอร์ตของผู้ส่งเท่านั้น ไม่ใช่ที่พอร์ตอื่นๆ
การรับส่งข้อความข้ามส่วนขยาย
นอกเหนือจากการส่งข้อความระหว่างคอมโพเนนต์ต่างๆ ในส่วนขยายแล้ว คุณยังใช้ Messaging API เพื่อสื่อสารกับส่วนขยายอื่นๆ ได้ด้วย ซึ่งช่วยให้คุณเปิดเผย API สาธารณะที่ส่วนขยายอื่นๆ ใช้ประโยชน์ได้
การรอรับคำขอและการเชื่อมต่อขาเข้าจะคล้ายกับกรณีภายใน ยกเว้นว่าคุณจะใช้วิธี runtime.onMessageExternal หรือ runtime.onConnectExternal ตัวอย่างของแต่ละรายการมีดังนี้
// 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.
});
});
ในทำนองเดียวกัน การส่งข้อความไปยังส่วนขยายอื่นก็คล้ายกับการส่งข้อความภายในส่วนขยายของคุณ ความแตกต่างเพียงอย่างเดียวคือคุณต้องส่งรหัสของส่วนขยายที่ต้องการสื่อสารด้วย เช่น
// 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(...);
การส่งข้อความจากหน้าเว็บ
แอปหรือส่วนขยายจะรับและตอบกลับข้อความจากหน้าเว็บปกติได้เช่นเดียวกับการรับส่งข้อความข้ามส่วนขยาย หากต้องการใช้ฟีเจอร์นี้ คุณต้องระบุใน manifest.json ก่อน ว่าต้องการสื่อสารกับเว็บไซต์ใด เช่น
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
ซึ่งจะเปิดเผย API การรับส่งข้อความต่อหน้าเว็บที่ตรงกับรูปแบบ URL ที่คุณระบุ รูปแบบ URL ต้องมีโดเมนระดับที่ 2 อย่างน้อย 1 รายการ ซึ่งหมายความว่าห้ามใช้รูปแบบชื่อโฮสต์ เช่น "*" "*.com" "*.co.uk" และ "*.appspot.com" จากหน้าเว็บ ให้ใช้ API runtime.sendMessage หรือ runtime.connect เพื่อส่งข้อความไปยังแอปหรือ ส่วนขยายที่เฉพาะเจาะจง เช่น
// 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);
});
จากแอปหรือส่วนขยาย คุณอาจฟังข้อความจากหน้าเว็บผ่าน API runtime.onMessageExternal มีเพียงหน้าเว็บเท่านั้นที่เริ่มการเชื่อมต่อได้ มีตัวอย่างดังต่อไปนี้
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);
});
การรับส่งข้อความแบบเดิม
ส่วนขยายและแอปแลกเปลี่ยนข้อความกับแอปพลิเคชันแบบเนทีฟที่ลงทะเบียนเป็นโฮสต์การรับส่งข้อความในเครื่องได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่การรับส่งข้อความเนทีฟ
ข้อควรพิจารณาด้านความปลอดภัย
สคริปต์เนื้อหาน่าเชื่อถือน้อยกว่า
สคริปต์เนื้อหามีความน่าเชื่อถือน้อยกว่าหน้าพื้นหลังของส่วนขยาย (เช่น หน้าเว็บที่เป็นอันตรายอาจทำให้กระบวนการแสดงผลที่สคริปต์เนื้อหาทำงานอยู่ถูกบุกรุกได้) สมมติว่าข้อความจาก Content Script อาจสร้างขึ้นโดยผู้โจมตี และตรวจสอบว่าได้ตรวจสอบและ ล้างข้อมูลอินพุตทั้งหมดแล้ว พึงระลึกว่าข้อมูลใดก็ตามที่ส่งไปยัง Content Script อาจรั่วไหลไปยังหน้าเว็บ จำกัดขอบเขตของการดำเนินการที่มีสิทธิ์ซึ่งทริกเกอร์ได้โดยข้อความที่ได้รับจาก Content Scripts
Cross-site Scripting
เมื่อได้รับข้อความจาก Content Script หรือส่วนขยายอื่น สคริปต์ของคุณควรระมัดระวัง ไม่ให้ตกเป็นเหยื่อของCross-Site Scripting คำแนะนำนี้ใช้กับสคริปต์ที่ทำงานภายใน หน้าพื้นหลังของส่วนขยาย รวมถึงสคริปต์เนื้อหาที่ทำงานภายในต้นทางเว็บอื่นๆ โดยเฉพาะอย่างยิ่ง ให้หลีกเลี่ยงการใช้ API ที่เป็นอันตราย เช่น API ต่อไปนี้
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;
});
แต่ให้ใช้ API ที่ปลอดภัยกว่าซึ่งไม่เรียกใช้สคริปต์แทน
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;
});
ตัวอย่าง
คุณดูตัวอย่างการสื่อสารผ่านข้อความอย่างง่ายได้ในไดเรกทอรี examples/api/messaging ตัวอย่างการรับส่งข้อความดั้งเดิมแสดงให้เห็นว่าแอป Chrome สื่อสารกับแอปที่มาพร้อมเครื่องได้อย่างไร ดูตัวอย่างเพิ่มเติมและรับความช่วยเหลือในการดูซอร์สโค้ดได้ที่ตัวอย่าง