Migra a un service worker

Reemplaza páginas de fondo o de eventos con un trabajador de servicio

Un trabajador de servicio reemplaza la página de eventos o en segundo plano de la extensión para garantizar que el código en segundo plano no esté en el subproceso principal. Esto permite que las extensiones se ejecuten solo cuando sea necesario, lo que ahorra recursos.

Las páginas en segundo plano han sido un componente fundamental de las extensiones desde su introducción. En pocas palabras, las páginas en segundo plano proporcionan un entorno independiente de cualquier otra ventana o pestaña. Esto permite que las extensiones observen y actúen en respuesta a eventos.

En esta página, se describen las tareas para convertir páginas en segundo plano en trabajadores del servicio de extensión. Para obtener más información sobre los trabajadores de servicio de extensión en general, consulta el instructivo Controla eventos con trabajadores de servicio y la sección Acerca de los trabajadores de servicio de extensión.

Diferencias entre las secuencias de comandos en segundo plano y los trabajadores del servicio de extensión

En algunos contextos, verás los trabajadores del servicio de extensión llamados "secuencias de comandos en segundo plano". Aunque los service workers de la extensión se ejecutan en segundo plano, llamarlos secuencias de comandos en segundo plano es un poco engañoso, ya que implica capacidades idénticas. Las diferencias se describen a continuación.

Cambios desde las páginas en segundo plano

Los service workers tienen varias diferencias con las páginas en segundo plano.

  • Funcionan fuera del subproceso principal, lo que significa que no interfieren con el contenido de la extensión.
  • Tienen capacidades especiales, como interceptar eventos de recuperación en el origen de la extensión, como los de una ventana emergente de la barra de herramientas.
  • Pueden comunicarse e interactuar con otros contextos a través de la interfaz de clientes.

Cambios que deberás realizar

Deberás realizar algunos ajustes de código para considerar las diferencias entre la forma en que funcionan las secuencias de comandos en segundo plano y los service workers. En primer lugar, la forma en que se especifica un service worker en el archivo de manifiesto es diferente de la forma en que se especifican las secuencias de comandos en segundo plano. Además:

  • Como no pueden acceder al DOM ni a la interfaz window, deberás mover esas llamadas a una API diferente o a un documento fuera de la pantalla.
  • Los objetos de escucha de eventos no deben registrarse en respuesta a promesas que se devuelven ni dentro de devoluciones de llamada de eventos.
  • Dado que no son retrocompatibles con XMLHttpRequest(), deberás reemplazar las llamadas a esta interfaz por llamadas a fetch().
  • Dado que se cancelan cuando no están en uso, deberás conservar los estados de la aplicación en lugar de depender de variables globales. La finalización de los service workers también puede finalizar los temporizadores antes de que se completen. Deberás reemplazarlas por alarmas.

En esta página, se describen estas tareas en detalle.

Cómo actualizar el campo “segundo plano” en el manifiesto

En Manifest V3, las páginas en segundo plano se reemplazan por un service worker. A continuación, se indican los cambios en el manifiesto.

  • Reemplaza "background.scripts" por "background.service_worker" en manifest.json. Ten en cuenta que el campo "service_worker" toma una cadena, no un array de cadenas.
  • Quita "background.persistent" de manifest.json.
Manifest V2
{
  ...
  "background": {
    "scripts": [
      "backgroundContextMenus.js",
      "backgroundOauth.js"
    ],
    "persistent": false
  },
  ...
}
Manifest V3
{
  ...
  "background": {
    "service_worker": "service_worker.js",
    "type": "module"
  }
  ...
}

El campo "service_worker" toma una sola cadena. Solo necesitarás el campo "type" si usas módulos de ES (con la palabra clave import). Su valor siempre será "module". Para obtener más información, consulta Conceptos básicos del trabajador de servicio de extensión.

Cómo mover las llamadas de DOM y ventana a un documento fuera de la pantalla

Algunas extensiones necesitan acceso al DOM y a los objetos de ventana sin abrir visualmente una ventana o pestaña nueva. La API de Offscreen admite estos casos de uso abriendo y cerrando documentos no mostrados empaquetados con la extensión, sin interrumpir la experiencia del usuario. Excepto por el envío de mensajes, los documentos fuera de la pantalla no comparten APIs con otros contextos de extensión, sino que funcionan como páginas web completas con las que las extensiones pueden interactuar.

Para usar la API de Offscreen, crea un documento fuera de pantalla desde el service worker.

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

En el documento fuera de pantalla, realiza cualquier acción que antes ejecutarías en una secuencia de comandos en segundo plano. Por ejemplo, puedes copiar el texto seleccionado en la página del host.

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

Comunicarse entre documentos fuera de la pantalla y trabajadores de servicios de extensión mediante el envío de mensajes

Convierte localStorage a otro tipo

No se puede usar la interfaz Storage de la plataforma web (a la que se puede acceder desde window.localStorage) en un trabajador de servicio. Para solucionarlo, sigue uno de los pasos que se indican a continuación. Primero, puedes reemplazarlo con llamadas a otro mecanismo de almacenamiento. El espacio de nombres chrome.storage.local se usará para la mayoría de los casos de uso, pero hay otras opciones disponibles.

