انتقال به کارگر سرویس

جایگزین کردن پس‌زمینه یا صفحه‌های رویداد با عامل خدماتی

عامل خدماتی جایگزین صفحه رویداد یا پس‌زمینه افزونه می‌شود تا مطمئن شود کد پس‌زمینه خارج از رشته اصلی باقی می‌ماند. این کار باعث می‌شود افزونه‌ها فقط درصورت نیاز اجرا شوند و در مصرف منابع صرفه‌جویی شود.

صفحه‌های پس‌زمینه از زمان معرفی افزونه‌ها، جزء اساسی آن‌ها بوده‌اند. به‌بیان ساده، صفحه‌های پس‌زمینه محیطی را فراهم می‌کنند که مستقل از هر پنجره یا برگه دیگری وجود دارد. این کار به افزونه‌ها اجازه می‌دهد رویدادها را مشاهده کنند و در پاسخ به آن‌ها اقدام کنند.

این صفحه وظایف تبدیل صفحه‌های پس‌زمینه به عامل‌های خدماتی افزونه را شرح می‌دهد. برای اطلاعات بیشتر درباره کارگران خدمات افزونه به‌طور کلی، آموزش مدیریت رویدادها با کارگران خدمات و بخش درباره کارگران خدمات افزونه را ببینید.

تفاوت‌های بین متن‌های پس‌زمینه و کارگران خدمات افزونه

در برخی زمینه‌ها، کارگران سرویس افزونه را خواهید دید که «دستورگان‌های پس‌زمینه» نامیده می‌شوند. اگرچه عامل‌های خدماتی افزونه در پس‌زمینه اجرا می‌شوند، اما نامیدن آن‌ها به‌عنوان اسکریپت‌های پس‌زمینه تا حدودی گمراه‌کننده است زیرا قابلیت‌های یکسانی را تداعی می‌کند. تفاوت‌ها در زیر شرح داده شده است.

تغییرات از صفحه‌های پس‌زمینه

عوامل خدماتی تفاوت‌های متعددی با صفحه‌های پس‌زمینه دارند.

  • آن‌ها خارج از رشته اصلی عمل می‌کنند، یعنی با محتوای افزونه تداخل ندارند.
  • آن‌ها قابلیت‌های ویژه‌ای دارند، مانند رهگیری رویدادهای واکشی در مبدأ افزونه، مانند رویدادهای مربوط به بالاپر نوار ابزار.
  • آن‌ها می‌توانند ازطریق میانای مشتریان با زمینه‌های دیگر ارتباط برقرار کنند و تعامل داشته باشند.

تغییراتی که باید اعمال کنید

باید چند اصلاح در کد انجام دهید تا تفاوت‌های بین نحوه عملکرد اسکریپت‌های پس‌زمینه و کارگران سرویس را درنظر بگیرید. برای شروع، نحوه مشخص شدن یک کارگر سرویس در فایل مانیفست با نحوه مشخص شدن اسکریپت‌های پس‌زمینه متفاوت است. علاوه‌براین:

  • چون آن‌ها نمی‌توانند به 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 API با باز و بسته کردن اسناد نمایش‌داده‌نشده بسته‌بندی‌شده با افزونه، بدون مختل کردن تجربه کاربری، از این موارد استفاده پشتیبانی می‌کند. به‌جز ارسال پیام، اسناد خارج‌از صفحه میاناهای برنامه‌سازی کاربردی را با دیگر زمینه‌های افزونه هم‌رسانی نمی‌کنند، اما به‌عنوان صفحات وب کامل برای تعامل افزونه‌ها عمل می‌کنند.

برای استفاده از 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');

از Chrome نسخه ۱۵۷، برای نوشتن در بریده‌دان به سند خارج‌از صفحه نیاز ندارید. یک کارگر سرویس افزونه با اجازه "clipboardWrite" می‌تواند مستقیماً با navigator.clipboard.writeText() یا navigator.clipboard.write() تماس بگیرد. عاملان خدماتی نمی‌توانند از بریده‌دان بخوانند.

async function copyToClipboard(data) {
  await navigator.clipboard.writeText(data);
}

بااستفاده از ارسال پیام، بین اسناد خارج از صفحه و کارگران سرویس افزونه ارتباط برقرار کنید.

تبدیل localStorage به نوعی دیگر

از میانای Storage پلاتفرم وب (قابل‌دسترسی از window.localStorage) نمی‌توان در کارگر سرویس استفاده کرد. برای رسیدگی به این موضوع، یکی از این دو کار را انجام دهید. ابتدا می‌توانید آن را با فراخوانی‌های سازوکار ذخیره‌سازی دیگری جایگزین کنید. جای‌نام browser.storage.local برای اکثر موارد استفاده مناسب است، اما گزینه‌های دیگری نیز دردسترس است.

