النقل إلى مشغّل خدمات

استبدال صفحات الخلفية أو صفحات الأحداث بمشغّل خدمات

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

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

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

الاختلافات بين النصوص البرمجية للخلفية وعاملي خدمات الإضافات

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

التغييرات من صفحات الخلفية

لدى عاملي الخدمات عدد من الاختلافات مع صفحات الخلفية.

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

التغييرات التي ستحتاج إلى إجرائها

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

  • وبما أنّه لا يمكن لهذه الطلبات الوصول إلى واجهة DOM أو window، عليك نقل عمليات الاستدعاء هذه إلى واجهة برمجة تطبيقات مختلفة أو إلى مستند خارج الشاشة.
  • يجب عدم تسجيل أدوات معالجة الأحداث استجابةً للوعود التي تم إرجاعها أو عند معاودة الاتصال داخل الحدث.
  • ولأنها غير متوافقة مع الأنظمة القديمة مع XMLHttpRequest()، ستحتاج إلى استبدال المكالمات الواردة إلى هذه الواجهة بالمكالمات الواردة إلى fetch().
  • ونظرًا لأنها تنتهي عند عدم استخدامها، ستحتاج إلى الاحتفاظ بحالات التطبيق بدلاً من الاعتماد على المتغيرات العمومية. ويمكن أيضًا إنهاء الموقتات قبل انتهاء العاملين في الخدمة. لَازِمْ يِتِمّْ تَغْيِيرْهَا بِالْمُنَبِّهَاتْ.

تصف هذه الصفحة هذه المهام بالتفصيل.

تعديل حقل "الخلفية" في البيان

في إصدار Manifest V3، يتم استبدال صفحات الخلفية بمشغّل خدمات. يتم إدراج تغييرات البيان أدناه.

  • يمكنك استبدال "background.scripts" بـ "background.service_worker" في manifest.json. يُرجى العِلم أنّ الحقل "service_worker" يأخذ سلسلة، وليس مصفوفة سلاسل.
  • إزالة "background.persistent" من manifest.json
إصدار Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
إصدار Manifest V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

يتضمّن الحقل "service_worker" سلسلة واحدة. ستحتاج إلى الحقل "type" فقط إذا كنت تستخدم وحدات ES (باستخدام الكلمة الرئيسية import). وستكون قيمتها دائمًا "module". لمزيد من المعلومات، راجِع أساسيات عامل خدمات الإضافات.

نقل استدعاءات DOM والنافذة إلى مستند خارج الشاشة

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

لاستخدام واجهة برمجة التطبيقات Offscreen API، عليك إنشاء مستند خارج الشاشة من مشغّل الخدمات.

chrome.offscreen.createDocument({
  url: chrome.runtime.getURL('offscreen.html'),
  reasons: ['CLIPBOARD'],
  justification: 'testing the offscreen API',
});

في المستند خارج الشاشة، نفِّذ أي إجراء كنت نفّذه سابقًا في نص برمجي للخلفية. على سبيل المثال، يمكنك نسخ النص المحدّد في صفحة المضيف.

let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');

التواصل بين المستندات خارج الشاشة وعاملي خدمات الإضافات باستخدام ميزة تمرير الرسائل.

تحويل LocalStorage إلى نوع آخر

لا يمكن استخدام واجهة Storage للنظام الأساسي للويب (يمكن الوصول إليها من window.localStorage) في مشغّل خدمات. لمعالجة هذا الأمر، عليك تنفيذ أحد أمرين. أولاً، يمكنك استبداله بالمكالمات الواردة إلى آلية تخزين أخرى. ستتيح مساحة الاسم chrome.storage.local معظم حالات الاستخدام، ولكن تتوفّر خيارات أخرى.

يمكنك أيضًا نقل مكالماته إلى مستند خارج الشاشة. على سبيل المثال، لنقل البيانات التي سبق تخزينها في localStorage إلى آلية أخرى:

  1. يمكنك إنشاء مستند خارج الشاشة باستخدام سلسلة إجراءات إحالة ناجحة ومعالج runtime.onMessage.
  2. أضِف سلسلة إجراءات إحالة ناجحة إلى المستند خارج الشاشة.
  3. في مشغّل خدمات الإضافات، تحقَّق من chrome.storage لبياناتك.
  4. في حال عدم العثور على بياناتك، يمكنك إنشاء مستند خارج الشاشة والاتصال بالرمز runtime.sendMessage() لبدء سلسلة إجراءات الإحالات الناجحة.
  5. في معالج runtime.onMessage الذي أضفته إلى المستند خارج الشاشة، استدعِ سلسلة إجراءات الإحالات الناجحة.

هناك أيضًا بعض الفروق الطفيفة في طريقة عمل واجهات برمجة تطبيقات تخزين الويب في الإضافات. مزيد من المعلومات في مساحة التخزين وملفات تعريف الارتباط

تسجيل المستمعين بشكلٍ متزامن

لا يمكن ضمان نجاح تسجيل المستمع في الإصدار 3 من ملف البيان بشكل غير متزامن (مثلاً في وعد أو معاودة الاتصال). ضع في الاعتبار التعليمة البرمجية التالية.

chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.browserAction.setBadgeText({ text: badgeText });
  chrome.browserAction.onClicked.addListener(handleActionClick);
});

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

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

