Blockierung des Zugriffs auf die Zwischenablage aufheben

Sichererer, uneingeschränkter Zugriff auf die Zwischenablage für Text und Bilder

Bisher wurde der Zugriff auf die Systemzwischenablage über die Tastenkombination document.execCommand() ausgeführt. Diese Methode zum Ausschneiden und Einfügen war zwar weithin unterstützt, hatte aber einen Nachteil: Der Zugriff auf die Zwischenablage war synchron und konnte nur lesen und schreiben im DOM.

Das ist für kleine Textmengen in Ordnung, aber es gibt viele Fälle, in denen das Blockieren der Seite für die Zwischenablageübertragung nicht optimal ist. Es kann eine zeitaufwendige Bereinigung oder Bilddekodierung erforderlich sein, bevor Inhalte sicher eingefügt werden können. Der Browser muss möglicherweise verknüpfte Ressourcen aus einem eingefügten Dokument laden oder inline einfügen. Dadurch wird die Seite blockiert, während auf das Laufwerk oder Netzwerk gewartet wird. Stellen Sie sich vor, Sie fügen Berechtigungen hinzu, die erfordern, dass der Browser die Seite blockiert, während er den Zugriff auf die Zwischenablage anfordert. Gleichzeitig sind die Berechtigungen für die Interaktion mit der Zwischenablage über document.execCommand() nur vage definiert und variieren je nach Browser.

Die Async Clipboard API behebt diese Probleme und bietet ein klar definiertes Berechtigungsmodell, das die Seite nicht blockiert. Die Async Clipboard API ist in den meisten Browsern auf die Verarbeitung von Text und Bildern beschränkt. Die Unterstützung variiert jedoch. Lesen Sie sich die Übersicht zur Browserkompatibilität für jeden der folgenden Abschnitte sorgfältig durch.

Kopieren: Daten in die Zwischenablage schreiben

writeText()

Rufe writeText() auf, um Text in die Zwischenablage zu kopieren. Da diese API asynchron ist, gibt die writeText()-Funktion ein Versprechen zurück, das je nachdem, ob der übergebene Text erfolgreich kopiert wurde, erfüllt oder abgelehnt wird:

async function copyPageUrl() {
  try {
    await navigator.clipboard.writeText(location.href);
    console.log('Page URL copied to clipboard');
  } catch (err) {
    console.error('Failed to copy: ', err);
  }
}

Unterstützte Browser

  • Chrome: 66.
  • Edge: 79.
  • Firefox: 63
  • Safari: 13.1.

Quelle

write()

Tatsächlich ist writeText() nur eine bequeme Methode für die generische write()-Methode, mit der Sie auch Bilder in die Zwischenablage kopieren können. Wie writeText() ist sie asynchron und gibt ein Versprechen zurück.

Wenn Sie ein Bild in die Zwischenablage kopieren möchten, benötigen Sie das Bild als blob. Eine Möglichkeit besteht darin, das Bild mit fetch() von einem Server anzufordern und dann blob() auf die Antwort anzuwenden.

Das Anfordern eines Images vom Server kann aus verschiedenen Gründen unerwünscht oder möglich sein. Glücklicherweise können Sie das Bild auch auf einer Canvas-Leinwand zeichnen und die Methode toBlob() der Canvas-Leinwand aufrufen.

Als Nächstes übergeben Sie ein Array von ClipboardItem-Objekten als Parameter an die Methode write(). Derzeit können Sie nur ein Bild gleichzeitig übergeben. Wir hoffen aber, in Zukunft auch mehrere Bilder unterstützen zu können. ClipboardItem nimmt ein Objekt mit dem MIME-Typ des Bilds als Schlüssel und dem Blob als Wert an. Bei Blob-Objekten, die über fetch() oder canvas.toBlob() abgerufen wurden, enthält das Attribut blob.type automatisch den richtigen MIME-Typ für ein Bild.

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      // The key is determined dynamically based on the blob's type.
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

Alternativ können Sie ein Versprechen an das ClipboardItem-Objekt schreiben. Bei diesem Muster müssen Sie den MIME-Typ der Daten im Voraus kennen.

