การส่งข้อความ

เนื่องจากสคริปต์เนื้อหาทำงานในบริบทของหน้าเว็บ ไม่ใช่ส่วนขยาย สคริปต์เหล่านี้จึงมักต้องมี ในการสื่อสารกับส่วนขยายอื่นๆ ได้ ตัวอย่างเช่น ส่วนขยายโปรแกรมอ่าน RSS อาจใช้ สคริปต์เนื้อหาเพื่อตรวจหาการมีอยู่ของฟีด RSS ในหน้าหนึ่งๆ แล้วแจ้งให้หน้าพื้นหลังใน เพื่อแสดงไอคอนการดำเนินการของหน้าเว็บสำหรับหน้านั้น

การสื่อสารระหว่างส่วนขยายและสคริปต์เนื้อหาทำงานโดยใช้การส่งผ่านข้อความ อย่างใดอย่างหนึ่ง จะคอยฟังข้อความที่ส่งจากปลายสาย และตอบกลับในช่องเดียวกันได้ ข้อความสามารถ มีออบเจ็กต์ JSON ที่ถูกต้อง (null, บูลีน, ตัวเลข, สตริง, อาร์เรย์ หรือออบเจ็กต์) มีเมนูง่ายๆ API สำหรับคำขอแบบครั้งเดียว และ API ที่ซับซ้อนมากขึ้นซึ่งทำให้คุณมีอายุการใช้งานที่ยาวนาน การเชื่อมต่อสำหรับการแลกเปลี่ยนข้อความหลายข้อความกับบริบทร่วมกัน หรืออาจส่ง ข้อความไปยังส่วนขยายอื่นหากคุณทราบ ID ของส่วนขยายนั้น ซึ่งระบุไว้ในส่วนขยายไขว้ ข้อความ

คำของ่ายๆ แบบครั้งเดียว

หากคุณต้องการส่งข้อความเดียวไปยังอีกส่วนหนึ่งของส่วนขยายเท่านั้น (และเลือกรับ ตอบกลับ) คุณควรใช้ runtime.sendMessage หรือ tabs.sendMessage แบบง่าย ตัวเลือกนี้ช่วยให้คุณสามารถส่งข้อความที่แยกเป็น JSON ได้แบบครั้งเดียวจากสคริปต์เนื้อหาไปยังส่วนขยาย หรือจากสคริปต์เนื้อหาไปยังส่วนขยาย ในทางกลับกัน ตามลำดับ พารามิเตอร์ Callback ที่ไม่บังคับให้คุณจัดการการตอบสนองจาก ถ้ามี

การส่งคำขอจากสคริปต์เนื้อหาจะมีลักษณะดังนี้

chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

การส่งคำขอจากส่วนขยายไปยังสคริปต์เนื้อหาดูคล้ายกันมาก แต่คุณจำเป็นต้องทำ ระบุแท็บที่จะส่งไป ตัวอย่างนี้แสดงให้เห็นการส่งข้อความถึงสคริปต์เนื้อหา ในแท็บที่เลือกไว้

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

หมายเหตุ: หากมีหน้าเว็บหลายหน้าที่กำลังรับข้อมูลเหตุการณ์ onMessage จะมีเพียงรายการแรกที่เรียกใช้ sendResponse() สำหรับเหตุการณ์ใดเหตุการณ์หนึ่งเท่านั้นที่จะส่งการตอบกลับได้สำเร็จ และจะไม่สนใจการตอบกลับอื่นๆ ทั้งหมดสำหรับเหตุการณ์นั้น
หมายเหตุ: การเรียกกลับ sendResponse จะใช้ได้หากใช้แบบซิงโครนัสเท่านั้น หรือหากเครื่องจัดการเหตุการณ์แสดงผล true เพื่อระบุว่าจะตอบสนองแบบไม่พร้อมกัน ระบบจะเรียกใช้ Callback ของฟังก์ชัน sendMessage โดยอัตโนมัติหากไม่มีตัวแฮนเดิลที่ส่งกลับ True หรือหาก Callback ของ sendResponse มีการเก็บข้อมูลที่ไม่ถูกต้อง

การเชื่อมต่อที่อยู่เป็นเวลานาน

บางครั้งการสนทนาที่นานกว่าคำขอหรือการตอบกลับ 1 รายการก็มีประโยชน์ ในกรณีนี้ คุณสามารถเปิดช่องที่มีการเผยแพร่มาอย่างยาวนานจากสคริปต์เนื้อหาไปยังหน้าส่วนขยาย หรือ ในทางกลับกัน โดยใช้ runtime.connect หรือ tabs.connect ตามลำดับ ช่องสามารถ ชื่อ (ไม่บังคับ) ซึ่งช่วยให้คุณแยกความแตกต่างระหว่างการเชื่อมต่อประเภทต่างๆ ได้

