Blockierung des Zugriffs auf die Zwischenablage aufheben

Sichererer Zugriff auf die Zwischenablage für Text und Bilder

Traditionell wurde der Zugriff auf die Systemzwischenablage über document.execCommand() für Interaktionen in der Zwischenablage gewährt. Obwohl diese Methode zum Ausschneiden und Einfügen weithin unterstützt wird, hat sie auch Nachteile: Der Zugriff auf die Zwischenablage war synchron und konnte nur im DOM lesen und schreiben.

Das ist für kleine Textstellen in Ordnung, aber es gibt viele Fälle, in denen das Blockieren der Seite für die Übertragung der Zwischenablage ungünstig ist. Möglicherweise ist eine zeitaufwendige Bereinigung oder Decodierung von Bildern erforderlich, bevor Inhalte sicher eingefügt werden können. Der Browser muss möglicherweise verlinkte Ressourcen aus einem eingefügten Dokument laden oder inline verknüpfen. Dadurch wird die Seite blockiert, während sie auf das Laufwerk oder Netzwerk wartet. Stellen Sie sich vor, Sie fügen Berechtigungen hinzu, sodass der Browser die Seite blockieren und den Zugriff auf die Zwischenablage anfordern muss. Gleichzeitig sind die Berechtigungen für document.execCommand() für die Interaktion mit der Zwischenablage lose 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 unbedingt in jedem der folgenden Abschnitte die Übersicht zur Browserkompatibilität.

Kopieren: Daten in die Zwischenablage schreiben

writeText()

Um Text in die Zwischenablage zu kopieren, rufen Sie writeText() auf. Da diese API asynchron ist, gibt die Funktion writeText() ein Promise zurück, das aufgelöst oder abgelehnt wird, je nachdem, ob der übergebene Text erfolgreich kopiert wurde:

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

  • 66
  • 79
  • 63
  • 13.1

Quelle

schreiben()

writeText() ist nur eine praktische Methode für die allgemeine Methode write(), mit der Sie auch Bilder in die Zwischenablage kopieren können. Wie writeText() ist er asynchron und gibt ein Promise-Objekt zurück.

Sie benötigen das Bild als blob, um ein Bild in die Zwischenablage zu schreiben. Eine Möglichkeit dazu besteht darin, das Bild mit fetch() von einem Server anzufordern und dann blob() für die Antwort aufzurufen.

Das Anfordern eines Bildes vom Server kann aus verschiedenen Gründen nicht wünschenswert oder möglich sein. Sie können das Bild auch auf einen Canvas zeichnen und die Canvas-Methode toBlob() aufrufen.

Übergeben Sie als Nächstes ein Array von ClipboardItem-Objekten als Parameter an die Methode write(). Derzeit kann immer nur ein Image übergeben werden. Wir hoffen jedoch, in Zukunft mehrere Images hinzufügen zu können. ClipboardItem verwendet ein Objekt mit dem MIME-Typ des Bildes als Schlüssel und das Blob als Wert. Bei Blob-Objekten, die aus 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 kannst du ein Promise in das ClipboardItem-Objekt schreiben. Für dieses Muster müssen Sie den MIME-Typ der Daten vorher 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

  • 66
  • 79
  • 13.1

Quelle

Das Text-Ereignis

Wenn ein Nutzer das Kopieren in die Zwischenablage initiiert und preventDefault() nicht aufruft, enthält das copy-Ereignis das Attribut clipboardData mit den Elementen bereits im richtigen Format. Wenn Sie eine eigene Logik implementieren möchten, müssen Sie preventDefault() aufrufen. Damit wird das Standardverhalten zugunsten Ihrer eigenen Implementierung verhindert. In diesem Fall ist clipboardData leer. Angenommen, Sie haben eine Seite mit Text und einem Bild. Wenn der Nutzer alle auswählt und eine Kopie in der Zwischenablage startet, sollte Ihre benutzerdefinierte Lösung den Text verwerfen und nur das Bild kopieren. Dazu können Sie das Codebeispiel unten sehen. In diesem Beispiel wird nicht behandelt, wie ein Fallback auf frühere APIs verwendet wird, 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 Ereignis copy:

Unterstützte Browser

  • 1
  • 12
  • 22
  • 3

Quelle

Für ClipboardItem:

Unterstützte Browser

  • 76
  • 79
  • 13.1

Quelle

Einfügen: Daten aus der Zwischenablage werden gelesen

readText()

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

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

  • 66
  • 79
  • 13.1

Quelle

read()

Die Methode navigator.clipboard.read() ist ebenfalls asynchron und gibt ein Promise zurück. Um ein Bild aus der Zwischenablage zu lesen, rufen Sie eine Liste mit ClipboardItem-Objekten ab und iterieren diese dann.

Jede ClipboardItem kann ihren Inhalt in verschiedenen Typen enthalten. Daher müssen Sie die Liste der Typen durchgehen und wieder eine for...of-Schleife ausführen. 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 Images 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

  • 66
  • 79
  • 13.1

Quelle

Mit eingefügten Dateien arbeiten

Nutzer können dazu Tastenkürzel in der Zwischenablage verwenden, z. B. Strg + C und Strg + V. Chromium zeigt schreibgeschützte Dateien in der Zwischenablage an, wie unten beschrieben. Dies wird ausgelöst, wenn der Nutzer die Standardverknüpfung zum Einfügen des Betriebssystems verwendet oder in der Menüleiste des Browsers auf Bearbeiten und dann auf Einfügen klickt. Es ist kein weiterer Klempnercode 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

  • 3
  • 12
  • 3.6
  • 4

