Déblocage de l'accès au presse-papiers

Accès plus sûr et débloqué au presse-papiers pour le texte et les images

La méthode traditionnelle d'accès au presse-papiers du système était document.execCommand() pour les interactions avec le presse-papiers. Bien que largement acceptée, cette méthode de découpage et de collage avait un coût : l'accès au presse-papiers était synchrone et ne pouvait lire et écrire que dans le DOM.

Cela convient pour de petits morceaux de texte, mais dans de nombreux cas, le blocage pour transférer le presse-papiers est une mauvaise expérience. Désinfection ou nettoyage un décodage des images peut être nécessaire avant de pouvoir coller le contenu en toute sécurité. Le navigateur peut être amené à charger ou à intégrer des ressources associées à partir d'un document collé. Ce serait bloquer la page en attendant le disque ou le réseau. Imaginez ajouter des autorisations, ce qui oblige le navigateur à bloquer la page tout en demandant l'accès au presse-papiers. En même temps, les autorisations mises en place autour de document.execCommand() pour l'interaction avec le presse-papiers sont vaguement définies et varient d'un navigateur à l'autre.

L'API Async Clipboard résout ces problèmes en fournissant un modèle d'autorisations bien défini qui ne bloque pas la page. L'API Async Clipboard est limitée à la gestion du texte et des images. sur la plupart des navigateurs, mais la prise en charge varie. Étudiez attentivement le navigateur de la compatibilité pour chacune des sections suivantes.

Copier : écrire des données dans le presse-papiers

writeText()

Pour copier du texte dans le presse-papiers, appelez writeText(). Puisque cette API est asynchrone, la fonction writeText() renvoie une promesse qui résout ou rejette selon que le texte transmis a bien été copié ou non:

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

Navigateurs pris en charge

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

Source

write()

En fait, writeText() n'est qu'une méthode pratique pour la write() générique , qui vous permet également de copier des images dans le presse-papiers. Comme writeText(), il est asynchrone et renvoie une promesse.

Pour écrire une image dans le presse-papiers, vous avez besoin de l'image en tant que blob Une façon de faire consiste à demander l'image à un serveur à l'aide de fetch(), puis à appeler blob() sur le de réponse.

Il peut être non souhaitable ou impossible de demander une image au serveur pour diverses raisons. Heureusement, vous pouvez également dessiner l'image sur un canevas et appeler le canevas toBlob() .

Transmettez ensuite un tableau d'objets ClipboardItem en tant que paramètre à write(). . Actuellement, vous ne pouvez transmettre qu'une image à la fois, mais nous espérons ajouter la prise en charge de plusieurs images à l'avenir. ClipboardItem prend un objet avec le type MIME de l'image comme clé et le blob comme valeur. Pour les objets blob obtenus à partir de fetch() ou canvas.toBlob(), la propriété blob.type contient automatiquement le type MIME approprié pour une image.

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

Vous pouvez également écrire une promesse à l'objet ClipboardItem. Pour ce format, vous devez connaître au préalable le type MIME des données.

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

Navigateurs pris en charge

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

Source

Événement de copie

Lorsqu'un utilisateur lance une copie du presse-papiers et n'appelle pas preventDefault(), la copy événement inclut une propriété clipboardData dont les éléments sont déjà au bon format ; Si vous souhaitez implémenter votre propre logique, vous devez appeler preventDefault() pour empêcher le comportement par défaut au profit de votre propre implémentation. Dans ce cas, clipboardData sera vide. Prenons l’exemple d’une page avec du texte et une image, et lorsque l’utilisateur sélectionne tout et lance une copie du presse-papiers, votre solution personnalisée doit supprimer le texte et ne copier l'image. Pour ce faire, reportez-vous à l'exemple de code ci-dessous. Cet exemple ne montre pas comment revenir à des API antérieures lorsque l'API Clipboard n'est pas prise en charge.

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

Pour l'événement copy:

Navigateurs pris en charge

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

Source

Pour ClipboardItem :

Navigateurs pris en charge

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

Source

Coller : lire les données du presse-papiers

readText()

Pour lire du texte à partir du presse-papiers, appelez navigator.clipboard.readText() et attendez que la promesse renvoyée soit résolue :

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

Navigateurs pris en charge

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

Source

read()

La méthode navigator.clipboard.read() est également asynchrone et renvoie une prometteurs. Pour lire une image depuis le presse-papiers, obtenez une liste d'objets ClipboardItem, puis itérez dessus.

Chaque ClipboardItem peut contenir son contenu de différents types. Vous devez donc itérer sur la liste des types, encore une fois à l'aide d'une boucle for...of. Pour chaque type, appelez la méthode getType() avec le type actuel comme argument pour obtenir le blob correspondant. Comme précédemment, ce code n'est pas lié à des images travailler avec d'autres types de fichiers futurs.

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

Navigateurs pris en charge

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

Source

Travailler avec des fichiers collés

Il est utile que les utilisateurs puissent utiliser les raccourcis clavier du presse-papiers, tels que Ctrl+C et Ctrl+V. Chromium expose les fichiers en lecture seule dans le presse-papiers, comme indiqué ci-dessous. Cela se produit lorsque l'utilisateur appuie sur le raccourci de collage par défaut du système d'exploitation ou clique sur Modifier, puis sur Coller dans la barre de menu du navigateur. Aucun code de plomberie supplémentaire n'est nécessaire.

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

Navigateurs pris en charge

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

Source

Événement de collage

Comme indiqué précédemment, nous prévoyons d'introduire des événements pour l'API Clipboard, mais pour le moment, vous pouvez utiliser l'événement paste existant. Il fonctionne parfaitement avec le nouveau des méthodes asynchrones pour lire le texte du presse-papiers. Comme pour l'événement copy, n'oubliez pas d'appeler preventDefault().

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