กรณีการใช้งานหนึ่งอาจเป็นส่วนขยายการกรอกแบบฟอร์มอัตโนมัติ สคริปต์เนื้อหาจะเปิดช่องเป็น หน้าส่วนขยายสำหรับการเข้าสู่ระบบหนึ่งๆ และส่งข้อความไปยังส่วนขยายสำหรับอินพุตแต่ละรายการ บนหน้าเว็บเพื่อขอให้กรอกข้อมูล การเชื่อมต่อที่ใช้ร่วมกันช่วยให้ส่วนขยาย เพื่อให้สถานะที่แชร์เชื่อมโยงข้อความต่างๆ ที่มาจากสคริปต์เนื้อหาต่อไป

เมื่อสร้างการเชื่อมต่อ แต่ละฝั่งจะได้รับออบเจ็กต์ 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"});
});

การส่งคำขอจากส่วนขยายไปยังสคริปต์เนื้อหาดูคล้ายกันมาก แต่คุณจำเป็นต้องทำ ระบุแท็บที่จะเชื่อมต่อ เพียงแทนที่การเรียกใช้เพื่อเชื่อมต่อในตัวอย่างด้านบนด้วย tabs.connect.

คุณต้องตั้งค่าเหตุการณ์ runtime.onConnect เพื่อจัดการการเชื่อมต่อขาเข้า Listener ซึ่งมีลักษณะเหมือนกันจากสคริปต์เนื้อหาหรือหน้าส่วนขยาย เมื่อส่วนอื่นของ ส่วนขยายเรียกใช้ "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 พอร์ต แล้ว พอร์ตนี้สามารถใช้ส่งข้อความให้กับปลายสายได้ทันที 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 จะเริ่มทำงานที่พอร์ตของผู้ส่งเท่านั้น และจะไม่เริ่มทำงานที่พอร์ตอื่น

การรับส่งข้อความข้ามส่วนขยาย

นอกจากการส่งข้อความระหว่างคอมโพเนนต์ต่างๆ ในส่วนขยายแล้ว คุณยังสามารถใช้เมธอด 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 เป็นอย่างน้อย นั่นคือรูปแบบชื่อโฮสต์ เช่น "*", "*.com", "*.co.uk" และ "*.appspot.com" เป็นสิ่งต้องห้าม จากหน้าเว็บ ให้ใช้เมธอด runtime.sendMessage หรือ runtime.connect API เพื่อส่งข้อความไปยังแอปที่ต้องการ ส่วนขยาย เช่น

// 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);
  });

จากแอปหรือส่วนขยาย คุณสามารถฟังข้อความจากหน้าเว็บผ่าน runtime.onMessageExternal หรือ runtime.onConnectExternal API ที่คล้ายกับ cross-extension มีเพียงหน้าเว็บเท่านั้นที่สามารถเริ่มการเชื่อมต่อ มีตัวอย่างดังต่อไปนี้

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);
  });

การรับส่งข้อความของระบบ

ส่วนขยายและแอปสามารถแลกเปลี่ยนข้อความกับแอปพลิเคชันที่มาพร้อมเครื่องซึ่งลงทะเบียนเป็น โฮสต์การรับส่งข้อความในเครื่อง ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่การรับส่งข้อความในเครื่อง

ข้อควรพิจารณาด้านความปลอดภัย

สคริปต์เนื้อหามีความน่าเชื่อถือน้อยกว่า

สคริปต์เนื้อหามีความน่าเชื่อถือน้อยกว่าหน้าเว็บพื้นหลังของส่วนขยาย (เช่น เว็บที่เป็นอันตราย อาจทำให้ขั้นตอนของตัวแสดงผลที่สคริปต์เนื้อหาทำงาน) สมมติว่า ข้อความจากสคริปต์เนื้อหาอาจสร้างขึ้นโดยผู้โจมตี และตรวจสอบว่าได้ตรวจสอบ ปรับปรุงอินพุตทั้งหมด สมมติว่าข้อมูลที่ส่งไปยังสคริปต์เนื้อหาอาจรั่วไหลไปยังหน้าเว็บ จำกัดขอบเขตของการดำเนินการที่มีสิทธิ์ซึ่งสามารถทริกเกอร์โดยข้อความที่ได้รับจากเนื้อหา สคริปต์

Cross-site Scripting

เมื่อได้รับข้อความจากสคริปต์เนื้อหาหรือส่วนขยายอื่น สคริปต์ของคุณควรระมัดระวัง ไม่ให้ตกเป็นเหยื่อของการเขียนสคริปต์ข้ามเว็บไซต์ คุณสามารถใช้คำแนะนำนี้ได้กับสคริปต์ที่ทำงานภายในโค้ด หน้าพื้นหลังของส่วนขยาย ตลอดจนสคริปต์เนื้อหาที่ทำงานอยู่ภายในต้นทางเว็บอื่นๆ กล่าวอย่างเจาะจงคือ ให้หลีกเลี่ยงการใช้ 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 สามารถสื่อสารกับ แอปที่มาพร้อมเครื่อง สำหรับตัวอย่างเพิ่มเติมและความช่วยเหลือในการดูซอร์สโค้ด โปรดดูที่ตัวอย่าง