cpfgen/extension/insert.js
Lucas F. 7632cfde2c feat: extensão de navegador para gerar CPF/CNPJ no menu de contexto
Adiciona uma extensão MV3 (Firefox e Chromium/Brave) que insere CPF ou CNPJ
válido direto no campo focado, pelo menu de contexto de qualquer input,
textarea ou contenteditable.

Extensões rodam em sandbox sem acesso a processos, então não há como invocar
os binários cpfgen/cnpjgen; fazer isso exigiria um host de native messaging
registrado no SO. Como os algoritmos são curtos e puros, foram portados para
JS em extension/generator.js, a partir de main.go (CPF) e de
cnpj_generator/cnpj/generate.go (CNPJ), trocando math/rand por
crypto.getRandomValues. test_generator.mjs compara as duas implementações
para que não divirjam.

A inserção usa document.execCommand("insertText"), que emite os eventos
nativos e portanto funciona em campos controlados por React/Vue — atribuir
.value diretamente seria descartado por esses frameworks.

Somente desktop: o Firefox para Android não implementa a API menus.

Testes carregam a extensão de verdade nos dois navegadores e verificam que os
8 itens de menu são criados e que a inserção funciona nos três tipos de campo.
2026-09-03 17:57:50 -03:00

50 lines
1.8 KiB
JavaScript

// Injetado na aba via chrome.scripting.executeScript. Roda isolado, sem imports:
// recebe o texto já gerado e o escreve no campo que abriu o menu de contexto.
function insertIntoFocused(text) {
const el = document.activeElement;
if (!el) return false;
const tag = el.tagName;
const isInput = tag === "INPUT" || tag === "TEXTAREA";
if (!isInput && !el.isContentEditable) return false;
el.focus();
// Caminho preferido: execCommand emite os eventos de input nativos, então
// campos controlados por React/Vue/Angular enxergam a mudança. Uma atribuição
// direta em .value seria silenciosamente descartada por esses frameworks.
if (document.execCommand("insertText", false, text)) return true;
// Fallback para quando execCommand é bloqueado ou não suportado.
if (isInput) {
const start = el.selectionStart ?? el.value.length;
const end = el.selectionEnd ?? el.value.length;
// Usa o setter nativo do prototype: React sobrescreve o setter da instância
// para rastrear o estado, e escrever por ele não notificaria o framework.
const proto =
tag === "TEXTAREA"
? HTMLTextAreaElement.prototype
: HTMLInputElement.prototype;
const setter = Object.getOwnPropertyDescriptor(proto, "value").set;
setter.call(el, el.value.slice(0, start) + text + el.value.slice(end));
const pos = start + text.length;
el.setSelectionRange(pos, pos);
} else {
const sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
const range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(text));
sel.collapseToEnd();
} else {
el.textContent += text;
}
}
el.dispatchEvent(new Event("input", { bubbles: true }));
el.dispatchEvent(new Event("change", { bubbles: true }));
return true;
}