HTML no limpio en la API de Async Clipboard

A partir de Chrome 120, hay una nueva opción de unsanitized disponible en el portapapeles asíncrono en la API de Cloud. Esta opción puede ayudar en situaciones especiales con HTML, en las que necesitas y pegar el contenido del portapapeles idéntico al que tenía cuando se copió. Es decir, sin ningún paso de depuración intermedio que suelen usar los navegadores por buenas razones: postúlate. Aprende a usarlo en esta guía.

Cuando trabajes con API de Async Clipboard, en la mayoría de los casos, los desarrolladores no tienen que preocuparse por la integridad del el contenido en el portapapeles y pueden asumir que lo que escribe en el el portapapeles (copia) es lo mismo que obtendrán cuando lean los datos de al portapapeles (pégalas).

Esto es definitivamente cierto para el texto. Intenta pegar el siguiente código en las Herramientas para desarrolladores Consola y, luego, vuelve a enfocar la página de inmediato. (El setTimeout() es necesario de modo que tengas tiempo suficiente para enfocar la página, lo que es un requisito del modelo API de Portapapeles). Como puedes ver, la entrada es exactamente la misma que la salida.

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);

Con las imágenes, es un poco diferente. Para evitar las llamadas bombas de compresión, navegadores volver a codificar imágenes como PNG, pero las imágenes de entrada y salida son visualmente exactamente igual, píxel por píxel.

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);

Sin embargo, ¿qué sucede con el texto HTML? Como habrás adivinado, con HTML, es diferente. Aquí, el navegador limpia el código HTML para evitar errores que ocurra algo, por ejemplo, si se quitan las etiquetas <script> del código HTML (y otros como <meta>, <head> y <style>) y mediante la intercalación de CSS. Considera el siguiente ejemplo y pruébalo en la consola de Herramientas para desarrolladores. Lo que harás Observa que el resultado difiere bastante de la 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);

En general, la limpieza de HTML es algo bueno. No quieres exponerte relacionados con la seguridad, ya que, en la mayoría de los casos, permite el uso de HTML sin depurar. Hay sin embargo, hay situaciones en las que el desarrollador sabe exactamente lo que está haciendo y en el que la integridad del código HTML de las entradas y salidas es crucial para el correcto el funcionamiento de la app. En estas circunstancias, tienes dos opciones:

  1. Si controlas tanto el extremo de copiado como el final, por ejemplo, si copias desde tu app para pegar el contenido dentro de ella, debes usar Formatos web personalizados para la API de Async Clipboard. Deja de leer aquí y revisa el artículo vinculado.
  2. Si solo controlas el extremo de pegado en tu app, pero no el extremo de copia, tal vez porque la operación de copia ocurre en una app nativa que no admite formatos personalizados de la Web, debes usar la opción unsanitized, que es se explica en el resto de este artículo.

La limpieza incluye la eliminación de etiquetas script, los estilos de incorporación y asegurándose de que el HTML tenga el formato correcto. Esta lista no es exhaustiva, es posible que se agreguen pasos en el futuro.

Copia y pega el código HTML sin depurar

Cuando write() (copias) HTML al portapapeles con la API de Async Clipboard, el navegador se asegura de que tenga el formato correcto ejecutándolo a través de un analizador del DOM y serializar la cadena HTML resultante, pero no se está realizando ninguna limpieza en este paso. No es necesario que realices ninguna acción. Si read() colocas el código HTML en el portapapeles de otra aplicación y tu aplicación web acepta obtener la contenido con fidelidad total y necesidad de hacer cualquier limpieza en tu propio código, Puedes pasar un objeto de opciones al método read() con una propiedad. unsanitized y un valor de ['text/html']. De forma aislada, se verá de la siguiente manera: navigator.clipboard.read({ unsanitized: ['text/html'] }) La siguiente muestra de código que se muestra a continuación es casi el mismo que se mostró antes, pero esta vez con el valor unsanitized de 12 a 1 con la nueva opción de compresión. Cuando lo pruebes en la consola de Herramientas para desarrolladores, verás que la entrada y los resultados sean los mismos.

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);

Compatibilidad con navegadores y detección de funciones

No hay una forma directa de comprobar si se admite la función, así que la detección se basa en observar el comportamiento. Por lo tanto, el siguiente ejemplo se basa en la detección del hecho de si una etiqueta <style> sobrevive, lo que indica compatibilidad o está intercalada, lo que indica no compatibilidad. Ten en cuenta que Para que esto funcione, la página ya debe tener el portapapeles permiso.

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);
};

Demostración

Para ver la opción unsanitized en acción, consulta la demostración de Glitch y revisa su código fuente.

Conclusiones

Como se indicó en la introducción, la mayoría de los desarrolladores nunca tendrán que preocuparse la limpieza del portapapeles y puede funcionar con las opciones de limpieza predeterminadas que creó el navegador. En los casos poco comunes en los que deban preocuparse a los desarrolladores, Existe la opción unsanitized.

Agradecimientos

Anupam Snigdha revisó este artículo, y Rachel Andrew. Se especificó la API y que implementa el equipo de Microsoft Edge.