ارسال پیام

از آنجایی که اسکریپت‌های محتوا در متن یک صفحه وب اجرا می‌شوند و نه در افزونه، اغلب به روشی برای برقراری ارتباط با بقیه افزونه نیاز دارند. به عنوان مثال، یک افزونه خواننده RSS ممکن است از اسکریپت‌های محتوا برای تشخیص وجود یک فید RSS در یک صفحه استفاده کند، سپس به صفحه پس‌زمینه اطلاع دهد تا یک آیکون عملکرد صفحه برای آن صفحه نمایش داده شود.

ارتباط بین افزونه‌ها و اسکریپت‌های محتوای آنها با استفاده از ارسال پیام انجام می‌شود. هر دو طرف می‌توانند به پیام‌های ارسالی از طرف دیگر گوش دهند و در همان کانال پاسخ دهند. یک پیام می‌تواند شامل هر شیء JSON معتبری (null، boolean، number، string، array یا object) باشد. یک API ساده برای درخواست‌های یک‌باره و یک API پیچیده‌تر وجود دارد که به شما امکان می‌دهد اتصالات طولانی‌مدت برای تبادل چندین پیام با یک زمینه مشترک داشته باشید. همچنین در صورت دانستن شناسه افزونه دیگر، می‌توانید پیامی را به آن ارسال کنید که در بخش پیام‌های بین افزونه‌ای به آن پرداخته شده است.

درخواست‌های ساده و یک‌باره

اگر فقط نیاز دارید یک پیام واحد را به بخش دیگری از افزونه خود ارسال کنید (و به صورت اختیاری پاسخی دریافت کنید)، باید از runtime.sendMessage یا tabs.sendMessage ساده‌شده استفاده کنید. این به شما امکان می‌دهد یک پیام JSON-serializable یک‌بار مصرف را از یک اسکریپت محتوا به افزونه یا برعکس ارسال کنید. یک پارامتر 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);
  });
});

در سمت گیرنده، باید یک شنونده رویداد 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 را برگرداند تا نشان دهد که به صورت غیرهمزمان پاسخ خواهد داد. تابع فراخوانی sendMessage در صورتی که هیچ کنترل‌کننده‌ای مقدار true را برنگرداند یا اگر تابع فراخوانی sendResponse از نوع garbage-collected باشد، به طور خودکار فراخوانی خواهد شد.

اتصالات طولانی مدت

گاهی اوقات مفید است که مکالمه‌ای داشته باشید که بیش از یک درخواست و پاسخ واحد طول بکشد. در این حالت، می‌توانید با استفاده از 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"});
});

ارسال درخواست از افزونه به یک اسکریپت محتوا بسیار شبیه به این است، با این تفاوت که باید مشخص کنید به کدام تب متصل شوید. کافیست فراخوانی connect در مثال بالا را با tabs.connect جایگزین کنید.

برای مدیریت اتصالات ورودی، باید یک شنونده رویداد 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."});
  });
});

طول عمر پورت

پورت‌ها به عنوان یک روش ارتباطی دو طرفه بین بخش‌های مختلف افزونه طراحی شده‌اند، که در آن یک فریم (سطح بالا) به عنوان کوچکترین بخش در نظر گرفته می‌شود. با فراخوانی 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 استفاده می‌کنید. در اینجا مثالی از هر کدام آورده شده است:

// 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 باید حداقل شامل یک دامنه سطح دوم باشد - یعنی الگوهای نام میزبان مانند "*"، "*.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 یا runtime.onConnectExternal به پیام‌های صفحات وب گوش دهید، مشابه پیام‌رسانی بین افزونه‌ای . فقط صفحه وب می‌تواند یک اتصال را آغاز کند. در اینجا مثالی آورده شده است:

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های خطرناک مانند موارد زیر خودداری کنید:

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 می‌تواند با یک برنامه بومی ارتباط برقرار کند. برای مثال‌های بیشتر و کمک در مشاهده کد منبع، به Samples مراجعه کنید.