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

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 browser.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

คำตอบ

หากต้องการฟังข้อความ ให้ใช้เหตุการณ์ browser.runtime.onMessage

// Event listener
function handleMessages(message, sender, sendResponse) {
  if (message !== 'get-status') return;

  fetch('https://example.com')
    .then((response) => sendResponse({statusCode: response.status}))

  // Since `fetch` is asynchronous, must return an explicit `true`
  return true;
}

browser.runtime.onMessage.addListener(handleMessages);

// From the sender's context...
const {statusCode} = await browser.runtime.sendMessage('get-status');

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

หากคุณเรียกใช้ sendResponse โดยไม่มีพารามิเตอร์ ระบบจะส่ง null เป็นการตอบกลับ

หากต้องการส่งการตอบกลับแบบไม่พร้อมกัน คุณมี 2 ตัวเลือก ได้แก่ การส่งคืน true หรือ การส่งคืน Promise

การคืนสินค้า true

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

ส่งคืน Promise

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

หาก Promise แก้ไขแล้ว ระบบจะส่งค่าที่แก้ไขแล้วเป็น การตอบกลับ

หากคำสัญญาถูกปฏิเสธ ระบบจะปฏิเสธการเรียก sendMessage() ของผู้ส่ง พร้อมข้อความของข้อผิดพลาด ดูรายละเอียดและตัวอย่างเพิ่มเติมได้ที่ส่วนการจัดการข้อผิดพลาด

ตัวอย่างที่แสดงการคืนค่า Promise ที่อาจส่งผลให้มีการแก้ไขหรือปฏิเสธ

// Event listener
function handleMessages(message, sender, sendResponse) {
  // Return a promise that wraps fetch
  // If the response is OK, resolve with the status. If it's not OK then reject
  // with the network error that prevents the fetch from completing.
  return new Promise((resolve, reject) => {
    fetch('https://example.com')
      .then(response => {
        if (!response.ok) {
          reject(response);
        } else {
          resolve(response.status);
        }
      })
      .catch(error => {
        reject(error);
      });
  });
}
browser.runtime.onMessage.addListener(handleMessages);

นอกจากนี้ คุณยังประกาศ Listener เป็น async เพื่อส่งคืน Promise ได้ด้วย

browser.runtime.onMessage.addListener(async function(message, sender) {
  const response = await fetch('https://example.com');
  if (!response.ok) {
    // rejects the promise returned by `async function`.
    throw new Error(`Fetch failed: ${response.status}`);
  }
  // resolves the promise returned by `async function`.
  return {statusCode: response.status};
});

ส่งคืน Promise: asyncข้อควรระวังเกี่ยวกับฟังก์ชัน

โปรดทราบว่าฟังก์ชัน async ในฐานะ Listener จะแสดงผลเป็น Promise เสมอ แม้จะไม่มีคำสั่ง return ก็ตาม หาก Listener async ไม่ได้ ส่งคืนค่า สัญญาของ Listener จะเปลี่ยนเป็น undefined โดยปริยาย และระบบจะส่ง null เป็นคำตอบไปยังผู้ส่ง ซึ่งอาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิดเมื่อมี Listener หลายรายการ ดังนี้

// content_script.js
function handleResponse(message) {
    // The first listener promise resolves to `undefined` before the second
    // listener can respond. When a listener responds with `undefined`, Chrome
    // sends null as the response.
    console.assert(message === null);
}
function notifyBackgroundPage() {
    const sending = browser.runtime.sendMessage('test');
    sending.then(handleResponse);
}
notifyBackgroundPage();

// background.js
browser.runtime.onMessage.addListener(async (message) => {
    // This just causes the function to pause for a millisecond, but the promise
    // is *not* returned from the listener so it doesn't act as a response.
    await new Promise(resolve => {
        setTimeout(resolve, 1, 'OK');
    });
   // `async` functions always return promises. So once we
   // reach here there is an implicit `return undefined;`. Chrome translates
   // `undefined` responses to `null`.
});

browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  return new Promise((resolve) => {
    setTimeout(resolve, 1000, 'response');
  });
});

การจัดการข้อผิดพลาด

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

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

หาก Listener แสดงผล Promise ที่ปฏิเสธ จะต้องปฏิเสธด้วยอินสแตนซ์ Error เพื่อให้ผู้ส่งได้รับข้อความแสดงข้อผิดพลาดนั้น หากมีการปฏิเสธ Promise ด้วยค่าอื่น (เช่น null หรือ undefined) sendMessage() จะถูกปฏิเสธด้วยข้อความแสดงข้อผิดพลาดทั่วไปแทน

หากลงทะเบียนผู้ฟังหลายคนสำหรับ onMessage จะมีเพียงผู้ฟังคนแรกที่ตอบกลับ ปฏิเสธ หรือแสดงข้อผิดพลาดเท่านั้นที่จะส่งผลต่อผู้ส่ง ส่วนผู้ฟังคนอื่นๆ จะทำงาน แต่ระบบจะไม่สนใจผลลัพธ์

ตัวอย่าง

หาก Listener ส่งคืน Promise ที่ปฏิเสธ sendMessage() จะถูกปฏิเสธ

// sender.js
try {
  await browser.runtime.sendMessage('test');
} catch (e) {
  console.log(e.message); // "some error"
}

// listener.js
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  return Promise.reject(new Error('some error'));
});

หากผู้ฟังตอบกลับด้วยค่าที่แปลงเป็นอนุกรมไม่ได้ ระบบจะปฏิเสธ sendMessage()

// sender.js
try {
  await browser.runtime.sendMessage('test');
} catch (e) {
  console.log(e.message); // "Error: Could not serialize message."
}

// listener.js
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse(() => {}); // Functions are not serializable
  return true; // Keep channel open for async sendResponse
});