Quelle

Das Ereignis „Einfügen“

Wie bereits erwähnt, gibt es Pläne, Ereignisse einzuführen, die mit der Clipboard API funktionieren. Derzeit kannst du aber das vorhandene paste-Ereignis verwenden. Es funktioniert gut mit den neuen asynchronen Methoden zum Lesen von Text in der Zwischenablage. Vergessen Sie nicht, wie beim copy-Ereignis preventDefault() aufzurufen.

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

Unterstützte Browser

  • 1
  • 12
  • 22
  • 3

Quelle

Mehrere MIME-Typen verarbeiten

Bei den meisten Implementierungen werden für einen einzelnen Ausschneide- oder Kopiervorgang mehrere Datenformate in der Zwischenablage gespeichert. Dafür gibt es zwei Gründe: Als App-Entwickler haben Sie keine Möglichkeit, die Funktionen der App zu kennen, in die ein Nutzer Text oder Bilder kopieren möchte. Viele Anwendungen unterstützen das Einfügen strukturierter Daten als Nur-Text. Nutzer sehen diese Option in der Regel über den Menüpunkt Bearbeiten mit einem Namen wie Einfügen und Stil anpassen oder Ohne Formatierung einfügen.

Das folgende Beispiel zeigt, wie das funktioniert. In diesem Beispiel wird fetch() verwendet, um Bilddaten abzurufen. Es kann aber auch aus 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 stellte Browser schon immer ein Sicherheitsproblem dar. Ohne die entsprechenden Berechtigungen könnte eine Seite unbemerkt alle möglichen schädlichen Inhalte in die Zwischenablage eines Nutzers kopieren. Das würde beim Einfügen zu katastrophalen Ergebnissen führen. Angenommen, eine Webseite kopiert im Hintergrund rm -rf / oder ein Bild einer Dekomprimierungsbombe in die Zwischenablage.

Aufforderung im Browser, in der der Nutzer um die Berechtigung für die Zwischenablage gebeten wird.
Die Berechtigungsaufforderung für die Clipboard API.

Es ist noch mühsam, Webseiten uneingeschränkten Lesezugriff auf die Zwischenablage zu gewähren. Nutzer kopieren regelmäßig vertrauliche Informationen 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 bereitgestellt werden. Um Missbrauch zu verhindern, ist der Zugriff auf die Zwischenablage nur dann zulässig, wenn eine Seite der aktive Tab ist. Seiten in aktiven Tabs können in die Zwischenablage schreiben, ohne eine Berechtigung anzufordern, aber das Lesen aus der Zwischenablage erfordert immer eine entsprechende Berechtigung.

Die Berechtigungen zum Kopieren und Einfügen wurden der Permissions API hinzugefügt. Die Berechtigung clipboard-write wird Seiten automatisch gewährt, wenn sie der aktive Tab sind. Die Berechtigung clipboard-read muss angefordert werden. Dazu können Sie versuchen, Daten aus der Zwischenablage zu lesen. Der folgende Code zeigt Letzteres:

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 eine Nutzergeste zum Ausschneiden oder Einfügen erforderlich ist. Der Standardwert für diesen Wert variiert je nach Browser, daher sollten Sie ihn immer einschließen.

An dieser Stelle kommt der asynchrone Charakter der Clipboard API besonders praktisch vor: Beim Versuch, Daten in der Zwischenablage zu lesen oder zu schreiben, wird der Nutzer automatisch zur Berechtigung aufgefordert, sofern dies noch nicht der Fall ist. Da die API auf Promise basiert, ist dies vollkommen transparent. Ein Nutzer, der die Berechtigung für die Zwischenablage nicht zulässt, führt dazu, dass das Versprechen abgelehnt wird, sodass 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 hier nicht ausgeführt, wenn sie direkt in die Browserkonsole eingefügt werden, da die Entwicklertools selbst der aktive Tab sind. Dafür gibt es einen Trick: Sie können den Zugriff auf die Zwischenablage mit setTimeout() zurückstellen und dann schnell auf die Seite klicken, um sie zu fokussieren, 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 mithilfe der Berechtigungsrichtlinie aktivieren. Diese definiert einen Mechanismus, mit dem verschiedene Browserfunktionen und APIs selektiv aktiviert und deaktiviert werden können. Konkret müssen Sie abhängig von den 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 und gleichzeitig alle Browser unterstützen möchten, testen Sie navigator.clipboard und greifen Sie auf frühere Methoden zurück. So können Sie das Einfügen z. B. zur Einbindung anderer 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);
});

Aber das ist noch nicht alles. Vor der Async Clipboard API gab es eine Mischung aus verschiedenen Implementierungen zum Kopieren und Einfügen in den Webbrowsern. In den meisten Browsern kann das Kopieren und Einfügen im Browser 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 eingeschleust 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 Demos unten können Sie mit der Async Clipboard API experimentieren. In Glitch kannst du einen Remix der Textdemo oder der Bilddemo erstellen und damit experimentieren.

Das erste Beispiel zeigt, wie Text in die und aus der Zwischenablage verschoben wird.

Verwenden Sie diese Demo, um die API mit Bildern zu testen. Denken Sie daran, dass nur PNGs und nur in wenigen Browsern unterstützt werden.

Danksagungen

Die Asynchronous Clipboard API wurde von Darwin Huang und Gary Kačmarčík implementiert. Darwin lieferte auch die Demo. Vielen Dank an Kyarik und Gary Kačmarčík für die Rezension von Teilen dieses Artikels.

Hero-Image von Markus Winkler auf Unsplash