Ocultar formatação de link da extensão do Firefox
Estou construindo uma extensão do Mozilla Firefox para ocultar a formatação do link e mostrá-los como texto normal. Consiste em um script que roda em páginas da web ( content.js) que é ativado clicando na extensão (tratada com background.js). Na ativação, uma regra CSS é injetada para ocultar a formatação do link. Ativá-lo novamente desligará as alterações.
background.js além disso, atualiza o ícone da extensão (com base nos links ocultos ou não) sempre que a guia ativa é alterada.
Sou novo em JavaScript. Eu realmente aprecio seus comentários e observações para melhorá-lo (melhores práticas, estrutura de código, etc).
Script executado em páginas da web (content.js):
'use strict';
// Links formatting state variable
let hideLinks = false;
const hideLinksRule = `
a,
a:hover,
a:focus,
a:active,
a:visited {
text-decoration: none !important;
color: inherit !important;
background-color: inherit !important;
border-bottom: initial !important;
}`;
let styleSheet = (function () {
let style = document.createElement('style');
document.head.appendChild(style);
return style.sheet;
})();
function handleRequest(request) {
// Do not modify if request is only a query
if (request.isQuery) {
return Promise.resolve({
hideLinks
});
}
// Toggle hide/show link formatting
if (hideLinks) {
styleSheet.deleteRule(0);
hideLinks = !hideLinks;
} else {
styleSheet.insertRule(hideLinksRule, 0);
hideLinks = !hideLinks;
}
// Return links state
return Promise.resolve({
hideLinks
});
}
browser.runtime.onMessage.addListener(handleRequest);
Script para lidar com a extensão no navegador (background.js):
// Icons paths
const showLinksIconPath = {
path: 'icons/link-icon.png'
};
const hideLinksIconPath = {
path: 'icons/broken-link-icon.png'
};
function onError(error) {
browser.browserAction.setIcon(showLinksIconPath);
}
function updateIcon(response) {
if (response.hideLinks) {
browser.browserAction.setIcon(hideLinksIconPath);
} else {
browser.browserAction.setIcon(showLinksIconPath);
}
}
function verifyTabStatus(activeInfo) {
browser.tabs.sendMessage(
activeInfo.tabId, {
isQuery: true
}
).then(updateIcon).catch(onError);
}
function activeTabLinkToggle() {
browser.tabs.query({
currentWindow: true,
active: true
}).then(requestLinkFormatToggle).catch(onError);
}
function requestLinkFormatToggle(tabs) {
if (!(tabs === undefined || tabs.length == 0)) {
const activeTab = tabs[0];
browser.tabs.sendMessage(
activeTab.id, {
isQuery: false
}
).then(updateIcon).catch(onError);
}
}
// Update Icon on tab change
browser.tabs.onActivated.addListener(verifyTabStatus);
// Request toggle link formatting on click
browser.browserAction.onClicked.addListener(activeTabLinkToggle);
Arquivo de configuração para colocá-lo junto (manifest.json):
{
"manifest_version": 2,
"name": "No-link",
"version": "1.0",
"description": "Hide link formatting.",
"icons": {
"48": "icons/link-icon.png"
},
"permissions": [
"tabs"
],
"browser_action": {
"default_icon": "icons/link-icon.png",
"default_title": "No-link"
},
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
}
Você também pode obter o código-fonte aqui . Muito obrigado pelo seu tempo.
Respostas
Prefira sempre const ao declarar variáveis - use apenasletquando precisar reatribuir uma variável. constrequer menos sobrecarga cognitiva, quando você sabe à primeira vista que algo nunca será reatribuído.
Alternância de estilo No momento, você está alternando o estilo com styleSheet.insertRule(rule, 0)e styleSheet.deleteRule(0). Acho que seria um pouco melhor adotar uma abordagem diferente.
- Quem sabe o que
insertRule(rule, 0)edeleteRule(0)fazer à primeira vista? Provavelmente não um monte de gente; não é uma API muito comum usada. - E se você precisar expandir a extensão para adicionar regras adicionais ? Então, usar vários indicadores de regra pode se tornar repetitivo.
Uma alternativa seria, alternando, anexar ou remover a styletag do <head>. Outra alternativa seria atribuir ou limpar o seu textContent. Veja abaixo, por exemplo.
hideLinks não é um nome totalmente preciso. À primeira vista, alguém que visse uma variável chamada hideLinksprovavelmente pensaria que parece uma função que, quando chamada, oculta links. Talvez chame de outra coisa, como linksAreHidden. Você também pode fazer sua alternância mais SECA:
if (hideLinks) {
styleSheet.deleteRule(0);
hideLinks = !hideLinks;
} else {
styleSheet.insertRule(hideLinksRule, 0);
hideLinks = !hideLinks;
}
pode ser
if (hideLinks) {
styleSheet.deleteRule(0);
} else {
styleSheet.insertRule(hideLinksRule, 0);
}
hideLinks = !hideLinks;
Ou, melhor ainda, em vez de ter uma variável externa persistente, você pode simplesmente verificar se o elemento de estilo está anexado ou tem conteúdo. Veja abaixo, por exemplo. Isso é o que eu prefiro - deixar o nó de estilo (que deve existir independentemente) seja a fonte da verdade, em vez de introduzir outra variável que precisa ficar em sincronia.
Solicitar objeto que você tem atualmente
if (request.isQuery) {
return Promise.resolve({
hideLinks
});
}
// otherwise, insert or remove style rule
Acho melhor ser mais explícito. E se você adicionar um terceiro comando de solicitação ou um quarto comando de solicitação? Em vez de ter um implícito " Senão, se isso não é uma consulta, faça ISSO ", prefiro sempre enviar uma propriedade indicando qual deve ser o comando.
Talvez assim, usando uma isTogglepropriedade:
const hideLinksStyleText = `
a,
a:hover,
a:focus,
a:active,
a:visited {
text-decoration: none !important;
color: inherit !important;
background-color: inherit !important;
border-bottom: initial !important;
}`;
const styleTag = document.head.appendChild(document.createElement('style'));
function handleRequest(request) {
const linksOriginallyHidden = Boolean(styleTag.textContent);
// Do not modify if request is only a query
if (request.isQuery) {
return Promise.resolve({
linksAreHidden: linksOriginallyHidden
});
}
if (request.isToggle) {
styleTag.textContent = linksOriginallyHidden
? hideLinksStyleText
: '';
return Promise.resolve({
linksAreHidden: !linksOriginallyHidden
});
}
}
O operador condicional pode ser usado para fazer expressões condicionais concisas. Este:
const showLinksIconPath = {
path: 'icons/link-icon.png'
};
const hideLinksIconPath = {
path: 'icons/broken-link-icon.png'
};
function updateIcon(response) {
if (response.hideLinks) {
browser.browserAction.setIcon(hideLinksIconPath);
} else {
browser.browserAction.setIcon(showLinksIconPath);
}
}
pode se transformar em
function updateIcon(response) {
browser.browserAction.setIcon({
path: `icons/${response.hideLinks ? 'broken-' : ''}link-icon.png`
});
}
Ou, se você não gosta da interpolação:
function updateIcon(response) {
browser.browserAction.setIcon({
path: response.hideLinks
? 'icons/broken-link-icon.png'
: 'icons/link-icon.png'
});
}
Verificação de guia Você tem:
if (!(tabs === undefined || tabs.length == 0)) {
const activeTab = tabs[0];
As negações são ligeiramente mais difíceis de analisar logicamente à primeira vista do que podem ser. Considere em vez disso:
if (tabs && tabs.length) {
Em um projeto maior, você pode usar encadeamento opcional e Babel:
if (tabs?.length) {
Mas o encadeamento opcional só é suportado em versões muito novas do FF, e o Babel não vale a pena apenas por isso. (Babel é uma boa ideia se você quiser escrever um script de tamanho razoável e permitir que versões mais antigas do navegador possam entender a sintaxe JS que você usa. Para uma extensão FF, é menos importante, porque quase todos os usuários estarão conectados razoavelmente navegadores atualizados de qualquer maneira - não há necessidade de transpilar para ES5 para aqueles usuários do IE11 miseráveis)