หากผู้ฟังแสดงข้อผิดพลาดแบบซิงโครนัสก่อนที่ผู้ฟังรายอื่นจะตอบกลับ ระบบจะปฏิเสธพรอมิสของsendMessage()ผู้ฟัง

// sender.js
try {
  await browser.runtime.sendMessage('test');
} catch (e) {
  console.log(e.message); // "error!"
}

// listener.js
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  throw new Error('error!');
});

อย่างไรก็ตาม หากผู้ฟังคนหนึ่งตอบกลับก่อนที่อีกคนจะส่งข้อผิดพลาด sendMessage() จะสำเร็จ

// sender.js
const response = await browser.runtime.sendMessage('test');
console.log(response); // "OK"

// listener.js
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  sendResponse('OK');
});
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
  throw new Error('error!');
});

การเชื่อมต่อที่มีอายุการใช้งานยาวนาน

หากต้องการสร้างช่องการส่งข้อความแบบมีอายุยาวนานที่ใช้ซ้ำได้ ให้เรียกใช้

  • runtime.connect() เพื่อส่งข้อความจากสคริปต์เนื้อหา ไปยังหน้าส่วนขยาย
  • tabs.connect() เพื่อส่งข้อความจากหน้าส่วนขยายไปยัง Content Script

คุณตั้งชื่อช่องได้โดยส่งพารามิเตอร์ options ที่มีคีย์ name เพื่อ แยกความแตกต่างระหว่างการเชื่อมต่อประเภทต่างๆ ดังนี้

const port = browser.runtime.connect({name: "example"});

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

เมื่อสร้างการเชื่อมต่อ ระบบจะกำหนดออบเจ็กต์ runtime.Port ให้แต่ละปลายทางเพื่อใช้ ส่งและรับข้อความผ่านการเชื่อมต่อนั้น

ใช้โค้ดต่อไปนี้เพื่อเปิดแชแนลจาก Content Script รวมถึงส่งและรับฟังข้อความ

content-script.js:

const port = browser.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:

browser.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 เป็นอนุกรม โปรดทราบว่าการดำเนินการนี้แตกต่างจากเบราว์เซอร์อื่นๆ ที่ใช้ API เดียวกันกับอัลกอริทึมการโคลนแบบมีโครงสร้าง

ซึ่งหมายความว่าข้อความ (และการตอบกลับที่ผู้รับระบุ) อาจมีJSON.stringify() ค่าที่ถูกต้อง ระบบจะบังคับให้ค่าอื่นๆ เป็นค่าที่ทำให้เป็นอนุกรมได้ (โดยเฉพาะอย่างยิ่ง undefined จะได้รับการทำให้เป็นอนุกรมเป็น null)

ขีดจำกัดขนาดข้อความ

ขนาดสูงสุดของข้อความคือ 64 MiB

อายุการใช้งานของพอร์ต

พอร์ตได้รับการออกแบบให้เป็นกลไกการสื่อสารแบบ 2 ทางระหว่างส่วนต่างๆ ของส่วนขยาย เมื่อส่วนหนึ่งของส่วนขยายเรียกใช้ tabs.connect(), runtime.connect() หรือ runtime.connectNative() ระบบจะสร้าง Port ที่ส่งข้อความได้ทันทีโดยใช้ 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:
browser.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:
browser.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:
browser.runtime.sendMessage(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      browser.runtime.sendMessage(laserExtensionId, {activateLasers: true});
  }
);

// For a long-lived connection:
const port = browser.runtime.connect(laserExtensionId);
port.postMessage(...);

ส่งข้อความจากหน้าเว็บ

ส่วนขยายยังรับและตอบกลับข้อความจากหน้าเว็บได้ด้วย หากต้องการส่งข้อความจากหน้าเว็บไปยังส่วนขยาย ให้ระบุใน manifest.json ว่าต้องการอนุญาตให้เว็บไซต์ใดส่งข้อความโดยใช้คีย์ไฟล์ Manifest "externally_connectable" เช่น

manifest.json

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

ซึ่งจะเปิดเผย Messaging API ให้กับหน้าเว็บใดก็ตามที่ตรงกับรูปแบบที่ตรงกันที่คุณระบุ

ใช้ 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 && browser.runtime) {
  // Make a request:
  browser.runtime.sendMessage(
    editorExtensionId,
    {
      openUrlInEditor: url
    },
    (response) => {
      if (!response.success) handleError(url);
    }
  );
}

จากส่วนขยาย ให้ฟังข้อความจากหน้าเว็บโดยใช้ API runtime.onMessageExternal หรือ runtime.onConnectExternal ตามที่ระบุไว้ในการรับส่งข้อความระหว่างส่วนขยาย เช่น

service-worker.js

browser.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

อย่าลืมปกป้องสคริปต์ของคุณจากCross-site Scripting เมื่อได้รับข้อมูลจากแหล่งที่มาที่ไม่น่าเชื่อถือ เช่น ข้อมูลจากผู้ใช้ เว็บไซต์อื่นๆ ผ่าน Content Script หรือ API ให้ระมัดระวังอย่าตีความข้อมูลนี้เป็น HTML หรือใช้ในลักษณะที่อาจอนุญาตให้โค้ดที่ไม่คาดคิดทำงาน

วิธีการที่ปลอดภัยกว่า

ใช้ API ที่ไม่เรียกใช้สคริปต์เมื่อเป็นไปได้

service-worker.js

browser.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

browser.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

browser.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be evaluating a malicious script!
  const resp = eval(`(${response.farewell})`);
});

service-worker.js

browser.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
  // WARNING! Might be injecting a malicious script!
  document.getElementById("resp").innerHTML = response.farewell;
});