Processar eventos com service workers

Tutorial com conceitos de service worker de extensão

Visão geral

Este tutorial apresenta uma introdução aos service workers da extensão do Chrome. Como parte deste tutorial, você criará uma extensão que permite aos usuários navegar rapidamente até as páginas de referência da API do Chrome usando a omnibox. Você vai aprender o seguinte:

  • Registre seu service worker e importe módulos.
  • Depurar o service worker de extensão.
  • Gerenciar o estado e processar eventos.
  • Acione eventos periódicos.
  • Comunicar-se com scripts de conteúdo.

Antes de começar

Este guia pressupõe que você tenha experiência básica em desenvolvimento da Web. Recomendamos ler Extensions 101 e Hello World para uma introdução ao desenvolvimento de extensões.

Criar a extensão

Comece criando um novo diretório chamado quick-api-reference para armazenar os arquivos de extensão ou faça o download do código-fonte no nosso repositório de exemplos do GitHub (em inglês).

Etapa 1: registrar o service worker

Crie o arquivo de manifesto na raiz do projeto e adicione o seguinte código:

manifest.json:

{
  "manifest_version": 3,
  "name": "Open extension API reference",
  "version": "1.0.0",
  "icons": {
    "16": "images/icon-16.png",
    "128": "images/icon-128.png"
  },
  "background": {
    "service_worker": "service-worker.js",
  },
}

As extensões registram seu service worker no manifesto, que usa apenas um arquivo JavaScript. Não é necessário chamar navigator.serviceWorker.register(), como você faria em uma página da Web.

Crie uma pasta images e faça o download dos ícones nela.

Confira as primeiras etapas do tutorial sobre Tempo de leitura para saber mais sobre os metadados e ícones da extensão no manifesto.

Etapa 2: importar vários módulos de service worker

Nosso service worker implementa dois recursos. Para melhorar a manutenção, vamos implementar cada recurso em um módulo separado. Primeiro, precisamos declarar o service worker como um módulo ES no nosso manifesto, o que permite importar módulos para ele:

manifest.json:

{
 "background": {
    "service_worker": "service-worker.js",
    "type": "module"
  },
}

Crie o arquivo service-worker.js e importe dois módulos:

import './sw-omnibox.js';
import './sw-tips.js';

Crie esses arquivos e adicione um registro do console a cada um deles.

sw-omnibox.js:

console.log("sw-omnibox.js")

sw-tips.js:

console.log("sw-tips.js")

Consulte Como importar scripts para conhecer outras maneiras de importar vários arquivos em um service worker.

Opcional: como depurar o service worker

Vou explicar como encontrar os registros do service worker e saber quando ele foi encerrado. Primeiro, siga as instruções para Carregar uma extensão descompactada.

Após 30 segundos, vai aparecer "service worker (inativo)", o que significa que o service worker foi encerrado. Clique no link "service worker (inativo)" para inspecionar. A animação a seguir mostra isso.

Você notou que inspecionar o service worker a despertou? Abra o service worker no DevTools para mantê-lo ativo. Para garantir que sua extensão se comporte corretamente quando o service worker for encerrado, lembre-se de fechar o DevTools.

Agora, quebre a extensão para saber onde localizar erros. Uma maneira de fazer isso é excluir ".js" da importação de './sw-omnibox.js' no arquivo service-worker.js. O Chrome não poderá registrar o service worker.

Volte para chrome://extensions e atualize a extensão. Serão exibidos dois erros:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Consulte Como depurar extensões para saber mais maneiras de depurar o service worker de extensão.

Etapa 4: inicializar o estado

O Chrome vai desativar os service workers se eles não forem necessários. Usamos a API chrome.storage para manter o estado entre as sessões do service worker. Para acessar o armazenamento, precisamos solicitar a permissão no manifesto:

manifest.json:

{
  ...
  "permissions": ["storage"],
}

Primeiro, salve as sugestões padrão no armazenamento. Podemos inicializar o estado quando a extensão é instalada pela primeira vez detectando o evento runtime.onInstalled():

sw-omnibox.js:

...
// Save default API suggestions
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === 'install') {
    chrome.storage.local.set({
      apiSuggestions: ['tabs', 'storage', 'scripting']
    });
  }
});

