مهاجرت به یک کارگر خدماتی

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

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

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

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

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

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

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

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

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

تغییراتی که باید ایجاد کنید

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

  • از آنجا که آنها نمی‌توانند به DOM یا رابط window دسترسی داشته باشند، باید چنین فراخوانی‌هایی را به یک API متفاوت یا به یک سند خارج از صفحه منتقل کنید.
  • شنونده‌های رویداد نباید در پاسخ به promiseهای برگشتی یا فراخوانی‌های رویداد داخلی ثبت شوند.
  • از آنجایی که آنها با XMLHttpRequest() سازگار نیستند، باید فراخوانی‌های این رابط را با فراخوانی‌های fetch() جایگزین کنید.
  • از آنجایی که آنها در صورت عدم استفاده خاتمه می‌یابند، شما باید حالت‌های برنامه را به جای تکیه بر متغیرهای سراسری، حفظ کنید. خاتمه دادن به سرویس ورکرها همچنین می‌تواند تایمرها را قبل از اتمام کارشان خاتمه دهد. شما باید آنها را با آلارم‌ها جایگزین کنید.

در این صفحه به تفصیل به شرح این وظایف پرداخته شده است.

فیلد "background" را در مانیفست به‌روزرسانی کنید.

در Manifest V3، صفحات پس‌زمینه با یک service worker جایگزین شده‌اند. تغییرات manifest در زیر فهرست شده‌اند.

  • "background.scripts" در فایل manifest.json با "background.service_worker" جایگزین کنید. توجه داشته باشید که فیلد "service_worker" یک رشته می‌گیرد، نه آرایه‌ای از رشته‌ها.
  • "background.persistent" را از فایل manifest.json حذف کنید.
مانیفست نسخه ۲
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
مانیفست نسخه ۳
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

فیلد "service_worker" یک رشته واحد می‌گیرد. فقط در صورت استفاده از ماژول‌های ES (با استفاده از کلمه کلیدی import ) به فیلد "type" نیاز خواهید داشت. مقدار آن همیشه "module" خواهد بود. برای اطلاعات بیشتر، به اصول اولیه Extension service worker مراجعه کنید.

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

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

برای استفاده از 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 که به سند offscreen اضافه کرده‌اید، روال تبدیل را فراخوانی کنید.

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

ثبت همزمان شنوندگان

ثبت یک شنونده (listener) به صورت غیرهمزمان (asynchronously) (مثلاً درون یک promise یا callback) تضمینی برای کار کردن در Manifest V3 ندارد. کد زیر را در نظر بگیرید.

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

این با یک صفحه پس‌زمینه ثابت کار می‌کند زیرا صفحه دائماً در حال اجرا است و هرگز دوباره مقداردهی اولیه نمی‌شود. در Manifest V3، سرویس ورکر هنگام ارسال رویداد دوباره مقداردهی اولیه می‌شود. این بدان معناست که وقتی رویداد اجرا می‌شود، شنونده‌ها ثبت نمی‌شوند (زیرا به صورت ناهمزمان اضافه می‌شوند) و رویداد از دست می‌رود.

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

chrome.action.onClicked.addListener(handleActionClick);

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

XMLHttpRequest() را با تابع global fetch() جایگزین کنید.

XMLHttpRequest() نمی‌توان از یک سرویس ورکر، افزونه یا موارد دیگر فراخوانی کرد. فراخوانی‌های XMLHttpRequest() از اسکریپت پس‌زمینه خود را با فراخوانی‌های تابع fetch() سراسری جایگزین کنید.

درخواست XMLHttp()
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);
واکشی ()
const response = await fetch('https://www.example.com/greeting.json'')
console.log(response.statusText);

حالت‌های پایدار

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

مثال زیر از یک متغیر سراسری برای ذخیره یک نام استفاده می‌کند. در یک سرویس ورکر، این متغیر می‌تواند در طول جلسه مرورگر کاربر چندین بار تنظیم مجدد شود.

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

اسکریپت پس‌زمینه Manifest V2
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

در عوض، از API هشدارها استفاده کنید. همانند سایر شنونده‌ها، شنونده‌های هشدار باید در سطح بالای اسکریپت شما ثبت شوند.

کارگر سرویس Manifest V3
async function startAlarm(name, duration) {
  await chrome.alarms.create(name, { delayInMinutes: 3 });
}

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

کارگر خدماتی را زنده نگه دارید

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

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

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

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

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

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

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

waitUntil(someExpensiveCalculation());

یک کارمند سرویس را به طور مداوم زنده نگه دارید

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

برای فعال نگه داشتن سرویس ورکر خود از قطعه کد زیر استفاده کنید:

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