Substituir páginas de eventos ou em segundo plano por um service worker
Um service worker substitui a página de segundo plano ou de eventos da extensão para garantir que o código em segundo plano fique fora da linha de execução principal. Isso permite que as extensões sejam executadas apenas quando necessário, economizando recursos.
As páginas em segundo plano são um componente fundamental das extensões desde que foram lançadas. Em outras palavras, as páginas em segundo plano oferecem um ambiente independente de qualquer outra janela ou guia. Isso permite que as extensões observem e reajam a eventos.
Esta página descreve as tarefas para converter páginas em segundo plano em service workers de extensão. Para mais informações sobre service workers de extensão em geral, consulte o tutorial Processar eventos com service workers e a seção Sobre service workers de extensão.
Diferenças entre scripts em segundo plano e service workers de extensão
Em alguns contextos, você verá service workers de extensão chamados de "scripts em segundo plano". Embora os service workers de extensão sejam executados em segundo plano, chamá-los de scripts em segundo plano é um pouco enganoso, pois implica recursos idênticos. As diferenças são descritas a seguir.
Mudanças nas páginas em segundo plano
Os service workers têm várias diferenças em relação às páginas em segundo plano.
- Elas funcionam fora da linha de execução principal, o que significa que não interferem no conteúdo da extensão.
- Eles têm recursos especiais, como interceptar eventos de busca na origem da extensão, como os de um pop-up da barra de ferramentas.
- Eles podem se comunicar e interagir com outros contextos pela interface de clientes.
Mudanças que você precisa fazer
Você precisará fazer alguns ajustes no código para considerar as diferenças entre a maneira como os scripts em segundo plano e os service workers funcionam. Para começar, a maneira como um service worker é especificado no arquivo de manifesto é diferente de como os scripts em segundo plano são especificados. Além disso:
- Como não é possível acessar o DOM ou a interface
window, você precisa mover essas chamadas para uma API diferente ou para um documento fora da tela. - Os listeners de eventos não devem ser registrados em resposta a promessas retornadas ou dentro de callbacks de eventos.
- Como eles não são compatíveis com versões anteriores do
XMLHttpRequest(), é necessário substituir as chamadas para essa interface por chamadas parafetch(). - Como elas são encerradas quando não estão em uso, é necessário manter os estados do aplicativo em vez de depender de variáveis globais. A interrupção de service workers também pode encerrar timers antes da conclusão. Você vai precisar substituí-los por alarmes.
Nesta página, descrevemos essas tarefas em detalhes.
Atualize o campo "background" no manifesto.
No Manifest V3, as páginas em segundo plano são substituídas por um service worker. As mudanças no manifesto estão listadas abaixo.
- Substitua
"background.scripts"por"background.service_worker"nomanifest.json. O campo"service_worker"aceita uma string, não uma matriz de strings. - Remova
"background.persistent"domanifest.json.
{ ... "background": { "scripts": [ "backgroundContextMenus.js", "backgroundOauth.js" ], "persistent": false }, ... }
{ ... "background": { "service_worker": "service_worker.js", "type": "module" } ... }
O campo "service_worker" usa uma única string. Você só vai precisar do campo "type" se usar módulos ES (com a palavra-chave import). O valor sempre será "module". Para mais informações, consulte Princípios básicos dos service workers de extensão.
Mover chamadas DOM e de janela para um documento fora da tela
Algumas extensões precisam de acesso ao DOM e aos objetos de janela sem abrir visualmente uma nova janela ou guia. A API Offscreen oferece suporte a esses casos de uso abrindo e fechando documentos não exibidos empacotados com a extensão, sem prejudicar a experiência do usuário. Exceto pela transmissão de mensagens, os documentos fora da tela não compartilham APIs com outros contextos de extensão, mas funcionam como páginas da Web completas para que as extensões interajam.
Para usar a API Offscreen, crie um documento fora da tela do service worker.
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
No documento fora da tela, execute qualquer ação que você teria executado em um script em segundo plano. Por exemplo, você pode copiar o texto selecionado na página do host.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Comunique-se entre documentos invisíveis e service workers de extensão usando a transmissão de mensagens.
Converter localStorage para outro tipo
A interface Storage da plataforma da Web (acessível em window.localStorage) não pode ser usada em um service worker. Para resolver isso, faça uma destas duas ações. Primeiro, você pode substituir por chamadas para outro mecanismo de armazenamento. O namespace chrome.storage.local atende à maioria dos casos de uso, mas outras opções estão disponíveis.
Também é possível mover as chamadas para um documento fora da tela. Por exemplo, para migrar dados armazenados anteriormente em localStorage para outro mecanismo:
- Crie um documento fora da tela com uma rotina de conversão e um gerenciador
runtime.onMessage. - Adicione uma rotina de conversão ao documento fora da tela.
- Na verificação do service worker da extensão, procure
chrome.storagepara encontrar seus dados. - Se os dados não forem encontrados, crie um documento fora da tela e chame
runtime.sendMessage()para iniciar a rotina de conversão. - No gerenciador
runtime.onMessageque você adicionou ao documento fora da tela, chame a rotina de conversão.
Também há algumas nuances sobre como as APIs de armazenamento da Web funcionam em extensões. Saiba mais em Armazenamento e cookies.
Registrar listeners de forma síncrona
Não há garantia de que o registro de um listener de forma assíncrona (por exemplo, em uma promessa ou callback) funcione no Manifest V3. Considere o código a seguir.
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
Isso funciona com uma página em segundo plano persistente porque ela está sempre em execução e nunca é reinicializada. No Manifest V3, o service worker será reinicializado quando o evento for enviado. Isso significa que, quando o evento for disparado, os listeners não serão registrados (já que são adicionados de forma assíncrona), e o evento será perdido.
Em vez disso, mova o registro do listener de eventos para o nível superior do script. Isso garante que o Chrome possa encontrar e invocar imediatamente o manipulador de cliques da sua ação, mesmo que a extensão não tenha terminado de executar a lógica de inicialização.
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
Substituir XMLHttpRequest() por fetch() global
XMLHttpRequest() não pode ser chamado de um service worker, extensão ou de outra forma. Substitua as chamadas do script em segundo plano para XMLHttpRequest() por chamadas para global fetch().
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);
Persistir estados
Os service workers são efêmeros, o que significa que eles provavelmente vão iniciar, executar e terminar repetidamente durante a sessão do navegador de um usuário. Isso também significa que os dados não ficam disponíveis imediatamente em variáveis globais, já que o contexto anterior foi desativado. Para contornar isso, use APIs de armazenamento como a fonte da verdade. Um exemplo vai mostrar como fazer isso.
O exemplo a seguir usa uma variável global para armazenar um nome. Em um service worker, essa variável pode ser redefinida várias vezes durante a sessão do navegador de um usuário.
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 o Manifest V3, substitua a variável global por uma chamada à API Storage.
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 }); });
Converter timers em alarmes
É comum usar operações atrasadas ou periódicas com os métodos setTimeout() ou setInterval(). No entanto, essas APIs podem falhar em service workers porque os timers são cancelados sempre que o service worker é encerrado.
// 3 minutes in milliseconds const TIMEOUT = 3 * 60 * 1000; setTimeout(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); }, TIMEOUT);
Em vez disso, use a API Alarms. Assim como outros listeners, os listeners de alarmes precisam ser registrados no nível superior do script.
async function startAlarm(name, duration) { await chrome.alarms.create(name, { delayInMinutes: 3 }); } chrome.alarms.onAlarm.addListener(() => { chrome.action.setIcon({ path: getRandomIconPath(), }); });
Manter o service worker ativo
Por definição, os service workers são orientados a eventos e terminam por inatividade. Assim, o Chrome pode otimizar o desempenho e o consumo de memória da extensão. Saiba mais na documentação sobre o ciclo de vida do service worker. Casos excepcionais podem exigir medidas adicionais para garantir que um service worker permaneça ativo por mais tempo.
Manter um service worker ativo até que uma operação de longa duração seja concluída
Durante operações de longa duração do service worker que não chamam APIs de extensão, o service worker pode ser encerrado no meio da operação. Por exemplo:
- Uma solicitação
fetch()que pode levar mais de cinco minutos (por exemplo, um download grande em uma conexão ruim). - Um cálculo assíncrono complexo que leva mais de 30 segundos.
Para estender a vida útil do service worker nesses casos, chame periodicamente uma API de extensão trivial para redefinir o contador de tempo limite. Observe que isso só é reservado para casos excepcionais. Na maioria das situações, geralmente há uma maneira melhor e idiomática da plataforma de alcançar o mesmo resultado.
O exemplo a seguir mostra uma função auxiliar waitUntil() que mantém o service worker ativo até que uma determinada promessa seja resolvida:
async function waitUntil(promise) {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Manter um service worker ativo continuamente
Em casos raros, é necessário estender a vida útil indefinidamente. Identificamos que os maiores casos de uso são empresas e educação, e permitimos especificamente nesses casos, mas não oferecemos suporte a isso em geral. Nessas circunstâncias excepcionais, manter um service worker ativo pode ser feito chamando periodicamente uma API de extensão trivial. É importante observar que essa recomendação se aplica apenas a extensões executadas em dispositivos gerenciados para casos de uso corporativos ou educacionais. Não é permitido em outros casos, e a equipe de extensões do Chrome se reserva o direito de tomar medidas contra essas extensões no futuro.
Use o snippet de código a seguir para manter seu service worker ativo:
/**
* 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'];
}