Executando verificação de segurança...
11

Criação de plugin para Google Chrome - Uma introdução

Originalmente publicado no meu blog em 2022-04-04

Introdução

Ao criar um plugin (extensão) para navegadores baseados em Chromium, é necessário entender que são como páginas web rodando localmente no navegador em que está instalado. Rodam em um ambiente separado, mas com capacidade de interagirem com o navegador, com abas (sites) abertos e até mesmo com outros plugins instalados.

As mesmas tecnologias usadas para criação de um site são empregadas ao criar um plugin, sendo o mínimo necessário apenas Javascript. Para uma melhor apresentação e maior interação, HTML e CSS são utilizados.

É possível realizar leitura do conteúdo de uma determinada página, salvar dados locais e até mesmo compartilhados entre outros navegadores em que você está logado.

No nosso exemplo vamos criar um plugin para inverter as cores de uma página qualquer.

O que é necessário?

O arquivo mais importante para começar seu plugin é o arquivo manifest.json, o qual guarda informações importantes sobre o projeto, como nome, versão, arquivo de execução principal, permissões etc.

Arquivo manifesto

Como dito antes, é o arquivo mais importante. Nele você vai especificar alguns dados relacionados ao seu plugin.
Os 3 dados necessários são: name, manifest_version e version.

{
    "name": "Nome da extensão",
    "manifest_version": 3,
    "version": "0.0.1"
}

O detalhe importante aqui é a versão do manifesto. Existe a versão 2 e versão 3. Estaremos utilizando a versão 3. Futuramente pretendo explicar um pouco a diferença entre as versões.

Um arquivo json com esses dados já são um plugin válido que já pode ser colocado no Chrome. Para testar, entre em chrome://extensions, ative o modo de desenvolvedor e clique no botão Carregar sem compactação. Após isso, deve existir um plugin na página com o nome que colocou e a versão.
Obviamente esse plugin não faz nada, mas logo veremos como dar vida a isso.

Background

A próxima chave que vamos conhecer é a background que abriga o conjunto chave/valor a seguir:

{
    "name": "Nome da extensão",
    "manifest_version": 3,
    "version": "0.0.1",
    "background": {
        "service_worker": "background.js"
    }
}

Definimos então nosso primeiro arquivo javascript que é a base para o plugin. O nome do arquivo do nosso service worker é colocado aqui. Com isso podemos criar um arquivo com o mesmo nome no diretório atual.

Para ver a execução, coloce um

console.log('Hello Plugin')

no arquivo do service worker. Para visualizar o console, volte à página de extensões e clique no ícone de atualizar para o plugin carregar as novas informações. Após isso, deve aparecer um link escrito "service worker". Clique nesse link e será aberto o Chrome Dev Tools do plugin.
No Chrome Dev Tools, clique na aba do Console e lá deve encontrar a mensagem que deixou no arquivo do service worker.

Permissões

As permissões dão a possibilidade de adicionarmos funcionalidades que não seriam possíveis sem elas, como injeção de scripts, armazenar informações etc.

Por exemplo, para que seja possível usarmos as funções de guardar dados, é necessário adicionar a permissão storage.
Para isso, incluímos uma chave permissions em nosso manifesto que recebe uma lista de strings.

{
    "name": "Nome da extensão",
    "manifest_version": 3,
    "version": "0.0.1",
    "background": {
        "service_worker": "background.js"
    },
    "permissions": ["storage"]
}

Com a permissão de storage, podemos utilizar duas funções dentro do arquivo background.js: set e get.
Eles funcionam da seguinte forma:

// Assim salvamos uma chave key com um valor qualquer
// O callback é chamado após o armazenamento, sendo então dispensável.
// a propriedade storage só estará disponível se a permissão falada anteriormente tiver sido colocada.
chrome.storage.local.set({key: value}, callback) // pode ser local ou sync

// A forma de recuperar a chave é parecida, utilizamos a função get(),
// onde o primeiro parâmetro é uma lista de strings com as chaves que deseja pegar.
// o segundo parâmetro (opcional) é uma função que tem um objeto com o resultado.
chrome.storage.local.get(['key'], ({key}) => {}) // também pode ser local ou sync.

Existe uma versão com retorno Promise tanto para set() como para get(), basta remover o callback.

Action

A action permite realizar configurações e operações relacionadas ao plugin na barra de ferramentas (barra onde ficam os plugins, ao lado da barra de endereço). É possível adicionar título que aparece ao passar o mouse por cima do plugin, adicionar ação de popup (criado com tecnologias web), definir o ícone, colocar texto por cima do ícone (badge) etc.

Para começar, adicionamos uma nova chave no arquivo manifesto, a chave action. Essa chave é de um objeto que aceita diversas chaves, como default_icon, default_title, default_popup etc.

{
    "name": "Nome da extensão",
    "manifest_version": 3,
    "version": "0.0.1",
    "background": {
        "service_worker": "background.js"
    },
    "permissions": ["storage"],
    "action": {
        "default_title": "Meu título que aparece no hover",
        "default_popup": "popup.html"
    }
}

