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

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

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

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

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

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

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

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

يؤدي ذلك إلى إتاحة 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.
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);
  });

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

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

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

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

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

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

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

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

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

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