Service workers não têm acesso direto ao objeto de janela e, portanto, não podem usar window.localStorage para armazenar valores. Além disso, os service workers são ambientes de execução de curta duração. Eles são encerrados repetidamente durante a sessão do navegador de um usuário, o que os torna incompatíveis com as variáveis globais. Em vez disso, use chrome.storage.local, que armazena dados na máquina local.

Consulte Persistir dados em vez de usar variáveis globais para saber mais sobre outras opções de armazenamento para service workers de extensão.

Etapa 5: registrar os eventos

Todos os listeners de eventos precisam estar estaticamente registrados no escopo global do service worker. Em outras palavras, os listeners de eventos não devem ser aninhados em funções assíncronas. Dessa forma, o Chrome pode garantir que todos os manipuladores de eventos sejam restaurados no caso de uma reinicialização do service worker.

Neste exemplo, vamos usar a API chrome.omnibox, mas primeiro precisamos declarar o acionador de palavra-chave da omnibox no manifesto:

manifest.json:

{
  ...
  "minimum_chrome_version": "102",
  "omnibox": {
    "keyword": "api"
  },
}

Agora, registre os listeners de eventos da omnibox no nível superior do script. Quando o usuário digita a palavra-chave da omnibox (api) na barra de endereço seguida por uma tecla ou espaço, o Chrome mostra uma lista de sugestões com base nas palavras-chave armazenadas. O evento onInputChanged(), que usa a entrada atual do usuário e um objeto suggestResult, é responsável por preencher essas sugestões.

sw-omnibox.js:

...
const URL_CHROME_EXTENSIONS_DOC =
  'https://developer.chrome.com/docs/extensions/reference/';
const NUMBER_OF_PREVIOUS_SEARCHES = 4;

// Display the suggestions after user starts typing
chrome.omnibox.onInputChanged.addListener(async (input, suggest) => {
  await chrome.omnibox.setDefaultSuggestion({
    description: 'Enter a Chrome API or choose from past searches'
  });
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  const suggestions = apiSuggestions.map((api) => {
    return { content: api, description: `Open chrome.${api} API` };
  });
  suggest(suggestions);
});

Depois que o usuário seleciona uma sugestão, o onInputEntered() abre a página de referência da API Chrome correspondente.

sw-omnibox.js:

...
// Open the reference page of the chosen API
chrome.omnibox.onInputEntered.addListener((input) => {
  chrome.tabs.create({ url: URL_CHROME_EXTENSIONS_DOC + input });
  // Save the latest keyword
  updateHistory(input);
});

A função updateHistory() recebe a entrada da omnibox e a salva em storage.local. Dessa forma, o termo de pesquisa mais recente pode ser usado posteriormente como uma sugestão da omnibox.

sw-omnibox.js:

...
async function updateHistory(input) {
  const { apiSuggestions } = await chrome.storage.local.get('apiSuggestions');
  apiSuggestions.unshift(input);
  apiSuggestions.splice(NUMBER_OF_PREVIOUS_SEARCHES);
  return chrome.storage.local.set({ apiSuggestions });
}

Etapa 6: configurar um evento recorrente

Os métodos setTimeout() ou setInterval() costumam ser usados para realizar tarefas atrasadas ou periódicas. No entanto, essas APIs podem falhar porque o programador cancelará os timers quando o service worker for encerrado. Em vez disso, as extensões podem usar a API chrome.alarms.

Comece solicitando a permissão "alarms" no manifesto. Além disso, para buscar as dicas de extensão em um local hospedado remoto, você precisa solicitar a permissão de host:

manifest.json:

{
  ...
  "permissions": ["storage", "alarms"],
  "permissions": ["storage"],
  "host_permissions": ["https://extension-tips.glitch.me/*"],
}

A extensão buscará todas as dicas, escolherá uma aleatoriamente e a salvará no armazenamento. Vamos criar um alarme que será acionado uma vez por dia para atualizar a gorjeta. Os alarmes não são salvos quando você fecha o Chrome. Então, precisamos verificar se o alarme existe e criá-lo caso não exista.

sw-tips.js:

// Fetch tip & save in storage
const updateTip = async () => {
  const response = await fetch('https://extension-tips.glitch.me/tips.json');
  const tips = await response.json();
  const randomIndex = Math.floor(Math.random() * tips.length);
  return chrome.storage.local.set({ tip: tips[randomIndex] });
};

const ALARM_NAME = 'tip';

