No Chrome 120, uma nova opção unsanitized
está disponível na API Async Clipboard. Essa opção pode ajudar em situações especiais com HTML, em que você precisa
colar o conteúdo da área de transferência de forma idêntica ao que foi copiado.
Ou seja, sem nenhuma etapa de limpeza intermediária que os navegadores geralmente
aplicam por bons motivos. Saiba como usá-la neste guia.
Ao trabalhar com a API Async Clipboard, na maioria dos casos, os desenvolvedores não precisam se preocupar com a integridade do conteúdo na área de transferência e podem presumir que o que eles gravam na área de transferência (cópia) é o mesmo que vão receber quando lerem os dados da área de transferência (colar).
Isso é definitivamente verdade para textos. Tente colar o código abaixo no console do DevTools e, em seguida, redirecione a página. O setTimeout()
é necessário
para que você tenha tempo suficiente para focar a página, o que é um requisito da API Async
Clipboard. Como você pode ver, a entrada é exatamente igual à saída.
setTimeout(async () => {
const input = 'Hello';
await navigator.clipboard.writeText(input);
const output = await navigator.clipboard.readText();
console.log(input, output, input === output);
// Logs "Hello Hello true".
}, 3000);
Com imagens, é um pouco diferente. Para evitar os chamados ataques de bomba de compactação, os navegadores recodificam imagens como PNGs, mas as imagens de entrada e de saída são visualmente iguais, pixel por pixel.
setTimeout(async () => {
const dataURL =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=';
const input = await fetch(dataURL).then((response) => response.blob());
await navigator.clipboard.write([
new ClipboardItem({
[input.type]: input,
}),
]);
const [clipboardItem] = await navigator.clipboard.read();
const output = await clipboardItem.getType(input.type);
console.log(input.size, output.size, input.type === output.type);
// Logs "68 161 true".
}, 3000);
Mas o que acontece com o texto HTML? Como você pode imaginar, com HTML, a
situação é diferente. Aqui, o navegador higieniza o código HTML para evitar
problemas, por exemplo, removendo tags <script>
do código
HTML (e outras como <meta>
, <head>
e <style>
) e inserindo o CSS.
Confira o exemplo a seguir e teste no console do DevTools. Você vai
notar que a saída é bastante diferente da entrada.
setTimeout(async () => {
const input = `<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="ProgId" content="Excel.Sheet" />
<meta name="Generator" content="Microsoft Excel 15" />
<style>
body {
font-family: HK Grotesk;
background-color: var(--color-bg);
}
</style>
</head>
<body>
<div>hello</div>
</body>
</html>`;
const inputBlob = new Blob([input], { type: 'text/html' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': inputBlob,
}),
]);
const [clipboardItem] = await navigator.clipboard.read();
const outputBlob = await clipboardItem.getType('text/html');
const output = await outputBlob.text();
console.log(input, output);
}, 3000);
A limpeza de HTML geralmente é uma coisa boa. Não se exponha a problemas de segurança ao permitir HTML não higienizado na maioria dos casos. No entanto, há cenários em que o desenvolvedor sabe exatamente o que está fazendo e em que a integridade do HTML de entrada e saída é crucial para o funcionamento correto do app. Nessas circunstâncias, você tem duas opções:
- Se você controlar a parte de cópia e colagem, por exemplo, se você copiar no app para colar no mesmo app, use Formatos personalizados da Web para a API Async Clipboard. Pare de ler aqui e confira o artigo vinculado.
- Se você controlar apenas a parte de colagem no app, mas não a de cópia,
talvez porque a operação de cópia ocorra em um app nativo que não oferece suporte a
formatos personalizados da Web, use a opção
unsanitized
, que é explicada no restante deste artigo.
A limpeza inclui coisas como remover tags script
, alinhar estilos e
garantir que o HTML esteja bem formado. Esta lista não é abrangente, e mais
etapas podem ser adicionadas no futuro.
Cópia e colagem de HTML não higienizado
Quando você write()
(copia) HTML para a área de transferência com a API Async Clipboard,
o navegador garante que ele esteja bem formado executando-o em um analisador DOM
e serializando a string HTML resultante, mas nenhuma limpeza está acontecendo
nesta etapa. Não é necessário fazer nada. Quando você read()
HTML colocado na
área de transferência por outro aplicativo e seu app da Web está ativando a opção de receber o
conteúdo de fidelidade total e precisa realizar a limpeza no seu próprio código,
é possível transmitir um objeto de opções para o método read()
com uma propriedade
unsanitized
e um valor de ['text/html']
. Isoladamente, ele fica assim:
navigator.clipboard.read({ unsanitized: ['text/html'] })
. O exemplo de código abaixo
é quase o mesmo que o mostrado anteriormente, mas desta vez com a opção
unsanitized
. Ao tentar no console do DevTools, você vai notar que a entrada e
a saída são as mesmas.
setTimeout(async () => {
const input = `<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="ProgId" content="Excel.Sheet" />
<meta name="Generator" content="Microsoft Excel 15" />
<style>
body {
font-family: HK Grotesk;
background-color: var(--color-bg);
}
</style>
</head>
<body>
<div>hello</div>
</body>
</html>`;
const inputBlob = new Blob([input], { type: 'text/html' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': inputBlob,
}),
]);
const [clipboardItem] = await navigator.clipboard.read({
unsanitized: ['text/html'],
});
const outputBlob = await clipboardItem.getType('text/html');
const output = await outputBlob.text();
console.log(input, output);
}, 3000);
Suporte a navegadores e detecção de recursos
Não há uma maneira direta de verificar se o recurso tem suporte. Portanto, a detecção
de recursos é baseada na observação do comportamento. Portanto, o exemplo a seguir
depende da detecção de se uma tag <style>
sobrevive, o que
indica suporte, ou se ela está sendo inline, o que indica falta de suporte. Para que isso funcione, a página precisa ter recebido a permissão de
área de transferência.
const supportsUnsanitized = async () => {
const input = `<style>p{color:red}</style><p>a`;
const inputBlob = new Blob([input], { type: 'text/html' });
await navigator.clipboard.write([
new ClipboardItem({
'text/html': inputBlob,
}),
]);
const [clipboardItem] = await navigator.clipboard.read({
unsanitized: ['text/html],
});
const outputBlob = await clipboardItem.getType('text/html');
const output = await outputBlob.text();
return /<style>/.test(output);
};
Demonstração
Para conferir a opção unsanitized
em ação, consulte a
demonstração no Glitch e confira o
código-fonte.
Conclusões
Conforme descrito na introdução, a maioria dos desenvolvedores nunca precisa se preocupar com
a higienização da área de transferência e pode trabalhar apenas com as opções de higienização padrão
feitas pelo navegador. Para os raros casos em que os desenvolvedores precisam se preocupar, a
opção unsanitized
existe.
Links úteis
Agradecimentos
Este artigo foi revisado por Anupam Snigdha e Rachel Andrew. A API foi especificada e implementada pela equipe do Microsoft Edge.