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

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

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

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

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

للحصول على معلومات عن تحويل طلبات معاودة الاتصال إلى وعود واستخدامها في الإضافات، يُرجى الاطّلاع على دليل نقل بيانات الإصدار 3 من Manifest.

تظهر عملية إرسال طلب من نص برمجي للمحتوى على النحو التالي:

content-script.js:

(async () => {
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

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

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

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

للاستماع إلى الطلبات الواردة والاتصالات من إضافات أخرى، استخدِم طريقة runtime.onMessageExternal أو runtime.onConnectExternal. فيما يلي مثال على كل منها:

service-work.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-work.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/*"]
}

ويؤدي ذلك إلى عرض واجهة برمجة تطبيقات المراسلة على أي صفحة تتطابق مع أنماط عناوين 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.
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-work.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-work.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-work.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-work.js

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

service-work.js

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