Processar eventos com service workers

Tutorial que aborda os conceitos de service worker de extensão

Visão geral

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

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

Antes de começar

Este guia pressupõe que você tenha experiência básica em desenvolvimento na Web. Recomendamos a análise Extensões 101 e Hello World para uma introdução a 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 (link 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 o service worker no manifesto, que só usa 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 o tempo de leitura para saber mais sobre os metadados e os ícones da extensão no manifesto.

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

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

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 aprender outras maneiras de importar vários arquivos em um service worker.

Opcional: como depurar o service worker

Explicarei 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, será exibido "service worker (inativo)". ou seja, o service worker foi encerrado. Clique em "service worker (inativo)" para inspecioná-la. A animação a seguir mostra isso.

Você notou que a inspeção do service worker o despertou? A abertura do service worker no DevTools vai mantê-lo ativo. Para garantir que a extensão se comporte corretamente quando o service worker for encerrado, feche o DevTools.

Agora, interrompa a extensão para saber onde localizar erros. Uma maneira de fazer isso é excluir ".js" da importação './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. Você vai encontrar dois erros:

Service worker registration failed. Status code: 3.

An unknown error occurred when fetching the script.

Consulte Como depurar extensões para conhecer outras formas de depurar o service worker de extensão.

Etapa 4: inicializar o estado

O Chrome encerrará 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 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']
    });
  }
});

Os service workers não têm acesso direto ao objeto window 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 do usuário, o que os torna incompatíveis com 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 aprender sobre outras opções de armazenamento para service workers de extensão.

Etapa 5: registrar seus eventos

Todos os listeners de eventos precisam ser registrados estaticamente 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 guia ou espaço, o Chrome exibe 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 selecionar uma sugestão, o onInputEntered() abrirá 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() usa 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 executar tarefas atrasadas ou periódicas tarefas. No entanto, essas APIs podem falhar porque o programador cancelará os timers quando o serviço 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 de um local hospedado remotamente, você precisa solicitar a permissão de host:

manifest.json:

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

A extensão buscará todas as dicas, escolherá uma aleatoriamente e a salvará no armazenamento. Criaremos 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. Portanto, 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 acessa uma página de referência da API do Google 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-over contendo a gorjeta da extensão. Esse código usa a nova plataforma da Web, API Popover.

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 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ões: 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. Insira 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 página de referência da API do Chrome.

Ele será parecido com o seguinte:

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

Abrir a dica do dia

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

Abrir a dica diária em
Extensão de API rápida que abre 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 de extensões da Web do MDN.

Continue criando!

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

Extensão O que você vai aprender
Tempo de leitura Inserir um elemento em um conjunto específico de páginas automaticamente.
Gerenciador de guias Criar um pop-up que gerencie guias do navegador.
Modo sem distrações 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 sobre service workers de extensão, recomendamos consultar os seguintes artigos: