از آنجایی که اسکریپتهای محتوا در متن یک صفحه وب اجرا میشوند، نه برنامه افزودنی که آنها را اجرا میکند، اغلب به راههایی برای برقراری ارتباط با بقیه برنامههای افزودنی نیاز دارند. به عنوان مثال، یک برنامه افزودنی RSS reader ممکن است از اسکریپت های محتوا برای تشخیص وجود فید RSS در یک صفحه استفاده کند، سپس به کارگر سرویس اطلاع دهد تا یک نماد عمل را برای آن صفحه نمایش دهد.
این ارتباط از ارسال پیام استفاده میکند که به برنامههای افزودنی و محتوا اجازه میدهد به پیامهای یکدیگر گوش داده و در یک کانال پاسخ دهند. یک پیام میتواند حاوی هر شیء JSON معتبر (تهی، بولی، عدد، رشته، آرایه یا شیء) باشد. دو API ارسال پیام وجود دارد: یکی برای درخواستهای یکباره و دیگری پیچیدهتر برای اتصالات طولانیمدت که امکان ارسال چندین پیام را فراهم میکند. برای اطلاعات در مورد ارسال پیام بین برنامههای افزودنی، به بخش پیامهای پسوندی مراجعه کنید.
درخواست های یکبار مصرف
برای ارسال یک پیام واحد به قسمت دیگری از برنامه افزودنی خود و دریافت پاسخ به صورت اختیاری، با runtime.sendMessage()
یا tabs.sendMessage()
تماس بگیرید. این روشها به شما امکان میدهند یک پیام یکبار سریالسازی با JSON از یک اسکریپت محتوا به برنامه افزودنی یا از برنامه افزودنی به یک اسکریپت محتوا ارسال کنید. برای رسیدگی به پاسخ، از قول برگشتی استفاده کنید. برای سازگاری با افزونههای قدیمیتر، میتوانید به جای آن یک callback را به عنوان آخرین آرگومان ارسال کنید. شما نمی توانید از یک وعده و یک تماس برگشتی در یک تماس استفاده کنید.
برای اطلاعات در مورد تبدیل تماسهای برگشتی به وعدهها و استفاده از آنها در برنامههای افزودنی، به راهنمای مهاجرت Manifest V3 مراجعه کنید.
ارسال یک درخواست از یک اسکریپت محتوا به شکل زیر است:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
اگر میخواهید به پیامی به طور همزمان پاسخ دهید، کافیست پس از دریافت پاسخ با sendResponse
تماس بگیرید و false
برای نشان دادن تمام شدن آن برگردانید. برای پاسخگویی ناهمزمان، true
برگردانید تا sendResponse
callback فعال بماند تا زمانی که آماده استفاده از آن باشید. توابع Async پشتیبانی نمی شوند زیرا یک Promise را برمی گردانند که پشتیبانی نمی شود.
برای ارسال درخواست به یک اسکریپت محتوا، همانطور که در زیر نشان داده شده است، مشخص کنید که درخواست برای کدام برگه اعمال می شود. این مثال در سرویس کارگران، پنجرههای بازشو و صفحات chrome-extension:// که بهعنوان یک برگه باز میشوند کار میکند.
(async () => {
const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
برای دریافت پیام، شنونده رویداد runtime.onMessage
را تنظیم کنید. این کدها هم در برنامه های افزودنی و هم در اسکریپت های محتوا از یک کد استفاده می کنند:
content-script.js یا service-worker.js:
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()
برای یک رویداد خاص فراخوانی میکند موفق به ارسال پاسخ میشود. تمام پاسخ های دیگر به آن رویداد نادیده گرفته می شود.
ارتباطات طولانی مدت
برای ایجاد یک کانال ارسال پیام با عمر طولانی قابل استفاده مجدد، با runtime.connect()
تماس بگیرید تا پیامها را از یک اسکریپت محتوا به صفحه افزونه ارسال کنید، یا tabs.connect()
برای ارسال پیامها از یک صفحه افزونه به یک اسکریپت محتوا. میتوانید کانال خود را برای تمایز بین انواع مختلف اتصالات نامگذاری کنید.
یکی از موارد استفاده بالقوه برای اتصال طولانی مدت، فرمت پر کردن خودکار است. اسکریپت محتوا ممکن است کانالی را به صفحه برنامه افزودنی برای ورود به سیستم خاص باز کند و برای هر عنصر ورودی در صفحه پیامی به برنامه افزودنی ارسال کند تا دادههای فرم را پر کند. اجزاء
هنگام برقراری یک اتصال، به هر انتهای یک شیء runtime.Port
برای ارسال و دریافت پیام از طریق آن اتصال اختصاص داده می شود.
از کد زیر برای باز کردن یک کانال از اسکریپت محتوا و ارسال و گوش دادن به پیام ها استفاده کنید:
content-script.js:
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"});
});
برای ارسال درخواست از برنامه افزودنی به یک اسکریپت محتوا، فراخوانی به runtime.connect()
در مثال قبلی را با tabs.connect()
جایگزین کنید.
برای مدیریت اتصالات ورودی برای یک اسکریپت محتوا یا یک صفحه افزونه، یک شنونده رویداد runtime.onConnect
راه اندازی کنید. هنگامی که بخش دیگری از برنامه افزودنی شما connect()
تماس می گیرد، این رویداد و شی runtime.Port
را فعال می کند. کد پاسخگویی به اتصالات ورودی به صورت زیر است:
service-worker.js:
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."});
});
});
طول عمر بندر
پورت ها به عنوان یک روش ارتباطی دو طرفه بین بخش های مختلف داخلی طراحی شده اند. یک قاب سطح بالا کوچکترین قسمت یک افزونه است که می تواند از پورت استفاده کند. هنگامی که بخشی از یک برنامه افزودنی tabs.connect()
، runtime.connect()
یا runtime.connectNative()
را فرا می خواند، پورتی ایجاد می کند که می تواند بلافاصله با استفاده از postMessage()
پیام ارسال کند.
اگر چندین فریم در یک برگه وجود داشته باشد، فراخوانی tabs.connect()
رویداد runtime.onConnect
را یک بار برای هر فریم در برگه فراخوانی می کند. به طور مشابه، اگر runtime.connect()
فراخوانی شود، رویداد onConnect
می تواند یک بار برای هر فریم در فرآیند افزونه فعال شود.
ممکن است بخواهید بدانید که چه زمانی یک اتصال بسته است، به عنوان مثال آیا حالت های جداگانه ای را برای هر پورت باز حفظ می کنید. برای انجام این کار، به رویداد runtime.Port.onDisconnect
گوش دهید. این رویداد زمانی فعال می شود که هیچ پورت معتبری در انتهای دیگر کانال وجود نداشته باشد، که می تواند یکی از دلایل زیر را داشته باشد:
- هیچ شنونده ای برای
runtime.onConnect
در انتهای دیگر وجود ندارد. - برگه حاوی پورت بارگیری می شود (به عنوان مثال، اگر برگه پیمایش شود).
- فریمی که در آن
connect()
فراخوانی شده بود بارگیری شد. - همه فریم هایی که پورت را دریافت کرده اند (از طریق
runtime.onConnect
) بارگیری شده اند. -
runtime.Port.disconnect()
توسط انتهای دیگر فراخوانی می شود. اگر فراخوانیconnect()
منجر به چندین پورت در انتهای گیرنده شود، وdisconnect()
روی هر یک از این پورت ها فراخوانی شود، رویدادonDisconnect
فقط در پورت ارسال کننده فعال می شود، نه در پورت های دیگر.
پیام رسانی متقاطع
علاوه بر ارسال پیام بین اجزای مختلف در برنامه افزودنی خود، میتوانید از API پیامرسانی برای برقراری ارتباط با سایر برنامههای افزودنی استفاده کنید. این به شما امکان می دهد یک API عمومی را برای استفاده از برنامه های افزودنی دیگر در معرض دید قرار دهید.
برای گوش دادن به درخواستهای ورودی و اتصالات از برنامههای افزودنی دیگر، از روشهای runtime.onMessageExternal
یا runtime.onConnectExternal
استفاده کنید. در اینجا یک نمونه از هر کدام آورده شده است:
service-worker.js
// For a single request:
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.
});
});
برای ارسال پیام به افزونه دیگر، شناسه افزونه مورد نظر برای برقراری ارتباط را به صورت زیر ارسال کنید:
service-worker.js
// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// For a simple request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// For a long-lived connection:
var port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
ارسال پیام از صفحات وب
برنامههای افزودنی همچنین میتوانند پیامهایی را از سایر صفحات وب دریافت کرده و به آنها پاسخ دهند، اما نمیتوانند به صفحات وب پیام ارسال کنند. برای ارسال پیام از یک صفحه وب به یک برنامه افزودنی، در manifest.json
خود مشخص کنید که می خواهید با استفاده از کلید مانیفست "externally_connectable"
با کدام وب سایت ها ارتباط برقرار کنید. به عنوان مثال:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
این API پیامرسانی را در معرض هر صفحهای قرار میدهد که با الگوهای URL که شما مشخص کردهاید مطابقت داشته باشد. الگوی URL باید حداقل شامل یک دامنه سطح دوم باشد. یعنی الگوهای نام میزبان مانند «*»، «*.com»، «*.co.uk» و «*.appspot.com» پشتیبانی نمیشوند. از Chrome 107، میتوانید از <all_urls>
برای دسترسی به همه دامنهها استفاده کنید. توجه داشته باشید که از آنجایی که همه میزبانها را تحت تأثیر قرار میدهد، بررسی فروشگاه وب Chrome برای افزونههایی که از آن استفاده میکنند ممکن است بیشتر طول بکشد .
از APIهای runtime.sendMessage()
یا runtime.connect()
برای ارسال پیام به یک برنامه یا برنامه افزودنی خاص استفاده کنید. به عنوان مثال:
webpage.js
// 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
مانند پیامرسانی بین برنامههای افزودنی گوش دهید. در اینجا یک مثال است:
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);
});
پیامرسانی بومی
برنامه های افزودنی می توانند پیام ها را با برنامه های کاربردی بومی که به عنوان میزبان پیام رسانی بومی ثبت شده اند مبادله کنند . برای کسب اطلاعات بیشتر در مورد این ویژگی، به پیامرسانی بومی مراجعه کنید.
ملاحظات امنیتی
در اینجا چند ملاحظات امنیتی مربوط به پیام رسانی وجود دارد.
اسکریپت های محتوا کمتر قابل اعتماد هستند
اسکریپتهای محتوا نسبت به کارمند خدمات افزودنی کمتر قابل اعتماد هستند . به عنوان مثال، یک صفحه وب مخرب ممکن است بتواند فرآیند رندرینگ را که اسکریپت های محتوا را اجرا می کند، به خطر بیندازد. فرض کنید که پیامهای یک اسکریپت محتوا ممکن است توسط یک مهاجم ساخته شده باشد و مطمئن شوید که تمام ورودیها را اعتبارسنجی و پاکسازی میکنید . فرض کنید هرگونه داده ارسال شده به اسکریپت محتوا ممکن است به صفحه وب نشت کند. دامنه اقدامات ممتازی را که می تواند توسط پیام های دریافتی از اسکریپت های محتوا ایجاد شود، محدود کنید.
اسکریپت بین سایتی
مطمئن شوید که از اسکریپت های خود در برابر اسکریپت های متقابل سایت محافظت می کنید. هنگام دریافت دادهها از یک منبع نامعتبر مانند ورودی کاربر، وبسایتهای دیگر از طریق یک اسکریپت محتوا یا یک API، مراقب باشید که از تفسیر آن بهعنوان HTML یا استفاده از آن بهگونهای که امکان اجرای کدهای غیرمنتظره را فراهم کند، خودداری کنید.
در صورت امکان از API هایی استفاده کنید که اسکریپت ها را اجرا نمی کنند:
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse doesn't evaluate the attacker's scripts. var 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! var 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; });