HTML não corrigido na API Async Clipboard

No Chrome 120, uma nova opção unsanitized está disponível na área de transferência assíncrona API. Essa opção pode ajudar em situações especiais com HTML, em que você precisa cole o conteúdo da área de transferência idêntico ao que estava quando ela foi copiada. Ou seja, sem nenhuma etapa de sanitização intermediária que os navegadores comumente - e por um bom motivo, faça sua inscrição. Aprenda a usá-la neste guia.

Ao trabalhar com o API Async Clipboard, na maioria dos casos, os desenvolvedores não precisam se preocupar com a integridade o conteúdo na área de transferência e podem presumir que o que foi gravado na área de transferência (cópia) é o mesmo que eles vão receber quando ler os dados da área de transferência (colar).

Isso é verdade para texto. Tente colar o seguinte código no DevTools Console e, em seguida, mude o foco da página imediatamente. O setTimeout() é necessário para que você tenha tempo suficiente para focar na página, o que é um requisito do API Clipboard. 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 compressão, navegadores recodificar imagens como PNGs, mas as imagens de entrada e saída são visualmente exatamente o mesmo, 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ê deve ter adivinhado, com o HTML, é diferente. Aqui, o navegador limpa o código HTML para evitar que aconteçam coisas como, por exemplo, removendo tags <script> do HTML. (e outros, como <meta>, <head> e <style>) e inline do CSS. Considere o exemplo a seguir e teste no Console do DevTools. Você vai observe que a saída é muito 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 sanitização de HTML geralmente é positiva. Você não quer se expor a problemas de segurança, permitindo HTML sem correção na maioria dos casos. são cenários, porém, em que o desenvolvedor sabe exatamente o que está fazendo e em que a integridade do HTML de entrada e de saída é crucial para o correto e funcionamento do app. Nessas circunstâncias, você tem duas opções:

  1. Se você controlar a ação de copiar e colar, por exemplo, de dentro do aplicativo para colar dentro do aplicativo, use Formatos personalizados da Web para a API Async Clipboard. Pare de ler aqui e confira o artigo vinculado.
  2. Se você controlar apenas a extremidade de colar no app, mas não a de cópia, talvez porque a operação de cópia aconteça em um app nativo que não suporte formatos personalizados da Web, use a opção unsanitized, que é explicado no restante deste artigo.

A limpeza inclui ações como remover tags script, estilos in-line e garantindo que o HTML esteja bem formado. Essa lista não está completa e muito mais etapas podem ser adicionadas no futuro.

Copiar e colar HTML não limpo

Quando você write() (copia) HTML para a área de transferência com a API Async Clipboard, o navegador verifica se a formatação está correta, executando-o por meio de um analisador DOM e serializando a string HTML resultante, mas nenhuma limpeza está acontecendo na nesta etapa. Você não precisa fazer nada. Quando você read() o HTML colocado no área de transferência por outro aplicativo e seu aplicativo da web estiver optando por obter a conteúdo de fidelidade total e precisar realizar qualquer sanitização em 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, o resultado será semelhante a este: navigator.clipboard.read({ unsanitized: ['text/html'] }): O exemplo de código a seguir abaixo é quase igual à mostrada anteriormente, mas com o unsanitized é a melhor opção. Ao testá-lo no Console do DevTools, você verá que a entrada e e a saída é a mesma.

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 forma direta de verificar se o recurso é compatível. Portanto, baseada na observação do comportamento. Portanto, o exemplo a seguir depende da detecção da sobrevivência de uma tag <style>, o que indica compatibilidade, ou está sendo em linha, o que indica não compatível. Observe que Para que isso funcione, a página já precisa ter a área de transferência permissão.

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 ver a opção unsanitized em ação, consulte o demonstração do Glitch e confira código-fonte.

Conclusões

Conforme descrito na introdução, a maioria dos desenvolvedores nunca vai precisar se preocupar com limpeza da área de transferência e podem trabalhar com as opções de limpeza padrão feita pelo navegador. Nos raros casos em que os desenvolvedores precisam se importar, os A opção unsanitized já existe.

Agradecimentos

Este artigo foi revisado por Anupam Snigdha e Rachel Andrew. A API foi especificada e implementado pela equipe do Microsoft Edge.