También puedes mover sus llamadas a un documento fuera de la pantalla. Por ejemplo, para migrar datos almacenados previamente en localStorage a otro mecanismo, haz lo siguiente:

  1. Crea un documento fuera de pantalla con una rutina de conversión y un controlador runtime.onMessage.
  2. Agregar una rutina de conversión al documento fuera de pantalla
  3. En el servicio de trabajo de la extensión, busca chrome.storage para ver tus datos.
  4. Si no se encuentran tus datos, create un documento fuera de la pantalla y llama a runtime.sendMessage() para iniciar la rutina de conversión.
  5. En el controlador runtime.onMessage que agregaste al documento fuera de pantalla, llama a la rutina de conversión.

También hay algunos matices en el funcionamiento de las APIs de almacenamiento web en las extensiones. Obtén más información en Almacenamiento y cookies.

Registra objetos de escucha de forma síncrona

No se garantiza que registrar un objeto de escucha de forma asíncrona (por ejemplo, dentro de una promesa o devolución de llamada) funcione en Manifest V3. Considera el siguiente código.

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

Esto funciona con una página en segundo plano persistente porque la página se ejecuta constantemente y nunca se reinicia. En Manifest V3, el service worker se volverá a inicializar cuando se envíe el evento. Esto significa que, cuando se active el evento, no se registrarán los objetos de escucha (ya que se agregan de forma asíncrona) y se perderá el evento.

En su lugar, mueve el registro del objeto de escucha de eventos al nivel superior de la secuencia de comandos. Esto garantiza que Chrome pueda encontrar e invocar inmediatamente el controlador de clics de tu acción, incluso si la extensión no terminó de ejecutar su lógica de inicio.

chrome.action.onClicked.addListener(handleActionClick);

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

Reemplaza XMLHttpRequest() por fetch() global

No se puede llamar a XMLHttpRequest() desde un service worker, una extensión ni de otra forma. Reemplaza las llamadas de tu secuencia de comandos en segundo plano a XMLHttpRequest() por llamadas a fetch() global.

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);
recuperar()
const response = await fetch('https://www.example.com/greeting.json'')
console.log(response.statusText);

Cómo conservar estados

Los trabajadores de servicio son efímeros, lo que significa que es probable que se inicien, ejecuten y finalicen de forma repetida durante la sesión del navegador de un usuario. También significa que los datos no están disponibles de inmediato en las variables globales, ya que se derribó el contexto anterior. Para solucionar este problema, usa las APIs de almacenamiento como fuente de información. A continuación, se muestra un ejemplo de cómo hacerlo.

En el siguiente ejemplo, se usa una variable global para almacenar un nombre. En un service worker, esta variable se puede restablecer varias veces durante la sesión del navegador de un usuario.

Secuencia de comandos en segundo plano de 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 });
});

Para el manifiesto v3, reemplaza la variable global por una llamada a la API de Storage.

Service worker de 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 });
});

Convierte temporizadores en alarmas

Es común usar operaciones retrasadas o periódicas mediante los métodos setTimeout() o setInterval(). Sin embargo, estas APIs pueden fallar en los trabajadores del servicio porque los temporizadores se cancelan cada vez que se finaliza el trabajador del servicio.

Secuencia de comandos en segundo plano de Manifest V2
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
  chrome.action.setIcon({
    path: getRandomIconPath(),
  });
}, TIMEOUT);

En su lugar, usa la API de Alarms. Al igual que con otros objetos de escucha, los objetos de escucha de alarma deben registrarse en el nivel superior de la secuencia de comandos.

Service worker de Manifest V3
async function startAlarm(name, duration) {
  await chrome.alarms.create(name, { delayInMinutes: 3 });
}

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

Mantén activo el trabajador de servicio

Por definición, los service workers se basan en eventos y finalizarán cuando haya inactividad. De esta manera, Chrome puede optimizar el rendimiento y el consumo de memoria de tu extensión. Obtén más información en nuestra documentación sobre el ciclo de vida de los trabajadores del servicio. En casos excepcionales, es posible que se requieran medidas adicionales para garantizar que un trabajador de servicio permanezca activo durante más tiempo.

Mantén un service worker activo hasta que finalice una operación de larga duración

Durante las operaciones de service worker de larga duración que no llaman a las APIs de extensión, es posible que el service worker se cierre durante la operación. Los siguientes son algunos ejemplos:

  • Es una solicitud fetch() que podría tardar más de cinco minutos (p.ej., una descarga grande con una conexión potencialmente deficiente).
  • Un cálculo asíncrono complejo que tarda más de 30 segundos.

Para extender la vida útil del trabajador de servicio en estos casos, puedes llamar periódicamente a una API de extensión trivial para restablecer el contador de tiempo de espera. Ten en cuenta que esto solo se reserva para casos excepcionales y, en la mayoría de las situaciones, suele haber una mejor manera idiomática de la plataforma para lograr el mismo resultado.

En el siguiente ejemplo, se muestra una función auxiliar waitUntil() que mantiene activo tu service worker hasta que se resuelve una promesa determinada:

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

waitUntil(someExpensiveCalculation());

Mantén un trabajador de servicio activo de forma continua

En casos excepcionales, es necesario extender el ciclo de vida de forma indefinida. Identificamos a la empresa y la educación como los casos de uso más importantes, y lo permitimos específicamente allí, pero no lo admitimos en general. En estas circunstancias excepcionales, se puede mantener activo un service worker llamando periódicamente a una API de extensión trivial. Es importante tener en cuenta que esta recomendación solo se aplica a las extensiones que se ejecutan en dispositivos administrados para casos de uso empresariales o educativos. No se permite en otros casos, y el equipo de extensiones de Chrome se reserva el derecho de tomar medidas contra esas extensiones en el futuro.

Usa el siguiente fragmento de código para mantener activo tu trabajador de servicio:

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