Em default_popup deve ser passado o endereço e nome do arquivo HTML que será aberto ao clicarmos na extensão.

Caso instale o plugin dessa forma, sem criar o popup.html, e clicar no plugin, verá uma página de erro informando que não conseguiu encontrar o arquivo. Para resolver isso, basta criar o arquivo no endereço indicado.

Exemplo

Nossa extensão servirá para inverter as cores de uma página.

Não usaremos o arquivo de background.js nesse projeto.

Teremos um plugin que abre um popup contendo um check button para ativar/desativar a inversão de cores.
Assim usaremos de tudo que vimos até agora.

Manifesto

Nosso manifesto será:

manifest.json

{
    "name": "Inverte cores extension",
    "manifest_version": 3,
    "version": "0.0.1",
    "permissions": ["storage", "scripting"],
    "action": {
        "default_title": "Clique aqui para abrir",
        "default_popup": "popup.html"
    },
    "host_permissions": ["https://*/*"]
}

Aqui temos algumas mudanças do que vimos antes, trocamos o texto de name e default_title, adicionamos uma nova permissão (scripting), além de incluírmos o host_permissions com um valor que permite o plugin ser executado em qualquer site. Removemos o background.

Lembre-se de criar os arquivos do service worker e do popup.

Arquivo do popup

Adicionamos um checkbox para saber se o efeito deve estar ativado ou não na página.

popup.html

<!DOCTYPE html>
<html>
<body>
    <div style="width: 100px;">
        <input type="checkbox" id="activate" /> Inverter cor
    </div>
    <script src="popup.js"></script>
</body>
</html>

Além do checkbox, temos também o popup.js que será utilizado para informar quando o status da checkbox mudar.

Toda vez que o popup é aberto, esse arquivo será executado novamente.

popup.js

// Encontra o checkbox que está no HTML
const checkbox = document.getElementById('activate')

// Função usada dentro do listener que veremos a seguir.
async function listener() {
    const currentTab = await getCurrentTab()

    // Salva o estado atual do checkbox com base na aba,
    // possibilitando sabermos o estado para cada aba.
    // Além disso, armazenamos em invert o estado atual,
    // facilitando a alteração do estado local futuramente.
    await chrome.storage.local.set({
        [currentTab.id]: checkbox.checked,
        invert: checkbox.checked
    })
    
    // Executa o script que aplica o filtro ou não depenpendo do estado.
    chrome.scripting.executeScript({
        target: {tabId: currentTab.id },
        files: ['activate.js']
    })
}

// Ao passarmos null como argumento, informamos que queremos tudo que está armazenado.
chrome.storage.local.get(null, async (data) => {
    // adicionamos uma função de listener quando ocorrer o evento change.
    checkbox.addEventListener('change', listener, false)

    const currentTab = await getCurrentTab()

    // altera o valor de checked caso o estado já tenha sido salvo antes para essa aba.
    if (data[currentTab.id]) {
        checkbox.checked = data[currentTab.id]
    }
})

// Removemos o evento quando o popup é fechado.
window.onclose = () => {
    checkbox.removeEventListener('change', listener, false)
}

// É feito uma busca nas queries com base nas opções active e currentWindow,
// assim pegamos a aba que estamos executando o popup.
async function getCurrentTab() {
    let queryOptions = { active: true, currentWindow: true };
    let [tab] = await chrome.tabs.query(queryOptions);
    return tab;
}

Arquivo de scripting

Este arquivo é executado após a chamada de scripting que ocorre no arquivo de popup.js, dentro do listener.

Quando o arquivo é executado, já existe alguns valores salvos, e queremos verificar o que está em invert, caso invert seja verdadeiro, aplicamos o filtro, caso não seja verdadeiro, removemos o filtro.

chrome.storage.local.get(['invert'], async ({invert}) => {
    const html = document.getElementsByTagName('html')[0]
    if (invert) {
        html.style.filter = 'invert(100%)'
    } else {
        html.style.filter = ''
    }
})

Conclusão

Criamos um plugin bem simples, mas completo em questões de usabilidade. Possui ações para o plugin, utiliza armazenamento local, além de utilização de execução de scripts.

Carregando publicação patrocinada...
2

Sempre quis saber como criar um extensão do chrome e teu torial foi bem certeiro mesmo. Se possível ,coloque outros com mais tarefas e subindo um pouco nível de dificuldade. Parabéns.

1

Obrigado pelo comentário!

Este foi apenas um exemplo que criei para depois criar um outro plugin. No caso, criei um plugin para pegar os dados de música da página específica que o usuário ativar, e mandando para o bot pra twitch que criei. Dessa forma poderia pegar dados de música em tempo real em plataformas como Youtube Music, onde a API não é boa igual do Spotify.

Pretendo criar novos tutoriais, só preciso de tempo hahah

1

Parabéns pela iniciativa, sempre tive a curiosidade de saber como funciona, pode compartilhar esse que desenvolveu, sabe como criar os plugins da Twitch? Sucesso em sua jornada!