// Check if alarm exists to avoid resetting the timer.
// The alarm might be removed when the browser session restarts.
async function createAlarm() {
  const alarm = await chrome.alarms.get(ALARM_NAME);
  if (typeof alarm === 'undefined') {
    chrome.alarms.create(ALARM_NAME, {
      delayInMinutes: 1,
      periodInMinutes: 1440
    });
    updateTip();
  }
}

createAlarm();

// Update tip once a day
chrome.alarms.onAlarm.addListener(updateTip);

Etapa 7: comunicar-se com outros contextos

As extensões usam scripts de conteúdo para ler e modificar o conteúdo da página. Quando um usuário visita uma página de referência da API do Chrome, o script de conteúdo da extensão atualiza a página com a dica do dia. Ele envia uma mensagem para solicitar a gorjeta do dia do service worker.

Comece declarando o script de conteúdo no manifesto e adicione o padrão de correspondência correspondente à documentação de referência da API Chrome.

manifest.json:

{
  ...
  "content_scripts": [
    {
      "matches": ["https://developer.chrome.com/docs/extensions/reference/*"],
      "js": ["content.js"]
    }
  ]
}

Crie um novo arquivo de conteúdo. O código a seguir envia uma mensagem ao service worker solicitando a gorjeta. Em seguida, adiciona um botão que abrirá um pop-up contendo a gorjeta da extensão. Esse código usa a nova API Popover da plataforma da Web.

content.js:

(async () => {
  // Sends a message to the service worker and receives a tip in response
  const { tip } = await chrome.runtime.sendMessage({ greeting: 'tip' });

  const nav = document.querySelector('.upper-tabs > nav');
  
  const tipWidget = createDomElement(`
    <button type="button" popovertarget="tip-popover" popovertargetaction="show" style="padding: 0 12px; height: 36px;">
      <span style="display: block; font: var(--devsite-link-font,500 14px/20px var(--devsite-primary-font-family));">Tip</span>
    </button>
  `);

  const popover = createDomElement(
    `<div id='tip-popover' popover style="margin: auto;">${tip}</div>`
  );

  document.body.append(popover);
  nav.append(tipWidget);
})();

function createDomElement(html) {
  const dom = new DOMParser().parseFromString(html, 'text/html');
  return dom.body.firstElementChild;
}

A etapa final é adicionar um gerenciador de mensagens ao nosso service worker que envia uma resposta ao script de conteúdo com a gorjeta diária.

sw-tips.js:

...
// Send tip to content script via messaging
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.greeting === 'tip') {
    chrome.storage.local.get('tip').then(sendResponse);
    return true;
  }
});

Testar se funciona

Verifique se a estrutura de arquivos do seu projeto é semelhante a esta:

O conteúdo da pasta de extensão: images folder, manifest.json, service-worker.js, sw-omnibox.js, sw-tips.js e content.js.

Carregar sua extensão localmente

Para carregar uma extensão descompactada no modo de desenvolvedor, siga as etapas em Hello world.

Abrir uma página de referência

  1. Digite a palavra-chave "api" na barra de endereço do navegador.
  2. Pressione a tecla "Tab" ou "espaço".
  3. Insira o nome completo da API.
    • OU escolha de uma lista de pesquisas anteriores
  4. Uma nova página será aberta com a referência da API do Chrome.

O código vai ficar assim:

Referência rápida da API que abre a referência da API de ambiente de execução
Extensão de API rápida abrindo a API Runtime.

Abrir a dica do dia

Clique no botão Tip localizado na barra de navegação para abrir a gorjeta da extensão.

Abrir a gorjeta diária em
Extensão de API rápida abrindo a dica do dia.

🎯 Possíveis melhorias

Com base no que você aprendeu hoje, tente realizar uma das seguintes ações:

  • Explore outra maneira de implementar as sugestões da omnibox.
  • Crie seu próprio modal personalizado para exibir a gorjeta da extensão.
  • Abra uma página adicional para as páginas da API de referência das extensões da Web do MDN.

Continue desenvolvendo!

Parabéns por concluir este tutorial 🎉. Continue aprimorando suas habilidades concluindo outros tutoriais para iniciantes:

Extensão Conteúdo do laboratório
Tempo de leitura Inserir um elemento em um conjunto específico de páginas automaticamente.
Gerenciador de guias Para criar um pop-up que gerencie as guias do navegador.
Modo de foco Para executar o código na página atual depois de clicar na ação da extensão.

Continue descobrindo

Para continuar seu programa de aprendizado do service worker de extensão, recomendamos consultar os seguintes artigos: