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

Accès plus sécurisé au texte et aux images dans le presse-papiers

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

Ce n'est pas un problème pour les petits morceaux de texte, mais dans de nombreux cas, le blocage de la page pour le transfert du presse-papiers est une mauvaise expérience. Un nettoyage ou un décodage des images chronophages peut être nécessaire avant que le contenu puisse être collé en toute sécurité. Le navigateur devra peut-être charger ou intégrer les ressources liées à partir d'un document collé. Cela bloquerait la page en attendant sur le disque ou le réseau. Imaginez que vous ajoutiez des autorisations, qui obligent le navigateur à bloquer la page tout en demandant l'accès au presse-papiers. En parallèle, les autorisations mises en place autour de document.execCommand() pour l'interaction avec le presse-papiers sont définies de manière approximative 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 se limite à la gestion du texte et des images dans la plupart des navigateurs, mais sa prise en charge varie. Veillez à étudier attentivement la présentation de la compatibilité des navigateurs dans chacune des sections suivantes.

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

writeText()

Pour copier du texte dans le presse-papiers, appelez writeText(). Cette API étant asynchrone, la fonction writeText() renvoie une promesse qui se résout ou refuse selon que le texte transmis a bien été copié:

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

  • 66
  • 79
  • 63
  • 13.1

Source

écrire()

En fait, writeText() n'est qu'une méthode pratique pour la méthode générique write(), 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, elle doit être au format blob. Pour ce faire, vous pouvez demander l'image à un serveur à l'aide de fetch(), puis appeler blob() dans la réponse.

Pour diverses raisons, une demande d'image au serveur n'est peut-être pas souhaitable, ni possible. Heureusement, vous pouvez également dessiner l'image sur un canevas et appeler la méthode toBlob() du canevas.

Transmettez ensuite un tableau d'objets ClipboardItem en tant que paramètre à la méthode write(). Actuellement, vous ne pouvez transmettre qu'une seule image à la fois, mais nous espérons pouvoir en ajouter plusieurs à l'avenir. ClipboardItem utilise un objet avec le type MIME de l'image comme clé et l'objet 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 dans l'objet ClipboardItem. Pour ce modèle, 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

  • 66
  • 79
  • 13.1

Source

Événement de copie

Si un utilisateur crée une copie du presse-papiers et n'appelle pas preventDefault(), l'événement copy inclut une propriété clipboardData avec les éléments déjà au bon format. Si vous souhaitez implémenter votre propre logique, vous devez appeler preventDefault() pour éviter le comportement par défaut en faveur de votre propre implémentation. Dans ce cas, clipboardData sera vide. Prenons l'exemple d'une page avec du texte et une image. Lorsque l'utilisateur sélectionne tout et lance une copie du presse-papiers, votre solution personnalisée doit supprimer le texte et ne copier que l'image. Pour ce faire, procédez comme indiqué dans l'exemple de code ci-dessous. Ce qui n'est pas abordé dans cet exemple, c'est 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

  • 1
  • 12
  • 22
  • 3

Source

Pour ClipboardItem :

Navigateurs pris en charge

  • 76
  • 79
  • 13.1

Source

Coller: lecture des données du presse-papiers

readText()

Pour lire le texte dans le 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

  • 66
  • 79
  • 13.1

Source

lecture()

La méthode navigator.clipboard.read() est également asynchrone et renvoie une promesse. Pour lire une image à partir du presse-papiers, obtenez une liste d'objets ClipboardItem, puis itérez-les.

Chaque ClipboardItem peut contenir son contenu de différents types. Vous devrez donc itérer la liste des types, à nouveau à l'aide d'une boucle for...of. Pour chaque type, appelez la méthode getType() avec le type actuel en tant qu'argument pour obtenir l'objet blob correspondant. Comme précédemment, ce code n'est pas lié aux images et fonctionnera avec d'autres types de fichiers à l'avenir.

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

  • 66
  • 79
  • 13.1

Source

Utiliser des fichiers collés

Il est utile pour les utilisateurs de pouvoir 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. Cette action se déclenche lorsque l'utilisateur clique sur le raccourci de collage par défaut du système d'exploitation ou sur Modifier, puis sur Coller dans la barre de menu du navigateur. Aucun autre code de plomberie 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

  • 3
  • 12
  • 3.6
  • 4

Source

L'événement de collage

