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 estava quando foi copiado.
Isso significa que não há nenhuma etapa de limpeza intermediária que os navegadores geralmente aplicam, e
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) são o mesmo que vão receber quando ler os dados da área de transferência (colar).
Isso é definitivamente verdade para textos. Cole o código abaixo no console do DevTools e redefina a página imediatamente. 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.
Considere 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 etapa de copiar e colar, por exemplo, se copiar de dentro do app para depois colar no 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 higienização 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']
. Isolado, ele tem esta aparência:
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 está sendo inline, o que indica a 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 importar, a
opção unsanitized
existe.
Links úteis
Agradecimentos
Este artigo foi revisado por Anupam Snigdha e Rachel Andrew. Ela foi especificada e implementada pela equipe do Microsoft Edge.