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

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

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

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

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

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

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

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

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

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

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

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

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

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

فیلد «پس‌زمینه» را در مانیفست به‌روزرسانی کنید

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

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

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

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

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

برای استفاده از Offscreen API، یک سند خارج از صفحه از Service Worker ایجاد کنید.

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

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

شنوندگان را به صورت همزمان ثبت کنید

ثبت نام یک شنونده به صورت ناهمزمان (به عنوان مثال در داخل یک وعده یا callback) تضمینی برای کار در Manifest V3 نیست. کد زیر را در نظر بگیرید.

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() با 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);
رفتن و آوردن()
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 جایگزین کنید.

کارگر خدمات مانیفست 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);

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

کارگر خدمات مانیفست V3
async function startAlarm(name, duration) {
  await chrome.alarms.create(name, { delayInMinutes: 3 });
}

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

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

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

یک کارگر خدماتی را تا پایان یک عملیات طولانی مدت زنده نگه دارید

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

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

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

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

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