
Por que extensões web?
Houve um momento em que eu fazia muitas palestras em conferências, e isso diminuiu bastante durante a pandemia, mas ocasionalmente eu ainda falo com as pessoas sobre construir coisas na web. Então eu dei uma palestra intitulada “ Expandindo a experiência do navegador com extensões da web ” na InternationalJS Singapore.
Meu interesse em extensões da web surgiu do fato de que nossa equipe na Interledger Foundation tem trabalhado para implementar o padrão de monetização da web em navegadores.
Mas a maioria de nós familiarizados com os padrões da web sabe que, realisticamente, incluir um novo recurso nos navegadores é comparável a ver seu filho pequeno finalmente fazer as malas e ir para a faculdade.
Não, estou só brincando.
Tipo.
A próxima melhor coisa que poderíamos fazer seria criar uma extensão web que implementasse todos os comportamentos especificados no padrão para que pudéssemos obter feedback sobre o que estávamos propondo, permitindo-nos fazer ajustes em como imaginávamos que as APIs funcionariam e, com sorte, melhorar a experiência geral de monetização da web.
O que é uma extensão web?
Este post NÃO falará sobre o que os meninos fizeram para construir a monetização da web porque isso agora se tornou uma fera de um pedaço de software neste momento. Você pode ler o post do blog de Sid Vishnoi sobre Testes de ponta a ponta da extensão do navegador de monetização da web em nosso blog de engenharia para ter uma ideia do escopo da extensão.
Não, vamos ao básico do básico das extensões da web. Porque temos que aprender a virar e sentar antes mesmo de ficarmos de pé.
O grupo comunitário de extensões da Web no W3C foi lançado em 2021, iniciado pela Apple, Google, Microsoft e Mozilla com o objetivo de alinhar uma visão comum para extensões de navegador e trabalhar em direção à padronização futura.
O coração de uma extensão é o arquivo manifest.json , que é o único arquivo obrigatório que deve existir em qualquer extensão da web. O arquivo manifest contém metadados sobre a extensão, bem como ponteiros para outros arquivos que compõem a extensão.
Esses outros arquivos incluem scripts de segundo plano, barras laterais, pop-ups e páginas de opções, páginas de extensão, scripts de conteúdo e recursos acessíveis pela web.
Este arquivo manifest.json tem uma sintaxe específica e, no momento da escrita, a sintaxe mais recente deveria ser v3. Se você nunca acompanhou o desenvolvimento de extensões da web ao longo do tempo, talvez não saiba que tem havido muita controvérsia em torno da v3.
Não vou entrar em todos os detalhes, mas para resumir as principais críticas: a v3 reduziu as extensões da web de serem tratadas como um aplicativo de primeira classe com seu próprio ambiente de execução persistente para serem tratadas como acessórios com privilégios limitados e capacidades de execução reativa. Isso é em grande parte uma consequência de tornar os service workers obrigatórios e remover o mecanismo de “bloqueio de webRequest”.
Sinta-se à vontade para pedir mais informações ao Google se estiver interessado.
O manifesto.json
Aqui está um exemplo de uma extensão que não faz nada além de mostrar os requisitos básicos para que o navegador reconheça que uma extensão existe:
{
"manifest_version": 3,
"name": "AE1",
"version": "1.0",
"description": "This extension doesn't actually do anything",
"icons": {
"32": "icons/icon32.png",
"48": "icons/icon48.png"
},
"action": {
"default_popup": "nothing.html"
}
}
Código completo aqui: https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE1
O manifest.json terá as 3 chaves obrigatórias, of manifest_version
, name
e version
. Incluídas no exemplo também estão as chaves description
, icons
e .action
Embora description
e icons
sejam opcionais, eles são obrigatórios se você quiser publicar sua extensão na Chrome Web Store. Eles também melhoram a experiência do usuário, porque dão à sua extensão um ícone bonito e uma descrição adequada.
action
determina como a extensão aparece na barra de ferramentas do navegador e o que acontece ao clicar nela. popup
pode conter qualquer conteúdo HTML e a janela será dimensionada automaticamente para se ajustar ao conteúdo.
<html>
<body>
<h1>Nothing</h1>
</body>
</html>
Para adicionar algum estilo e interatividade à extensão, CSS e Javascript podem ser incluídos por meio de links ou elementos de estilo e script.
<html>
<body>
<h1>Nothing</h1>
<button>Something</button>
</body>
<style>
body {
text-align: center;
}
</style>
<script src="nothing.js"></script>
</html>
document.querySelector("button").addEventListener("click", () => {
document.querySelector("h1").style.color = "tomato";
});
O ambiente em que o pop-up opera é isolado do conteúdo da página da web carregado pelo navegador. Neste exemplo, nothing.js
tem como alvo os elementos de tag extremamente genéricos de button
e h1
, mas o código acima afeta apenas os elementos no pop-up e nada na página da web.
Se você é um desenvolvedor front-end como eu, talvez prefira ter algum feedback visual de que seu código fez alguma coisa.
Para carregar a extensão no Firefox, você terá que carregá-la como um complemento temporário. A opção de recarregar a extensão depois de fazer alterações na fonte também está disponível.
Para o Chrome, você terá que habilitar o Modo Desenvolvedor antes de poder carregar a extensão descompactada. A mesma fonte funciona em ambos os navegadores.
Scripts de conteúdo
Quando queremos que a extensão realmente faça algo com o conteúdo de uma página da web, teremos que usar scripts de conteúdo, que rodam no contexto da página da web carregada do navegador. Essa é a única maneira de acessar o conteúdo da página a partir da extensão.
Há 3 maneiras de carregar o script de conteúdo de uma extensão na página da web, ou seja, por meio de declaração estática, declaração dinâmica ou programaticamente. Esses métodos diferentes permitem a mais ampla gama de casos de uso, quer você queira que a extensão modifique a experiência padrão pronta para uso ou com base em gatilhos específicos.
Os scripts de conteúdo são isolados, no sentido de que podem fazer alterações em seu próprio ambiente JavaScript sem entrar em conflito com os scripts de conteúdo da página ou de outras extensões. Embora os scripts de conteúdo possam acessar e alterar o DOM, eles só veem a versão “limpa” do DOM que não foi modificada por nenhum JavaScript.
O Firefox e o Chrome lidam com esse comportamento de isolamento de forma diferente. No Firefox, o conceito é chamado de Xray vision, onde scripts de conteúdo podem encontrar objetos JavaScript de seu próprio escopo global ou versões Xray-wrapped da página da web.
Enquanto para o Chrome, existe o conceito de 3 tipos de mundos, um mundo principal, um mundo isolado e um mundo trabalhador. Cada mundo tem seu próprio contexto, seu próprio escopo de variável global e cadeias de protótipos.
Os scripts de conteúdo têm acesso direto a um conjunto específico e limitado de APIs de extensão, mas para outras APIs, é necessária uma forma de troca de mensagens, que abordaremos brevemente no próximo exemplo.
Esta extensão consiste em um botão que tomará conta de toda a página da web e a cobrirá com alguma pixel art originada da interwebs. Código completo aqui: https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE2
{
"manifest_version": 3,
"name": "AE2",
"version": "1.0",
"description": "Activate pixel art",
"icons": {
"32": "icons/icon32.png",
"48": "icons/icon48.png"
},
"permissions": ["activeTab", "scripting"],
"action": {
"default_popup": "pixel.html"
},
"web_accessible_resources": [
{
"resources": [
"images/pixel-adventure-time.png",
"images/pixel-cat.jpg",
"images/pixel-city.png",
"images/pixel-zen-garden.png"
],
"extension_ids": ["*"],
"matches": ["*://*/*"]
}
]
}
Para que a extensão reconheça e carregue imagens, elas precisam ser declaradas no arquivo manifest.json com a web_accessible_resources
chave. A extensão também precisa da activeTab
permissão, que concede à extensão privilégios extras para a aba ativa somente quando ocorre uma interação do usuário e a permissão de script, que é necessária para usar métodos da API de script chamados no script de conteúdo.
let id;
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
id = tabs[0].id;
browser.scripting.executeScript({
target: { tabId: tabs[0].id },
files: ["content.js"],
});
browser.scripting.insertCSS({
target: { tabId: tabs[0].id },
files: ["content.css"],
});
});
document.getElementById("pixelate").addEventListener("click", () => {
browser.tabs.sendMessage(id, { message: "pixelate" });
});
document.getElementById("reset").addEventListener("click", () => {
browser.tabs.sendMessage(id, { message: "reset" });
});
browser.tabs.query()
é usado para obter informações sobre a aba que queremos atingir, especificamente, o ID da aba, porque o método de script requer que um ID de aba seja passado para ele, o que faz sentido, já que temos que especificar o alvo no qual queremos injetar nosso script, certo?
O mesmo vale para injetar CSS. Era possível estilizar o material que está sendo injetado via JavaScript? Claro que sim. Devemos fazer dessa forma? Depende de você. Eu, pessoalmente, gosto dos meus estilos em arquivos CSS, só isso.
Então, qual é esse sendMessage()
método? Bem, como os scripts de conteúdo são executados no contexto da página da web e não da extensão em si, essa é a maneira do script de conteúdo se comunicar com a extensão. A extensão e os scripts de conteúdo ouvirão as mensagens um do outro e responderão no mesmo canal.
Para acessar imagens fornecidas com a extensão, o runtime.getURL()
método é usado, o que converte o caminho relativo da imagem em uma URL totalmente qualificada que o navegador pode renderizar corretamente. E, finalmente, o runtime.onMessage()
evento é usado para ouvir mensagens.
Scripts de fundo
Outro tipo de script que é visto em extensões da web são os scripts de segundo plano. Eles são feitos para monitorar eventos no navegador e reagir a eles de acordo. Por exemplo, se quiséssemos implementar atalhos de teclado para nossa extensão, poderíamos usar um script de segundo plano para ouvir comandos específicos e disparar alguma ação de acordo.
O próximo exemplo adicionará alguns atalhos de teclado à extensão usando um script de segundo plano. Código completo aqui: https://github.com/huijing/slides/tree/gh-pages/109-ijs-2024/extensions/AE3 .
Há algumas adições que precisam ser feitas no arquivo manifest.json para que as coisas funcionem.
{
"background": {
"service_worker": "background.js", // v3 syntax
"scripts": ["background.js"] // v2 syntax
},
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+Y"
}
},
"pixelate": {
"suggested_key": {
"default": "Alt+A"
},
"description": "Send a 'pixelate' event to the extension"
},
"reset": {
"suggested_key": {
"default": "Ctrl+Shift+E"
},
"description": "Send a 'reset' event to the extension"
}
}
}
A API Commands nos permite definir comandos e vinculá-los a combinações de teclas específicas. Esses comandos devem primeiro ser declarados como propriedades no arquivo manifest.json . Então, escutamos um evento onCommand para ser disparado usando o script de segundo plano e executamos o que quisermos executar quando a combinação de teclas correta for pressionada.
Há 4 atalhos especiais com ações padrão para as quais o onCommand
evento não dispara, e _execute_action
é um deles. Este atalho age como se o usuário tivesse clicado no ícone da extensão na barra de ferramentas.
No arquivo background.js , usamos o onCommand.addListener
para vincular um manipulador a cada um dos comandos listados no manifesto. Como mencionado anteriormente, o _execute_action
comando não dispara um evento, então não precisamos de um manipulador para isso.
Encerrando
Isto abrange todas as informações básicas necessárias para começar com o desenvolvimento de extensões web. A extensão pode ser tão simples quanto mudar a cor do texto na página ou tão complicada quanto um aplicativo completo (sim, você pode usar React para construir sua extensão se assim escolher).
Suponho que se você tem medo de downloads de extensões duvidosas em lojas, você sempre pode criar sua própria extensão para fazer exatamente o que você quer que ela faça, então por que não?