try {
  const imgURL = '/images/generic/file.png';
  await navigator.clipboard.write([
    new ClipboardItem({
      // Set the key beforehand and write a promise as the value.
      'image/png': fetch(imgURL).then(response => response.blob()),
    })
  ]);
  console.log('Image copied.');
} catch (err) {
  console.error(err.name, err.message);
}

Unterstützte Browser

  • Chrome: 76
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Quelle

Das Kopier-Ereignis

Für den Fall, dass ein Nutzer das Kopieren in die Zwischenablage startet und nicht preventDefault() aufruft, enthält das copy-Ereignis ein clipboardData-Attribut mit den Elementen, die bereits das richtige Format haben. Wenn Sie Ihre eigene Logik implementieren möchten, müssen Sie preventDefault() aufrufen, um das Standardverhalten zugunsten Ihrer eigenen Implementierung zu verhindern. In diesem Fall ist clipboardData leer. Angenommen, es gibt eine Seite mit Text und einem Bild. Wenn der Nutzer den gesamten Text auswählt und eine Kopie in die Zwischenablage startet, sollte Ihre benutzerdefinierte Lösung den Text verwerfen und nur das Bild kopieren. Das geht so, wie im Codebeispiel unten gezeigt. In diesem Beispiel wird nicht behandelt, wie auf frühere APIs zugegriffen werden kann, wenn die Clipboard API nicht unterstützt wird.

<!-- The image we want on the clipboard. -->
<img src="kitten.webp" alt="Cute kitten.">
<!-- Some text we're not interested in. -->
<p>Lorem ipsum</p>
document.addEventListener("copy", async (e) => {
  // Prevent the default behavior.
  e.preventDefault();
  try {
    // Prepare an array for the clipboard items.
    let clipboardItems = [];
    // Assume `blob` is the blob representation of `kitten.webp`.
    clipboardItems.push(
      new ClipboardItem({
        [blob.type]: blob,
      })
    );
    await navigator.clipboard.write(clipboardItems);
    console.log("Image copied, text ignored.");
  } catch (err) {
    console.error(err.name, err.message);
  }
});

Für das copy-Ereignis:

Unterstützte Browser

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 22.
  • Safari: 3.

Quelle

Für ClipboardItem:

Unterstützte Browser

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Quelle

Einfügen: Daten aus der Zwischenablage lesen

readText()

Um Text aus der Zwischenablage zu lesen, rufen Sie navigator.clipboard.readText() auf und warten Sie, bis das zurückgegebene Promise aufgelöst wird:

async function getClipboardContents() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('Pasted content: ', text);
  } catch (err) {
    console.error('Failed to read clipboard contents: ', err);
  }
}

Unterstützte Browser

  • Chrome: 66.
  • Edge: 79.
  • Firefox: 125
  • Safari: 13.1.

Quelle

read()

Die Methode navigator.clipboard.read() ist ebenfalls asynchron und gibt ein Versprechen zurück. Wenn Sie ein Bild aus der Zwischenablage lesen möchten, rufen Sie eine Liste der ClipboardItem-Objekte ab und iterieren Sie dann darüber.

Da jeder ClipboardItem-Wert unterschiedliche Typen enthalten kann, müssen Sie die Liste der Typen noch einmal mit einer for...of-Schleife durchgehen. Rufen Sie für jeden Typ die Methode getType() mit dem aktuellen Typ als Argument auf, um den entsprechenden Blob abzurufen. Wie zuvor ist dieser Code nicht an Bilder gebunden und funktioniert auch mit anderen zukünftigen Dateitypen.

async function getClipboardContents() {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const blob = await clipboardItem.getType(type);
        console.log(URL.createObjectURL(blob));
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
}

Unterstützte Browser

  • Chrome: 76.
  • Edge: 79.
  • Firefox: 127.
  • Safari: 13.1.

Quelle

Mit eingefügten Dateien arbeiten

Es ist hilfreich, wenn Nutzer Tastenkürzel für die Zwischenablage wie Strg + C und Strg + V verwenden können. Chromium stellt schreibgeschützte Dateien in der Zwischenablage bereit, wie unten beschrieben. Das wird ausgelöst, wenn der Nutzer die standardmäßige Tastenkombination des Betriebssystems zum Einfügen verwendet oder in der Menüleiste des Browsers auf Bearbeiten und dann auf Einfügen klickt. Es ist kein weiterer Installationscode erforderlich.

