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

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

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

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

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

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

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

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

تختلف نصوص مشغّلي الخدمات عن صفحات الخلفية في عدة جوانب.

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

التغييرات التي يجب إجراؤها

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

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

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

تعديل حقل "background" في ملف البيان

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

لاستخدام Offscreen API، أنشئ مستندًا خارج الشاشة من مشغّل الخدمات.

browser.offscreen.createDocument({
  url: browser.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) في مشغّل خدمات. لحلّ هذه المشكلة، يمكنك اتّخاذ أحد الإجراءَين التاليَين. أولاً، يمكنك استبدالها بطلبات إلى آلية تخزين أخرى. ستلبي مساحة الاسم browser.storage.local معظم حالات الاستخدام، ولكن تتوفّر خيارات أخرى.

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

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

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

تسجيل أدوات معالجة الأحداث بشكل متزامن

لا يمكن ضمان عمل تسجيل مستمع بشكل غير متزامن (على سبيل المثال داخل وعد أو رد اتصال) في Manifest V3. ضع الرمز التالي في اعتبارك.

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

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

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

browser.action.onClicked.addListener(handleActionClick);

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

استبدال XMLHttpRequest()‎ بـ fetch()‎ العام

لا يمكن استدعاء 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;

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

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

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

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

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

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

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

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

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

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

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

إبقاء مشغّل الخدمات نشطًا

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

إبقاء مشغّل الخدمات نشطًا إلى أن تكتمل عملية طويلة الأمد

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

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

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

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

async function waitUntil(promise) {
  const keepAlive = setInterval(browser.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 browser.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 browser.storage.local.get('last-heartbeat'))['last-heartbeat'];
}