Como desenvolver extensões para Chrome e Firefox

1. Fundamentos das Extensões de Navegador

Desenvolver extensões para navegadores exige compreender a arquitetura comum entre Chrome e Firefox, baseada em três componentes principais: o arquivo de manifesto (manifest.json), scripts de background e content scripts. O manifesto define metadados, permissões e recursos. No Chrome, o Manifest V3 é obrigatório desde 2023, utilizando service workers no lugar de background pages persistentes. O Firefox adota o padrão WebExtensions, compatível com a API do Chrome, mas com suporte ao Manifest V2 até 2025.

As principais diferenças incluem o namespace das APIs: Chrome usa chrome.*, enquanto Firefox usa browser.* (que retorna Promises nativamente). Permissões como storage e tabs são comuns, mas o Firefox exige browser_specific_settings para identificar o add-on.

2. Estrutura do Projeto e Configuração Inicial

Crie uma pasta para o projeto e adicione o arquivo manifest.json. Exemplo mínimo para Chrome (V3):

{
  "manifest_version": 3,
  "name": "Minha Extensão",
  "version": "1.0.0",
  "description": "Exemplo de extensão multiplataforma",
  "permissions": ["storage", "tabs"],
  "host_permissions": ["*://*/*"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  }
}

Para Firefox, adicione:

"browser_specific_settings": {
  "gecko": {
    "id": "minha-extensao@exemplo.com",
    "strict_min_version": "109.0"
  }
}

Use chrome://extensions (Chrome) com modo desenvolvedor ativado para carregar a pasta. No Firefox, use about:debugging#/runtime/this-firefox.

3. Ciclo de Vida e Scripts de Background

No Chrome V3, o background é um service worker que não persiste entre eventos. Exemplo de background.js:

// Chrome V3 - Service Worker
chrome.runtime.onInstalled.addListener(() => {
  console.log('Extensão instalada!');
  chrome.storage.local.set({ installed: true });
});

chrome.action.onClicked.addListener((tab) => {
  chrome.tabs.create({ url: 'https://exemplo.com' });
});

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.type === 'GET_DATA') {
    chrome.storage.local.get('data', (result) => {
      sendResponse(result);
    });
    return true; // Mantém a resposta assíncrona
  }
});

Para compatibilidade com Firefox, use polyfill ou adapte para browser.*:

// Firefox - usa browser.* que retorna Promise
browser.runtime.onInstalled.addListener(() => {
  browser.storage.local.set({ installed: true });
});

4. Content Scripts e Interação com Páginas Web

Content scripts são injetados no contexto da página. Defina no manifest.json:

"content_scripts": [
  {
    "matches": ["*://*/*"],
    "js": ["content.js"],
    "css": ["styles.css"]
  }
]

Exemplo de content.js:

// Envia mensagem para o background
chrome.runtime.sendMessage({ type: 'PAGE_LOADED', url: window.location.href }, (response) => {
  console.log('Resposta do background:', response);
});

// Manipula DOM
const button = document.createElement('button');
button.innerText = 'Clique aqui';
button.addEventListener('click', () => {
  chrome.runtime.sendMessage({ type: 'BUTTON_CLICKED' });
});
document.body.appendChild(button);

Para comunicação bidirecional, o background pode usar tabs.sendMessage:

// No background.js
chrome.tabs.sendMessage(tabId, { action: 'updateUI' }, (response) => {
  console.log('Content script respondeu:', response);
});

Restrições: content scripts não acessam variáveis da página (como window.jQuery) diretamente, a menos que injetem script via script tag.

5. Interface de Usuário: Popups, Sidebars e Action Buttons

Crie popup.html:

<!DOCTYPE html>
<html>
<head>
  <style>
    body { width: 300px; padding: 10px; font-family: Arial; }
    button { margin: 5px; padding: 8px; }
  </style>
</head>
<body>
  <h3>Minha Extensão</h3>
  <button id="save">Salvar</button>
  <button id="load">Carregar</button>
  <div id="output"></div>
  <script src="popup.js"></script>
</body>
</html>

popup.js:

document.getElementById('save').addEventListener('click', () => {
  chrome.storage.local.set({ data: 'exemplo' }, () => {
    document.getElementById('output').innerText = 'Salvo!';
  });
});

document.getElementById('load').addEventListener('click', () => {
  chrome.storage.local.get('data', (result) => {
    document.getElementById('output').innerText = result.data || 'Vazio';
  });
});

Para sidebar no Firefox, adicione ao manifest:

"sidebar_action": {
  "default_panel": "sidebar.html",
  "default_title": "Minha Sidebar"
}

6. Funcionalidades Avançadas e APIs Específicas

Controle de abas com chrome.tabs:

// Abrir nova aba
chrome.tabs.create({ url: 'https://exemplo.com', active: true });

// Fechar aba atual
chrome.tabs.remove(tabId);

// Obter aba ativa
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
  console.log('Aba ativa:', tabs[0].id);
});

Interceptação de rede com declarativeNetRequest (Chrome V3) — mais seguro que webRequest:

// No manifest.json
"declarative_net_request": {
  "rule_resources": [{
    "id": "ruleset_1",
    "enabled": true,
    "path": "rules.json"
  }]
}

// rules.json
[{
  "id": 1,
  "priority": 1,
  "action": { "type": "block" },
  "condition": {
    "urlFilter": "anuncio-tracker.com",
    "resourceTypes": ["script"]
  }
}]

7. Compatibilidade entre Chrome e Firefox

Use polyfill para unificar APIs. Exemplo:

// polyfill.js — carregue antes dos outros scripts
if (typeof browser === 'undefined') {
  var browser = chrome;
}

// Agora use browser.* em todo o código
browser.storage.local.get('key').then((result) => {
  console.log(result);
});

No manifest, diferencie versões mínimas:

// Chrome
"minimum_chrome_version": "109"

// Firefox
"browser_specific_settings": {
  "gecko": {
    "strict_min_version": "109.0"
  }
}

Ferramenta web-ext para testar no Firefox:

npm install -g web-ext
web-ext run --firefox=nightly

8. Publicação e Manutenção

Empacote a extensão:

  • Chrome: comprima a pasta em .zip e envie ao Chrome Web Store.
  • Firefox: use web-ext build para gerar .xpi ou envie o código-fonte para análise.

Antes de publicar, valide:

  • Teste em ambos navegadores.
  • Verifique permissões mínimas necessárias.
  • Monitore erros com console.log e ferramentas de depuração.

Para atualizações, incremente a versão no manifest.json e reenvie. Mantenha um changelog e teste regressivamente.

Referências