chrome.action.onClicked.addListener(handleActionClick);

chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
  chrome.action.setBadgeText({ text: badgeText });
});

استبدال XMLHttpRequest() بالجلب العمومي()

لا يمكن طلب XMLHttpRequest() من مشغّل خدمات أو إضافة أو غير ذلك. يمكنك استبدال المكالمات من النص البرمجي للخلفية إلى XMLHttpRequest() باستدعاءات fetch() العامة.

XMLHttpRequest()
const xhr = new XMLHttpRequest();
console.log('UNSENT', xhr.readyState);

xhr.open('GET', '/api', true);
console.log('OPENED', xhr.readyState);

xhr.onload = () => {
    console.log('DONE', xhr.readyState);
};
xhr.send(null);
fetch()
const response = await fetch('https://www.example.com/greeting.json'')
console.log(response.statusText);

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

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

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

إصدار Manifest V2 النصي للخلفية
let savedName = undefined;

chrome.runtime.onMessage.addListener(({ type, name }) => {
  if (type === "set-name") {
    savedName = name;
  }
});

chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.sendMessage(tab.id, { name: savedName });
});

بالنسبة إلى إصدار Manifest V3، يمكنك استبدال المتغيّر العمومي باستدعاء Storage API.

مشغّل خدمات Manifest V3
chrome.runtime.onMessage.addListener(({ type, name }) => {
  if (type === "set-name") {
    chrome.storage.local.set({ name });
  }
});

chrome.action.onClicked.addListener(async (tab) => {
  const { name } = await chrome.storage.local.get(["name"]);
  chrome.tabs.sendMessage(tab.id, { name });
});

تحويل الموقّتات إلى منبّهات

من الشائع استخدام العمليات المتأخرة أو الدورية باستخدام طريقة setTimeout() أو setInterval(). ومع ذلك، قد تفشل واجهات برمجة التطبيقات هذه في مشغِّلي الخدمة، بسبب إلغاء الموقّتات عند إنهاء عامل الخدمة.

إصدار Manifest V2 النصي للخلفية
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

بدلاً من ذلك، يمكنك استخدام Alarms API. كما هي الحال مع أدوات معالجة الإنذار الأخرى، يجب أن تكون أدوات معالجة الإنذار مسجَّلة في المستوى الأعلى من النص.

مشغّل خدمات Manifest V3
async function startAlarm(name, duration) {
  await chrome.alarms.create(name, { delayInMinutes: 3 });
}

chrome.alarms.onAlarm.addListener(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
});

إبقاء عامل الخدمة نشِطًا

يستند عاملو الخدمة إلى الحدث وينتهي عند عدم النشاط. وبهذه الطريقة يمكن لـ Chrome تحسين الأداء واستهلاك الذاكرة لإضافتك. يمكنك الاطّلاع على مزيد من المعلومات في مستندات مراحل نشاط موظفي الخدمات. وقد تتطلب الحالات الاستثنائية اتخاذ تدابير إضافية لضمان بقاء عامل الخدمة على قيد الحياة لفترة أطول.

الإبقاء على عامل الخدمة نشِطًا حتى الانتهاء من عملية طويلة الأمد

خلال عمليات مشغِّل الخدمات التي تستغرق مدة طويلة ولا تستدعي واجهات برمجة التطبيقات للإضافات، قد يتم إيقاف عامل الخدمات في منتصف التشغيل. وتشمل الأمثلة ما يلي:

  • من المحتمل أن يستغرق طلب fetch() أكثر من خمس دقائق (على سبيل المثال، عملية تنزيل كبيرة بسبب ضعف الاتصال المحتمل).
  • عملية حسابية معقدة غير متزامنة تستغرق أكثر من 30 ثانية.

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

يعرض المثال التالي إحدى وظائف المساعدة في waitUntil() التي تُبقي عامل الخدمة نشِطًا حتى يتم حلّ وعد معيّن:

async function waitUntil(promise) = {
  const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
  try {
    await promise;
  } finally {
    clearInterval(keepAlive);
  }
}

waitUntil(someExpensiveCalculation());

الحفاظ على استمرارية عامل الخدمة

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

استخدِم مقتطف الرمز التالي لإبقاء عامل الخدمة نشِطًا:

/**
 * Tracks when a service worker was last alive and extends the service worker
 * lifetime by writing the current time to extension storage every 20 seconds.
 * You should still prepare for unexpected termination - for example, if the
 * extension process crashes or your extension is manually stopped at
 * chrome://serviceworker-internals. 
 */
let heartbeatInterval;

async function runHeartbeat() {
  await chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}

/**
 * Starts the heartbeat interval which keeps the service worker alive. Call
 * this sparingly when you are doing work which requires persistence, and call
 * stopHeartbeat once that work is complete.
 */
async function startHeartbeat() {
  // Run the heartbeat once at service worker startup.
  runHeartbeat().then(() => {
    // Then again every 20 seconds.
    heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
  });
}

async function stopHeartbeat() {
  clearInterval(heartbeatInterval);
}

/**
 * Returns the last heartbeat stored in extension storage, or undefined if
 * the heartbeat has never run before.
 */
async function getLastHeartbeat() {
  return (await chrome.storage.local.get('last-heartbeat'))['last-heartbeat'];
}