Nicht bereinigter HTML-Code in der Async Clipboard API

Ab Chrome 120 ist in der Async Clipboard API die neue Option unsanitized verfügbar. Diese Option kann in bestimmten Situationen mit HTML hilfreich sein, in denen Sie den Inhalt der Zwischenablage genauso wie beim Kopieren einfügen müssen. Das heißt, ohne einen Zwischenschritt der Bereinigung, den Browser häufig – und aus guten Gründen – anwenden. In diesem Leitfaden erfahren Sie, wie Sie diese Funktion verwenden.

Bei der Arbeit mit der Async Clipboard API müssen sich Entwickler in den meisten Fällen keine Gedanken um die Integrität des Inhalts in der Zwischenablage machen. Sie können davon ausgehen, dass das, was sie in die Zwischenablage (Kopie) schreibt, dasselbe ist, was sie erhalten, wenn sie die Daten aus der Zwischenablage lesen (einfügen).

Das gilt definitiv für Text. Versuche, den folgenden Code in die Entwicklertools-Konsole einzufügen, und fokussiere dann sofort die Seite neu. Die setTimeout() ist erforderlich, damit Sie genug Zeit haben, sich auf die Seite zu konzentrieren – eine Voraussetzung für die Async Clipboard API. Wie Sie sehen, ist die Eingabe exakt mit der Ausgabe identisch.

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

Bei Bildern ist das ein wenig anders. Um sogenannte Kompressionsbomben zu verhindern, codieren Browser Bilder wie PNGs neu. Die Eingabe- und Ausgabebilder sind jedoch visuell genau gleich (Pixel pro 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);

Was passiert jedoch mit HTML-Text? Wie Sie sich vielleicht schon vermutet haben, ist die Situation bei HTML anders. Hier bereinigt der Browser den HTML-Code, um zu verhindern, dass unerwünschte Aktionen auftreten. Dazu werden beispielsweise <script>-Tags aus dem HTML-Code und andere Tags wie <meta>, <head> und <style> entfernt und CSS eingefügt. Sieh dir das folgende Beispiel an und probiere es in der Entwicklertools-Konsole aus. Sie werden feststellen, dass die Ausgabe erheblich von der Eingabe abweicht.

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

HTML-Bereinigung ist im Allgemeinen eine gute Sache. Sie sollten in den meisten Fällen nicht bereinigten HTML-Code zulassen, um Sicherheitsproblemen vorzubeugen. Es gibt jedoch Szenarien, in denen der Entwickler genau weiß, was er tut und in welchen Fällen die Integrität des Ein- und Ausgabe-HTMLs für das einwandfreie Funktionieren der App entscheidend ist. Unter diesen Umständen haben Sie zwei Möglichkeiten:

  1. Wenn Sie sowohl das Ende des Kopierens als auch das Ende steuern, z. B. wenn Sie Inhalte aus der App kopieren und dann in der App einfügen, sollten Sie benutzerdefinierte Webformate für die Async Clipboard API verwenden. Hören Sie hier auf und sehen Sie sich den verlinkten Artikel an.
  2. Wenn Sie in der Anwendung nur das Einfügen-Ende, aber nicht das Kopierende steuern, liegt das möglicherweise daran, dass der Kopiervorgang in einer nativen Anwendung erfolgt, die keine benutzerdefinierten Web-Formate unterstützt. In diesem Fall sollten Sie die Option unsanitized verwenden, die im Rest dieses Artikels erläutert wird.

Die Bereinigung umfasst unter anderem das Entfernen von script-Tags, Inline-Styles und das Sicherstellen, dass der HTML-Code korrekt formatiert ist. Diese Liste ist nicht umfassend. In Zukunft werden möglicherweise weitere Schritte hinzugefügt.

Unbereinigten HTML-Code kopieren und einfügen

Wenn Sie mit der Async Clipboard API HTML (write()) in die Zwischenablage kopieren (kopieren), stellt der Browser sicher, dass er gut geformt ist. Dazu wird er über einen DOM-Parser ausgeführt und der resultierende HTML-String serialisiert. In diesem Schritt findet jedoch keine Bereinigung statt. Sie müssen nichts tun. Wenn Sie HTML-Code, der von einer anderen Anwendung in der Zwischenablage platziert wurde, mit read() versehen, und Ihre Webanwendung den Abruf der vollständigen Fidelity-Inhalte aktiviert und eine Bereinigung in Ihrem eigenen Code durchführen muss, können Sie ein Optionsobjekt mit der Eigenschaft unsanitized und dem Wert ['text/html'] an die Methode read() übergeben. Isoliert sieht das so aus: navigator.clipboard.read({ unsanitized: ['text/html'] }). Das folgende Codebeispiel ist fast das gleiche wie das zuvor gezeigte, aber dieses Mal mit der Option unsanitized. Wenn Sie es in der Entwicklertools-Konsole ausprobieren, werden Sie feststellen, dass die Eingabe und die Ausgabe identisch sind.

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

Browserunterstützung und Funktionserkennung

Es gibt keine direkte Möglichkeit zu prüfen, ob das Feature unterstützt wird. Daher basiert die Featureerkennung auf der Beobachtung des Verhaltens. Im folgenden Beispiel wird daher erkannt, ob ein <style>-Tag bestehen bleibt, was Unterstützung angibt, oder Inline-Tag erhalten, was auf eine fehlende Unterstützung hinweist. Damit dies funktioniert, muss die Seite bereits die Berechtigung für die Zwischenablage haben.

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

Demo

Wenn Sie die Option unsanitized in Aktion sehen möchten, sehen Sie sich die Demo zu Glitch an und sehen Sie sich den Quellcode an.

Ergebnisse

Wie in der Einführung beschrieben, müssen sich die meisten Entwickler keine Gedanken über die Bereinigung der Zwischenablage machen und können einfach mit den vom Browser vorgenommenen Standardentscheidungen zur Bereinigung arbeiten. Für die seltenen Fälle, in denen Entwickler sich darum kümmern müssen, steht die Option unsanitized zur Verfügung.

Danksagungen

Dieser Artikel wurde von Anupam Snigdha und Rachel Andrew geprüft. Die API wurde vom Microsoft Edge-Team angegeben und implementiert.