document.addEventListener("paste", async e => {
  e.preventDefault();
  if (!e.clipboardData.files.length) {
    return;
  }
  const file = e.clipboardData.files[0];
  // Read the file's contents, assuming it's a text file.
  // There is no way to write back to it.
  console.log(await file.text());
});

Unterstützte Browser

  • Chrome: 3.
  • Rand: 12.
  • Firefox: 3.6.
  • Safari: 4.

Quelle

Das Ereignis „Einfügevorgang“

Wie bereits erwähnt, sind Ereignisse geplant, die mit der Clipboard API funktionieren. Vorläufig können Sie jedoch das vorhandene paste-Ereignis verwenden. Sie funktioniert gut mit den neuen asynchronen Methoden zum Lesen von Clipboard-Text. Wie beim Ereignis copy darf auch hier preventDefault() nicht vergessen werden.

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  const text = await navigator.clipboard.readText();
  console.log('Pasted text: ', text);
});

Unterstützte Browser

  • Chrome: 1.
  • Rand: 12.
  • Firefox: 22.
  • Safari: 3.

Quelle

Mehrere MIME-Typen verarbeiten

Bei den meisten Implementierungen werden bei einem einzigen Vorgang zum Ausschneiden oder Kopieren mehrere Datenformate in die Zwischenablage kopiert. Dafür gibt es zwei Gründe: Als App-Entwickler wissen Sie nicht, welche Möglichkeiten die App bietet, in die ein Nutzer Text oder Bilder kopieren möchte. Außerdem unterstützen viele Anwendungen das Einfügen strukturierter Daten als Nur-Text. Nutzer sehen in der Regel einen Menüpunkt Bearbeiten mit einem Namen wie Einfügen und Stil anpassen oder Einfügen ohne Formatierung.

Das folgende Beispiel zeigt, wie das geht. In diesem Beispiel werden Bilddaten über fetch() abgerufen. Sie können aber auch von einer <canvas> oder der File System Access API stammen.

async function copy() {
  const image = await fetch('kitten.png').then(response => response.blob());
  const text = new Blob(['Cute sleeping kitten'], {type: 'text/plain'});
  const item = new ClipboardItem({
    'text/plain': text,
    'image/png': image
  });
  await navigator.clipboard.write([item]);
}

Sicherheit und Berechtigungen

Der Zugriff auf die Zwischenablage war schon immer ein Sicherheitsrisiko für Browser. Ohne die richtigen Berechtigungen könnte eine Seite unbemerkt alle möglichen schädlichen Inhalte in die Zwischenablage eines Nutzers kopieren, was beim Einfügen zu katastrophalen Folgen führen würde. Stellen Sie sich eine Webseite vor, die rm -rf / oder ein Bild mit einer Dekomprimierungsbombeheimlich in die Zwischenablage kopiert.

Browseraufforderung, in der der Nutzer um die Berechtigung zur Zwischenablage gebeten wird
Die Berechtigungsaufforderung für die Clipboard API.

Das Gewähren von uneingeschränkten Lesezugriffs auf die Zwischenablage durch Webseiten ist noch problematischer. Nutzer kopieren regelmäßig vertrauliche Daten wie Passwörter und personenbezogene Daten in die Zwischenablage, die dann von jeder Seite ohne Wissen des Nutzers gelesen werden können.

Wie viele neue APIs wird die Clipboard API nur für Seiten unterstützt, die über HTTPS ausgeliefert werden. Um Missbrauch zu verhindern, ist der Zugriff auf die Zwischenablage nur zulässig, wenn eine Seite der aktive Tab ist. Seiten in aktiven Tabs können ohne Berechtigung in die Zwischenablage schreiben. Zum Lesen aus der Zwischenablage ist jedoch immer eine Berechtigung erforderlich.

Berechtigungen zum Kopieren und Einfügen wurden der Permissions API hinzugefügt. Die Berechtigung clipboard-write wird Seiten automatisch gewährt, wenn sie sich auf dem aktiven Tab befinden. Die Berechtigung clipboard-read muss angefordert werden. Sie können das tun, indem Sie versuchen, Daten aus der Zwischenablage zu lesen. Der folgende Code zeigt das letztere Beispiel:

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// Will be 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

