O Manifest V3 agora é a única opção para novas extensões do Chrome e muda significativamente a arquitetura: as páginas em segundo plano tornaram-se service workers efêmeros, a execução remota de código foi proibida e a interceptação de rede foi movida para uma API declarativa. Se você aprendeu o desenvolvimento de extensões na V2, vários hábitos não funcionam mais. Este guia cria uma extensão completa e funcional do zero sob as regras V3.
📋 Table of Contents
- O que estamos construindo
- Etapa 1: Estrutura do Projeto e Manifesto
- Etapa 2: O Service Worker não é uma página de plano de fundo
- Etapa 3: O script de conteúdo
- Etapa 4: o pop-up
- Etapa 5: opções de armazenamento
- Etapa 6: Interceptação de rede alterada
- Etapa 7: Carregar e depurar
- Etapa 8: Publicação
- Erros Comuns
- Conclusão
O que estamos construindo
Uma extensão de anotação de página: adiciona um pop-up na barra de ferramentas, injeta um script de conteúdo que destaca o texto selecionado em qualquer página, salva os destaques por URL e os sincroniza por meio dechrome.storage. Ele exercita todas as partes da V3 que você realmente usará – interface de usuário pop-up, script de conteúdo, service worker, mensagens, armazenamento e permissões.
Etapa 1: Estrutura do Projeto e Manifesto
Crie um diretório com esses arquivos. O manifesto é o ponto de entrada e o Chrome o lê primeiro.
my-extension/
manifest.json
background.js
content.js
popup.html
popup.js
styles.css
icons/icon16.png icon48.png icon128.png
O manifesto declara a versão 3, seus pontos de entrada e permissões. Mantenha as permissões mínimas – cada permissão extra retarda a revisão e assusta os usuários no momento da instalação.
{
"manifest_version": 3,
"name": "Page Highlighter",
"version": "1.0.0",
"description": "Highlight and save text on any page.",
"permissions": ["storage", "activeTab", "scripting"],
"host_permissions": ["http://*/*", "https://*/*"],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"],
"css": ["styles.css"],
"run_at": "document_idle"
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
PrefiraactiveTab sobre amplas permissões de host quando puder. Ele concede acesso à guia atual somente depois que o usuário clica no ícone da extensão, o que é muito mais fácil de justificar na revisão.
Etapa 2: O Service Worker não é uma página de plano de fundo
Esta é a maior mudança do V3. Seu script em segundo plano é um service worker que o Chrome encerra quando está inativo e reinicia no próximo evento. Qualquer variável definida no nível superior desaparece após o encerramento. O estado deve morar emchrome.storage, não na memória.
// background.js
// WRONG under V3 — this resets every time the worker restarts.
// let highlightCount = 0;
chrome.runtime.onInstalled.addListener(async () => {
const { highlights } = await chrome.storage.local.get('highlights');
if (!highlights) {
await chrome.storage.local.set({ highlights: {} });
}
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'SAVE_HIGHLIGHT') {
saveHighlight(message.payload).then(() => sendResponse({ ok: true }));
// Returning true keeps the message channel open for the async reply.
return true;
}
});
async function saveHighlight({ url, text }) {
const { highlights = {} } = await chrome.storage.local.get('highlights');
const forPage = highlights[url] || [];
forPage.push({ text, createdAt: Date.now() });
highlights[url] = forPage;
await chrome.storage.local.set({ highlights });
}
Oreturn true no ouvinte de mensagem é fácil de perder e causa um bug clássico: seu manipulador assíncrono é concluído, mas o remetente nunca recebe a resposta, porque o Chrome fechou o canal quando o ouvinte retornou indefinido.
Etapa 3: O script de conteúdo
Os scripts de conteúdo são executados no DOM da página, mas em um mundo JavaScript isolado. Eles podem ler e modificar o DOM, mas não podem ver as próprias variáveis JavaScript da página. Esse isolamento é um recurso de segurança, não uma limitação para contornar.
// content.js
document.addEventListener('mouseup', async () => {
const selection = window.getSelection();
const text = selection.toString().trim();
if (text.length < 3) return;
const range = selection.getRangeAt(0);
const mark = document.createElement('mark');
mark.className = 'ext-highlight';
try {
range.surroundContents(mark);
} catch {
// surroundContents throws when the selection crosses element boundaries.
return;
}
await chrome.runtime.sendMessage({
type: 'SAVE_HIGHLIGHT',
payload: { url: location.href, text }
});
selection.removeAllRanges();
});
Observe otry/catch por aísurroundContents. É lançado sempre que a seleção abrange vários elementos, o que acontece constantemente em páginas reais. Lidar com isso é a diferença entre uma extensão que funciona na sua página de teste e outra que funciona em qualquer lugar.
Etapa 4: o pop-up
O pop-up é uma página da web comum com acesso a APIs de extensão. Ele é destruído toda vez que é fechado, portanto, trate-o como sem estado e leia-o do armazenamento aberto.
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="width:320px;font:14px system-ui;padding:12px">
<h1 style="font-size:15px;margin:0 0 8px">Highlights on this page</h1>
<ul id="list"></ul>
<script src="popup.js"></script>
</body>
</html>
Os scripts embutidos são bloqueados pela política de segurança de conteúdo de extensão, portanto<script src> a referência é obrigatória — você não pode colocar JavaScript diretamente no HTML.
// popup.js
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const { highlights = {} } = await chrome.storage.local.get('highlights');
const items = highlights[tab.url] || [];
const list = document.getElementById('list');
if (items.length === 0) {
list.innerHTML = '<li>No highlights yet.</li>';
} else {
for (const item of items) {
const li = document.createElement('li');
li.textContent = item.text; // textContent, never innerHTML
list.appendChild(li);
}
}
UsartextContent em vez deinnerHTML para qualquer valor originado de uma página da web. O texto destacado são dados controlados pelo invasor, e construir HTML a partir dele é uma rota XSS direta para o contexto privilegiado da sua extensão.
Etapa 5: opções de armazenamento
O Chrome oferece três áreas de armazenamento e a escolha errada causa falhas silenciosas.
| Área | Cota | Usar para |
|---|---|---|
storage.local |
~10MB (ilimitado com permissão) | Dados em massa, conteúdo em cache |
storage.sync |
~100 KB no total, 8 KB por item | Pequenas configurações de usuário, sincronizadas entre dispositivos |
storage.session |
~10 MB, apenas memória | Dados que não devem sobreviver à reinicialização do navegador |
O erro comum é colocar conteúdo do usuário emstorage.sync porque a sincronização parece desejável. O limite de 8 KB por item é atingido rapidamente e as gravações falham — muitas vezes silenciosamente, se você não verificar se há erros.
Etapa 6: Interceptação de rede alterada
O bloqueiowebRequest A API desapareceu. Se você precisar bloquear ou redirecionar solicitações, usedeclarativeNetRequest, onde você registra regras estáticas que o próprio Chrome avalia. Sua extensão nunca vê a solicitação.
{
"permissions": ["declarativeNetRequest"],
"declarative_net_request": {
"rule_resources": [{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}]
}
}
Isso é mais restritivo por natureza e é por isso que vários bloqueadores de anúncios tiveram que ser reescritos. Se o valor principal da sua extensão depende da inspeção dos corpos da solicitação em tempo de execução, a V3 pode realmente não suportá-la.
Etapa 7: Carregar e depurar
Abrirchrome://extensions, ative o modo de desenvolvedor e escolha “Carregar descompactado”. Existem três consoles separados e saber qual abrir economiza horas:
- Trabalhador de serviço: clique no link “trabalhador de serviço” em seu cartão de extensão
- Pop-up: clique com o botão direito no pop-up e escolha Inspecionar
- Roteiro de conteúdo: o console do DevTools da página normal, com o seletor de contexto definido para sua extensão
Se o service worker parecer morto, isso é esperado – ele termina após cerca de 30 segundos de inatividade. Acione um evento e ele será reiniciado.
Etapa 8: Publicação
Compacte o conteúdo do diretório de extensão (não a pasta anexa) e faça upload por meio do Painel do desenvolvedor da Chrome Web Store, que exige uma taxa única de registro. O tempo de revisão depende muito de suas permissões: uma extensão usando apenasactiveTab estorage normalmente limpa a revisão rapidamente, enquanto amplas permissões de host maisscripting atrair revisão manual e pode levar muito mais tempo. Escreva uma justificativa clara para cada permissão na listagem – os revisores rejeitam explicações vagas.
Erros Comuns
Armazenando estado em globais de service workers. Ele desaparece na rescisão. Usarchrome.storage.
Esquecendoreturn true em ouvintes de mensagens assíncronas. A resposta nunca chega e o fracasso é silencioso.
Solicitando<all_urls> quandoactiveTab faria. Isso retarda a revisão e reduz as instalações.
Carregando código remoto. V3 proíbe isso completamente. Todo o código executável deve ser enviado no pacote, o que significa que não há scripts CDN e nãoeval.
Conclusão
O desenvolvimento do Manifest V3 se resume a algumas disciplinas:trate o trabalhador de serviço como apátrida e mantenha todo o estado emchrome.storage, solicite as permissões mais restritas que funcionam, usetextContent para qualquer coisa proveniente de uma página, lembre-sereturn true para manipuladores de mensagens assíncronas e envie cada linha de código dentro do pacote. Construa com essas restrições desde o início e a plataforma permanecerá fora do seu caminho – adaptá-las em um design em forma de V2 é onde reside a dor.
🔗 Share this article
✍️ Leave a Comment