Comme indiqué précédemment, il est prévu de créer des événements compatibles avec l'API Clipboard, mais pour l'instant, vous pouvez utiliser l'événement paste existant. Il fonctionne bien avec les nouvelles méthodes asynchrones de lecture du 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

  • 1
  • 12
  • 22
  • 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 opération de couper ou de copie. Cela s'explique par deux raisons: en tant que développeur d'applications, vous n'avez aucun moyen de connaître les fonctionnalités de l'application dans laquelle un utilisateur souhaite copier du texte ou des images. De plus, de nombreuses applications acceptent le collage de données structurées en tant que texte brut. Il est généralement présenté aux utilisateurs qui disposent d'un élément de menu Modifier avec un nom tel que Coller et faire correspondre 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'une <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 présenté un problème de sécurité pour les navigateurs. Sans les autorisations appropriées, une page pourrait copier silencieusement toute sorte de contenu malveillant dans le presse-papiers d'un utilisateur, ce qui entraînerait des résultats catastrophiques lorsqu'elle était collée. Imaginez une page Web qui copie silencieusement rm -rf / ou une image de bombe à décompression dans votre presse-papiers.

Invite du navigateur demandant à l&#39;utilisateur l&#39;autorisation d&#39;utiliser le presse-papiers.
Invite d'autorisation pour l'API Clipboard.

Accorder aux pages Web un accès en lecture sans entrave au presse-papiers est encore plus pénible. Les utilisateurs copient régulièrement des informations sensibles telles que des mots de passe et des informations personnelles dans le presse-papiers, qui peuvent ensuite être lues par n'importe quelle page à son insu.

Comme avec de nombreuses nouvelles API, l'API Clipboard n'est compatible qu'avec les pages diffusées via HTTPS. Pour éviter toute utilisation abusive, 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'il s'agit de l'onglet actif. L'autorisation clipboard-read doit être demandée. Vous pouvez le faire en essayant de lire les données 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 de l'utilisateur est requis pour effectuer un couper ou un collage à l'aide de l'option allowWithoutGesture. La valeur par défaut varie selon le navigateur. Vous devez donc toujours l'inclure.

C'est là que la nature asynchrone de l'API Clipboard s'avère très pratique : toute tentative de lecture ou d'écriture des données du presse-papiers demande automatiquement à l'utilisateur une autorisation si celle-ci n'a pas déjà été accordée. Comme l'API est basée sur des promesses, cela est totalement transparent. Un utilisateur refusant l'autorisation du presse-papiers entraîne le rejet de la promesse afin que la page puisse répondre 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 exemples ne s'exécutent pas s'ils sont collés directement dans la console du navigateur, car les outils pour les développeurs eux-mêmes constituent l'onglet actif. Astuce: différez l'accès au presse-papiers à l'aide de setTimeout(), puis cliquez rapidement sur la page pour la sélectionner 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 d'autorisation

Pour utiliser l'API dans des cadres iFrame, vous devez l'activer avec les règles relatives aux autorisations, qui définissent un mécanisme permettant d'activer et de désactiver de manière sélective diverses fonctionnalités et API du navigateur. Concrètement, vous devez transmettre clipboard-read ou clipboard-write, ou les deux, en fonction des 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 tout en prenant en charge tous les navigateurs, testez navigator.clipboard et revenez aux méthodes précédentes. Par exemple, voici comment 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, il existait différentes implémentations de copier-coller dans les navigateurs Web. Dans la plupart des navigateurs, le copier-coller propre au navigateur peut être déclenché à l'aide de document.execCommand('copy') et document.execCommand('paste'). Si le texte à copier est une chaîne absente du DOM, il doit être injecté dans le DOM et sélectionné:

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 jouer avec l'API Async Clipboard dans les démonstrations ci-dessous. Dans Glitch, vous pouvez remixer la démonstration textuelle ou la démonstration d'images pour les tester.

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

Pour essayer l'API avec des images, utilisez cette démonstration. N'oubliez pas que seuls les fichiers PNG sont compatibles avec certains navigateurs seulement.

Remerciements

L'API Asynchrone Clipboard a été implémentée par Darwin Huang et Gary Kačmarčík. Darwin a également effectué la démonstration. Merci à Kyarik et encore à Gary Kačmarčík d'avoir pris connaissance de certaines parties de cet article.

Image héros par Markus Winkler sur Unsplash.