همچنین می‌توانید تماس‌های آن را به سند خارج از صفحه منتقل کنید. برای مثال، برای انتقال داده‌هایی که قبلاً در localStorage ذخیره شده‌اند به سازوکاری دیگر:

  1. سند خارج‌ازصفحه با روال تبدیل و گرداننده runtime.onMessage ایجاد کنید.
  2. روال تبدیلی به سند خارج از صفحه اضافه کنید.
  3. در کارگر سرویس افزونه، browser.storage را برای داده‌هایتان بررسی کنید.
  4. اگر داده‌هایتان پیدا نشد، سند خارج از صفحه‌ای ایجاد کنید و برای شروع روال تبدیل، runtime.sendMessage() را فراخوانی کنید.
  5. در گرداننده runtime.onMessage که به سند خارج‌از صفحه اضافه کردید، روال تبدیل را فراخوانی کنید.

همچنین تفاوت‌های ظریفی در نحوه عملکرد میاناهای برنامه‌سازی کاربردی فضای ذخیره‌سازی وب در افزونه‌ها وجود دارد. در فضای ذخیره‌سازی و کوکی‌ها بیشتر بدانید.

ثبت شنوندگان به‌صورت همگام

تضمینی وجود ندارد که ثبت ناهمگام شنونده (برای مثال، در یک وعده یا فراخوانی برگشتی) در «مانیفست نسخه ۳» کار کند. کد زیر را درنظر بگیرید.

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

برای «مانیفست نسخه ۳»، متغیر سراسری را با فراخوانی 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() رایج است. بااین‌حال، این API-ها ممکن است در سرویس‌کارها ناموفق باشند، زیرا هر زمان که سرویس‌کار خاتمه یابد، زمان‌سنج‌ها لغو می‌شوند.

دستورات پس‌زمینه 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 می‌تواند عملکرد و مصرف حافظه افزونه‌تان را بهینه‌سازی کند. در سند چرخه حیات کارگر سرویس ما اطلاعات بیشتری کسب کنید. موارد استثنایی ممکن است به اقدامات اضافی نیاز داشته باشند تا اطمینان حاصل شود که یک کارگر خدماتی برای مدت طولانی‌تری زنده می‌ماند.

فعال نگه داشتن یک کارگر سرویس تا زمانی که یک عملیات طولانی‌مدت به پایان برسد

درطول عملیات طولانی‌مدت کارگر سرویس که API افزونه را فراخوانی نمی‌کند، ممکن است کارگر سرویس در اواسط عملیات خاموش شود. چند نمونه:

  • درخواست fetch() که احتمالاً بیش‌از پنج دقیقه طول می‌کشد (مثلاً بارگیری بزرگ در اتصال احتمالاً ضعیف).
  • محاسبه ناهمگام پیچیده‌ای که بیش‌از ۳۰ ثانیه طول می‌کشد.

برای افزایش طول عمر کارگر سرویس در این موارد، می‌توانید به‌طور دوره‌ای یک API افزونه جزئی را فراخوانی کنید تا شمارنده زمان‌تمام بازنشانی شود. لطفاً توجه داشته باشید که این فقط برای موارد استثنایی درنظر گرفته شده است و در اکثر شرایط معمولاً روش بهتر و اصطلاحی‌تری برای دستیابی به همان نتیجه وجود دارد.

مثال زیر تابع کمکی waitUntil() را نشان می‌دهد که تا زمانی که وعده داده‌شده حل شود، کارگر سرویس شما را فعال نگه می‌دارد:

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

waitUntil(someExpensiveCalculation());

فعال نگه داشتن مداوم کارگر سرویس

در موارد نادر، لازم است طول عمر به‌طور نامحدود تمدید شود. ما شرکت‌ها و آموزش را به‌عنوان بزرگ‌ترین موارد استفاده شناسایی کرده‌ایم و به‌طور خاص این اجازه را در آنجا می‌دهیم، اما به‌طور کلی از این موضوع پشتیبانی نمی‌کنیم. در این شرایط استثنایی، با فراخوانی دوره‌ای یک API افزونه جزئی می‌توان یک کارگر سرویس را فعال نگه داشت. توجه به این نکته مهم است که این توصیه فقط برای افزونه‌هایی اعمال می‌شود که در دستگاه‌های مدیریت‌شده برای موارد استفاده سازمانی یا آموزشی اجرا می‌شوند. در موارد دیگر مجاز نیست و تیم افزونه 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'];
}