// Listen for changes to the permission state
permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

Mit der Option allowWithoutGesture können Sie auch festlegen, ob zum Ausführen von Ausschneiden oder Einfügen eine Nutzergeste erforderlich ist. Der Standardwert für diesen Wert variiert je nach Browser. Sie sollten ihn daher immer angeben.

Hier kommt die asynchrone Natur der Clipboard API wirklich ins Spiel: Wenn Sie versuchen, Zwischenablagedaten zu lesen oder zu schreiben, wird der Nutzer automatisch um Erlaubnis gebeten, falls diese noch nicht erteilt wurde. Da die API versprechensbasiert ist, ist dies völlig transparent. Wenn ein Nutzer die Berechtigung für die Zwischenablage verweigert, wird das Versprechen abgelehnt, damit die Seite entsprechend reagieren kann.

Da Browser den Zugriff auf die Zwischenablage nur dann zulassen, wenn eine Seite der aktive Tab ist, werden einige der Beispiele nicht ausgeführt, wenn sie direkt in die Konsole des Browsers eingefügt werden, da die Entwicklertools selbst der aktive Tab sind. Es gibt einen Trick: Verschieben Sie den Zugriff auf die Zwischenablage mit setTimeout() und klicken Sie dann schnell auf die Seite, um den Fokus darauf zu legen, bevor die Funktionen aufgerufen werden:

setTimeout(async () => {
  const text = await navigator.clipboard.readText();
  console.log(text);
}, 2000);

Integration von Berechtigungsrichtlinien

Wenn Sie die API in Iframes verwenden möchten, müssen Sie sie mit der Berechtigungsrichtlinie aktivieren. Diese definiert einen Mechanismus, mit dem verschiedene Browserfunktionen und APIs selektiv aktiviert und deaktiviert werden können. Sie müssen je nach Anforderungen Ihrer Anwendung entweder clipboard-read oder clipboard-write oder beide übergeben.

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

Funktionserkennung

Wenn Sie die Async Clipboard API verwenden möchten, während alle Browser unterstützt werden, testen Sie navigator.clipboard und greifen Sie auf frühere Methoden zurück. So könnten Sie das Einfügen beispielsweise für andere Browser implementieren:

document.addEventListener('paste', async (e) => {
  e.preventDefault();
  let text;
  if (navigator.clipboard) {
    text = await navigator.clipboard.readText();
  }
  else {
    text = e.clipboardData.getData('text/plain');
  }
  console.log('Got pasted text: ', text);
});

Das ist aber noch nicht alles. Vor der Async Clipboard API gab es in verschiedenen Webbrowsern eine Mischung aus verschiedenen Implementierungen zum Kopieren und Einfügen. In den meisten Browsern können die Kopier- und Einfügefunktionen des Browsers mit document.execCommand('copy') und document.execCommand('paste') ausgelöst werden. Wenn der zu kopierende Text ein String ist, der nicht im DOM vorhanden ist, muss er in das DOM eingefügt und ausgewählt werden:

button.addEventListener('click', (e) => {
  const input = document.createElement('input');
  input.style.display = 'none';
  document.body.appendChild(input);
  input.value = text;
  input.focus();
  input.select();
  const result = document.execCommand('copy');
  if (result === 'unsuccessful') {
    console.error('Failed to copy text.');
  }
  input.remove();
});

Demos

In den folgenden Demos können Sie die Async Clipboard API ausprobieren. Auf Glitch können Sie die Textdemo oder die Bilddemo remixen, um damit zu experimentieren.

Im ersten Beispiel wird gezeigt, wie Text in die Zwischenablage kopiert und daraus wieder kopiert wird.

Wenn Sie die API mit Bildern ausprobieren möchten, verwenden Sie diese Demo. Zur Erinnerung: Es werden nur PNGs unterstützt und das auch nur in einigen Browsern.

Danksagungen

Die Asynchronous Clipboard API wurde von Darwin Huang und Gary Kačmarčík implementiert. Darwin hat auch die Demo zur Verfügung gestellt. Vielen Dank an Kyarik und noch einmal an Gary Kačmarčík für die Überprüfung einiger Teile dieses Artikels.

Hero-Image von Markus Winkler auf Unsplash