تمرير الرسالة

ولأنّ نصوص برمجية المحتوى يتم تشغيلها في سياق صفحة ويب، وليس في سياق الإضافة التي شغّلتها، غالبًا ما تحتاج إلى طرق للتواصل مع بقية الإضافة. على سبيل المثال، قد تستخدم إضافة ملف RSS قارئ نصوص برمجية للمحتوى لرصد توفّر خلاصة RSS على صفحة معيّنة، ثم إرسال إشعار إلى عامل الخدمة لعرض رمز إجراء لهذه الصفحة.

ويستخدم هذا التواصل عملية تمرير الرسائل، ما يسمح لكل من الإضافات والنصوص البرمجية للمحتوى بالاستماع إلى رسائل بعضها البعض والردّ عليها في القناة نفسها. يمكن أن تحتوي الرسالة على أيّ كائن JSON صالح (خالي أو منطقي أو رقم أو سلسلة أو مصفوفة أو كائن). هناك واجهتَا برمجة تطبيقات لإرسال الرسائل: واحدة للطلبات لمرة واحدة، وأخرى أكثر تعقيدًا للاتصالات التي تستمر لفترة طويلة والتي تسمح بإرسال رسائل متعددة. للاطّلاع على معلومات حول إرسال الرسائل بين الإضافات، يُرجى الاطّلاع على القسم الرسائل بين الإضافات.

الطلبات لمرة واحدة

لإرسال رسالة واحدة إلى جزء آخر من إضافة المساحة، والحصول على ردّ اختياري، اتصل على runtime.sendMessage() أو tabs.sendMessage(). تتيح لك هذه الطرق إرسال رسالة لمرة واحدة يمكن تسلسلها باستخدام تنسيق JSON من نص برمجي للمحتوى إلى الإضافة، أو من الإضافة إلى نص برمجي للمحتوى. لمعالجة الاستجابة، استخدِم الوعد الذي تم إرجاعه. للتوافق مع الإصدارات القديمة من الإضافات، يمكنك بدلاً من ذلك تمرير دالة استدعاء كأحد المَعلمات الأخيرة. لا يمكنك استخدام وعد وطريقة استدعاء في المكالمة نفسها.

عند إرسال رسالة، يتم تمرير sendResponse، وهي ثالثة اختيارية، إلى أداة معالجة الحدث التي تعالج الرسالة. هذه دالة تأخذ عنصرًا قابلاً للتسلسل بتنسيق JSON ويُستخدم كقيمة معروضة للدالة التي أرسلت الرسالة. يجب استدعاء دالة الاستدعاء sendResponse بشكل متزامن تلقائيًا. إذا كنت تريد تنفيذ عمل غير متزامن للحصول على القيمة المرسَلة إلى sendResponse، يجب عرض قيمة true حرفية (وليس مجرد قيمة صحيحة) من مستمع الأحداث. سيؤدي ذلك إلى إبقاء قناة الرسائل مفتوحة للطرف الآخر إلى أن يتم استدعاء sendResponse.

// Event listener
function handleMessages(message, sender, sendResponse) {

  fetch(message.url)
    .then((response) => sendResponse({statusCode: response.status}))

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

// Message sender
  const {statusCode} = await chrome.runtime.sendMessage({
    url: 'https://example.com'
  });

للحصول على معلومات عن تحويل عمليات الاستدعاء إلى وعد واستخدامها في الإضافات، يُرجى الاطّلاع على دليل نقل البيانات إلى 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 نشطًا إلى أن تصبح مستعدًا لاستخدامه. لا تتوفّر الدوالّ غير المتزامنة لأنّها تعرض وعدًا غير متوافق.

لإرسال طلب إلى نص محتوى، حدِّد علامة التبويب التي ينطبق عليها الطلب، كما هو موضّح في ما يلي. يعمل هذا المثال في مشغّلي الخدمات والنوافذ المنبثقة وصفحات 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 في منفذ الإرسال فقط، وليس في المنافذ الأخرى.

المراسلة بين الإضافات

بالإضافة إلى إرسال الرسائل بين المكوّنات المختلفة في إضافتك، يمكنك استخدام واجهة برمجة التطبيقات messaging 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 minimal 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/*"]
}

يؤدي ذلك إلى إتاحة Messaging API لأي صفحة تتطابق مع أنماط عناوين URL التي تحدّدها. يجب أن يحتوي نمط عنوان URL على نطاق من المستوى الثاني على الأقل، أي أنّ أنماط أسماء المضيفين مثل "*" و"*.com" و"*.co.uk" و "*.appspot.com" غير متوافقة. بدءًا من الإصدار 107 من Chrome، يمكنك استخدام رمز <all_urls> للوصول إلى جميع النطاقات. يُرجى العِلم أنّه قد يستغرق الأمر وقتًا أطول لمراجعة "سوق Chrome الإلكتروني" للإضافات التي تستخدم هذا الإجراء، لأنّه يؤثر في جميع المضيفين.

استخدِم واجهات برمجة التطبيقات 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 && chrome.runtime) {
  // Make a request:
  chrome.runtime.sendMessage(
    editorExtensionId,
    {
      openUrlInEditor: url
    },
    (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);
  });

المراسلة مع التطبيقات الأصلية

يمكن للإضافة تبادل الرسائل مع التطبيقات الأصلية المسجّلة على أنّها مضيف مراسلة أصلي. لمزيد من المعلومات عن هذه الميزة، اطّلِع على مقالة المراسلة المدمجة.

الاعتبارات الأمنية

في ما يلي بعض النقاط التي يجب مراعاتها في ما يتعلّق بالأمان في المراسلة.

النصوص البرمجية للمحتوى أقل موثوقية

النصوص البرمجية للمحتوى أقل موثوقية من الخدمة العاملة للإضافة. على سبيل المثال، قد تتمكّن صفحة ويب ضارة من اختراق عملية العرض التي تشغّل نصوص المحتوى البرمجية. افترض أنّ المهاجم قد كون رسائل من نص محتوى، واحرص على التحقّق من صحة كل الإدخالات وتنقيتها. افترض أنّ أي بيانات يتم إرسالها إلى نص المحتوى قد تتسرّب إلى صفحة الويب. الحد من نطاق الإجراءات المميّزة التي يمكن أن يتم تفعيلها من خلال الرسائل الواردة من نصوص برمجية للمحتوى

هجمات النصوص البرمجية على المواقع الإلكترونية

احرص على حماية نصوصك البرمجية من هجمات النصوص البرمجية على مستوى المواقع الإلكترونية. عند تلقّي بيانات من مصدر غير موثوق به، مثل إدخال المستخدم أو مواقع إلكترونية أخرى من خلال نص برمجي للمحتوى أو واجهة برمجة تطبيقات، يجب توخّي الحذر لتجنُّب تفسير ذلك على أنّه رمز HTML أو استخدامه بطريقة قد تسمح بتشغيل رمز غير متوقّع.

طرق أكثر أمانًا

استخدِم واجهات برمجة التطبيقات التي لا تُنفِّذ النصوص البرمجية كلّما أمكن:

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;
});
الطرق غير الآمنة

تجنَّب استخدام الطرق التالية التي تجعل إضافة Chrome عرضة للاختراق:

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