Navigateurs pris en charge

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

Source

Gérer plusieurs types MIME

La plupart des implémentations placent plusieurs formats de données dans le presse-papiers pour une seule coupe. ou de copie. Il y a deux raisons à cela: en tant que développeur d'applications, aucun moyen de connaître les fonctionnalités de l'application dans lesquelles un utilisateur souhaite copier du texte ou des images, et de nombreuses applications prennent en charge le collage de données structurées en texte brut. Cette option est généralement présentée aux utilisateurs sous la forme d'un élément de menu Modifier avec un nom tel que Coller et appliquer le style ou Coller sans mise en forme.

L'exemple suivant montre comment procéder. Cet exemple utilise fetch() pour obtenir des données d'image, mais il peut également provenir d'un <canvas> ou de l'API File System Access.

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

Sécurité et autorisations

L'accès au presse-papiers a toujours représenté un problème de sécurité pour les navigateurs. Sans autorisations appropriées, une page peut copier de manière silencieuse toutes sortes de contenus malveillants dans le presse-papiers d'un utilisateur, ce qui produira des résultats catastrophiques lors du collage. Imaginez une page Web qui copie silencieusement rm -rf / ou une image de bombe de décompression dans votre presse-papiers.

Invite du navigateur demandant à l&#39;utilisateur l&#39;autorisation d&#39;accéder au presse-papiers. <ph type="x-smartling-placeholder">
</ph> Invite d'autorisation pour l'API Clipboard.

Accorder aux pages Web un accès en lecture illimité au presse-papiers est encore plus problématique. Les utilisateurs copient régulièrement des informations sensibles telles que des mots de passe et vos informations personnelles dans le presse-papiers, qui peut ensuite être lu par n'importe quelle page les connaissances de l'utilisateur.

Comme c'est le cas avec de nombreuses nouvelles API, l'API Clipboard n'est compatible qu'avec les pages diffusées HTTPS Pour éviter les abus, l'accès au presse-papiers n'est autorisé que lorsqu'une page est l'onglet actif. Les pages des onglets actifs peuvent écrire dans le presse-papiers sans demander d'autorisation, mais la lecture à partir du presse-papiers nécessite toujours une autorisation.

Des autorisations de copier-coller ont été ajoutées à l'API Permissions. L'autorisation clipboard-write est accordée automatiquement aux pages lorsqu'elles sont l'onglet actif. L'autorisation clipboard-read doit être demandée. Pour ce faire, essayez de lire les données à partir du presse-papiers. Le code ci-dessous illustre ce dernier:

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

Vous pouvez également contrôler si un geste utilisateur est nécessaire pour appeler la coupe ou le collage à l'aide de l'option allowWithoutGesture. La valeur par défaut pour cette valeur varie selon le navigateur. Vous devez donc toujours l'inclure.

C'est là que la nature asynchrone de l'API Clipboard s'avère vraiment pratique: toute tentative de lecture ou d'écriture des données du presse-papiers invite automatiquement l'utilisateur à saisir si elle ne l'a pas déjà été. L'API étant basée sur des promesses, cela est totalement transparent. Si un utilisateur refuse l'autorisation d'utiliser le presse-papiers, la promesse de rejeter afin que la page puisse réagir de manière appropriée.

Étant donné que les navigateurs n'autorisent l'accès au presse-papiers que lorsqu'une page est l'onglet actif, vous constaterez que certains des exemples présentés ici ne s'exécutent pas s'ils sont collés directement dans la console du navigateur, car les outils pour les développeurs sont eux-mêmes l'onglet actif. Une astuce consiste à différer accéder au presse-papiers avec setTimeout(), puis cliquer rapidement dans la page pour avant que les fonctions ne soient appelées:

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

Intégration des règles sur les autorisations

Pour utiliser l'API dans des iFrames, vous devez l'activer avec la stratégie d'autorisation, qui définit un mécanisme permettant d'activer et de désactiver sélectivement diverses fonctionnalités et API du navigateur. Concrètement, vous devez transmettre clipboard-read ou clipboard-write, ou les deux, selon les besoins de votre application.

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

Détection de fonctionnalités

Pour utiliser l'API Async Clipboard avec tous les navigateurs, testez navigator.clipboard et revenir aux méthodes précédentes. Par exemple, voici comment vous pouvez implémenter le collage pour inclure d'autres navigateurs.

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

Ce n'est pas tout. Avant l'API Async Clipboard, différentes implémentations de copier-coller dans les navigateurs Web. Dans la plupart des navigateurs, les copier-coller du navigateur peuvent être déclenchés document.execCommand('copy') et document.execCommand('paste'). Si le texte à copier est une chaîne qui ne figure pas dans le DOM, elle doit être injectée dans le DOM et sélectionnés:

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

Démonstrations

Vous pouvez tester l'API Async Clipboard dans les démonstrations ci-dessous. Sur Glitch, vous pouvez remixer la démo de texte ou la démo d'image pour les tester.

Le premier exemple montre comment déplacer du texte dans le presse-papiers et en sortir.

Pour tester l'API avec des images, utilisez cette démonstration. N'oubliez pas que seuls les fichiers PNG sont acceptés et uniquement dans quelques navigateurs.

Remerciements

L'API Async Clipboard a été implémentée par Darwin Huang et Gary Kačmarčík. Darwin a également fourni la démonstration. Merci à Kyarik et encore à Gary Kačmarčík pour pour revoir certaines parties de cet article.

Image principale par Markus Winkler sur Unsplash.