Sblocco dell'accesso agli appunti

Accesso più sicuro e sbloccato agli appunti per testo e immagini

Il modo tradizionale per accedere agli appunti di sistema era tramite document.execCommand() per le interazioni con gli appunti. Sebbene sia ampiamente supportato, questo metodo di taglio e incolla era a pagamento: l'accesso agli appunti era sincrono e poteva solo leggere e scrivere nel DOM.

Questo non è un problema per piccole parti di testo, ma in molti casi bloccare la pagina per il trasferimento degli appunti è un'esperienza negativa. Prima di poter incollare i contenuti in sicurezza, potrebbero essere necessarie operazioni di sanitizzazione o decodifica delle immagini, che richiedono molto tempo. Il browser potrebbe dover caricare o incorporare le risorse collegate da un documento incollato. In questo modo, la pagina viene bloccata sul disco o sulla rete. Immagina di aggiungere autorizzazioni alla combinazione, richiedendo che il browser blocchi la pagina mentre richiede l'accesso agli appunti. Allo stesso tempo, le autorizzazioni applicate intorno a document.execCommand() per l'interazione con gli appunti sono definite in modo vago e variano da browser a browser.

L'API Async Clipboard risolve questi problemi, fornendo un modello di autorizzazioni ben definito che non blocca la pagina. L'API Async Clipboard è limitata alla gestione di testo e immagini sulla maggior parte dei browser, ma il supporto varia. Assicurati di esaminare attentamente la panoramica sulla compatibilità del browser per ciascuna delle sezioni seguenti.

Copia: scrittura di dati negli appunti

writeText()

Per copiare il testo negli appunti, chiama writeText(). Poiché questa API è asincrona, la funzione writeText() restituisce una promessa che risolve o rifiuta a seconda che il testo trasmesso venga copiato correttamente:

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

Supporto dei browser

  • 66
  • 79
  • 63
  • 13.1

Fonte

write()

In realtà, writeText() è solo un pratico metodo per il metodo write() generico, che consente anche di copiare le immagini negli appunti. Come writeText(), è asincrono e restituisce un valore Promise.

Per scrivere un'immagine negli appunti, devi avere l'immagine come blob. Un modo per farlo è richiedere l'immagine a un server utilizzando fetch(), quindi chiamare blob() in risposta.

Richiedere un'immagine al server potrebbe non essere desiderabile o possibile per diversi motivi. Fortunatamente, puoi anche disegnare l'immagine su una tela e chiamare il metodo toBlob() della tela.

Quindi, passa un array di oggetti ClipboardItem come parametro al metodo write(). Al momento puoi trasmettere una sola immagine alla volta, ma ci auguriamo di aggiungere il supporto per più immagini in futuro. ClipboardItem prende un oggetto con il tipo MIME dell'immagine come chiave e il blob come valore. Per gli oggetti BLOB ottenuti da fetch() o canvas.toBlob(), la proprietà blob.type contiene automaticamente il tipo MIME corretto di un'immagine.

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

In alternativa, puoi scrivere una promessa per l'oggetto ClipboardItem. Per questo pattern, devi conoscere preventivamente il tipo MIME dei dati.

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

Supporto dei browser

  • 66
  • 79
  • 13.1

Fonte

L'evento di copia

Nel caso in cui un utente avvia una copia degli appunti e non chiama preventDefault(), l'evento copy include una proprietà clipboardData con gli elementi già nel formato corretto. Se vuoi implementare una logica personalizzata, devi chiamare preventDefault() per impedire il comportamento predefinito a favore della tua implementazione. In questo caso, il campo clipboardData sarà vuoto. Considera una pagina con testo e un'immagine e, quando l'utente seleziona tutto e avvia una copia degli appunti, la soluzione personalizzata dovrebbe eliminare il testo e copiare solo l'immagine. come illustrato nell'esempio di codice riportato di seguito. Gli elementi non trattati in questo esempio sono come tornare alle API precedenti quando l'API Clipboard non è supportata.

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

Per l'evento copy:

Supporto dei browser

  • 1
  • 12
  • 22
  • 3

Fonte

Per ClipboardItem:

Supporto dei browser

  • 76
  • 79
  • 13.1

Fonte

Incolla: lettura dei dati dagli appunti

readText()

Per leggere il testo dagli appunti, chiama navigator.clipboard.readText() e attendi che venga risolta la promessa restituita:

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

Supporto dei browser

  • 66
  • 79
  • 13.1

Fonte

read()

Anche il metodo navigator.clipboard.read() è asincrono e restituisce una promessa. Per leggere un'immagine dagli appunti, recupera un elenco di oggetti ClipboardItem e ripeti l'operazione.

Ogni ClipboardItem può contenere i contenuti in diversi tipi, quindi dovrai eseguire l'iterazione nell'elenco dei tipi di nuovo utilizzando un loop for...of. Per ogni tipo, chiama il metodo getType() con il tipo corrente come argomento per ottenere il BLOB corrispondente. Come prima, questo codice non è collegato alle immagini e funzionerà con altri tipi di file futuri.

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

Supporto dei browser

  • 66
  • 79
  • 13.1

Fonte

Utilizzare i file incollati

Gli utenti possono utilizzare le scorciatoie da tastiera degli appunti come Ctrl+C e Ctrl+V. Chromium espone i file di sola lettura negli appunti come descritto di seguito. Questo si attiva quando l'utente seleziona la scorciatoia predefinita del sistema operativo per incollare o quando fa clic su Modifica e poi su Incolla nella barra dei menu del browser. Non sono necessari ulteriori codici idraulici.

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

Supporto dei browser

  • 3
  • 12
  • 3.6
  • 4

Fonte

L'evento Incolla

Come già osservato, è previsto l'introduzione di eventi che funzionino con l'API Clipboard, ma per il momento puoi utilizzare l'evento paste esistente. Funziona perfettamente con i nuovi metodi asincroni per la lettura del testo degli appunti. Come per l'evento copy, non dimenticare di chiamare preventDefault().

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

Supporto dei browser

  • 1
  • 12
  • 22
  • 3

Fonte

Gestione di più tipi MIME

La maggior parte delle implementazioni inserisce più formati dei dati negli appunti per un'unica operazione di taglio o copia. I motivi sono due: in qualità di sviluppatore di app, non hai modo di conoscere le funzionalità dell'app in cui un utente vuole copiare testo o immagini e molte applicazioni supportano di incollare i dati strutturati come testo normale. In genere, questo viene presentato agli utenti con una voce di menu Modifica con un nome come Incolla e trova stile o Incolla senza formattazione.

Nell'esempio seguente viene illustrato come eseguire questa operazione. Questo esempio utilizza fetch() per ottenere i dati delle immagini, ma potrebbe anche provenire da <canvas> o dall'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]);
}

Sicurezza e autorizzazioni

L'accesso agli appunti ha sempre presentato un problema di sicurezza per i browser. Senza le autorizzazioni appropriate, una pagina potrebbe copiare in silenzio negli appunti di un utente tutti i tipi di contenuti dannosi che, se incollati, produrrebbero risultati catastrofici. Immagina una pagina web che copia in silenzio rm -rf / o un'immagine di una bomba di decompressione negli appunti.

Prompt del browser che chiede all&#39;utente l&#39;autorizzazione per gli appunti.
La richiesta di autorizzazione per l'API Clipboard.

Concedere l'accesso in lettura agli appunti alle pagine web è ancora più complicato. Gli utenti copiano abitualmente negli appunti informazioni sensibili, come password e dettagli personali, che potrebbero essere lette da qualsiasi pagina all'insaputa dell'utente.

Come con molte nuove API, l'API Clipboard è supportata solo per le pagine pubblicate tramite HTTPS. Per impedire abusi, l'accesso agli appunti è consentito solo quando una pagina è la scheda attiva. Le pagine nelle schede attive possono scrivere negli appunti senza richiedere l'autorizzazione, ma la lettura dagli appunti richiede sempre l'autorizzazione.

Le autorizzazioni per le operazioni di copia e incolla sono state aggiunte all'APIPermissions. L'autorizzazione clipboard-write viene concessa automaticamente alle pagine che sono le schede attive. È necessario richiedere l'autorizzazione clipboard-read, operazione che puoi eseguire provando a leggere i dati dagli appunti. Il codice seguente mostra quest'ultimo:

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

Puoi anche controllare se è necessario un gesto dell'utente per richiamare l'elemento di taglio o incolla utilizzando l'opzione allowWithoutGesture. L'impostazione predefinita per questo valore varia a seconda del browser, quindi dovresti sempre includerlo.

In questo caso la natura asincrona dell'API Clipboard torna utile: il tentativo di leggere o scrivere dati negli appunti richiede automaticamente all'utente l'autorizzazione, se non è già stata concessa. Poiché l'API è basata su promesse, questo è completamente trasparente e un utente che nega l'autorizzazione agli appunti comporta il rifiuto della promessa in modo che la pagina possa rispondere in modo appropriato.

Poiché i browser consentono l'accesso agli appunti solo quando una pagina è la scheda attiva, noterai che alcuni degli esempi qui riportati non vengono eseguiti se incollati direttamente nella console del browser, poiché gli strumenti per sviluppatori stessi sono la scheda attiva. C'è un trucco: rimanda l'accesso agli appunti utilizzando setTimeout(), quindi fai clic rapidamente all'interno della pagina per impostarla prima che vengano chiamate le funzioni:

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

Integrazione dei criteri relativi alle autorizzazioni

Per utilizzare l'API negli iframe, devi abilitarla con il Criterio di autorizzazioni, che definisce un meccanismo che consente di abilitare e disabilitare in modo selettivo varie funzionalità del browser e API. Concretamente, devi passare o entrambi i passaggi clipboard-read o clipboard-write, a seconda delle esigenze della tua app.

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

Rilevamento delle funzionalità

Per utilizzare l'API Async Clipboard e supportare tutti i browser, esegui il test di navigator.clipboard e utilizza i metodi precedenti. Ad esempio, ecco come potresti implementare il metodo Incolla per includere altri browser.

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

Non è tutta la storia. Prima dell'API Async Clipboard, esisteva un mix di implementazioni di copia e incolla diverse nei browser web. Nella maggior parte dei browser, la funzionalità di copia e incolla del browser può essere attivata utilizzando document.execCommand('copy') e document.execCommand('paste'). Se il testo da copiare è una stringa non presente nel DOM, deve essere inserito nel DOM e selezionato:

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

Demo

Puoi giocare con l'API Async Clipboard nelle demo riportate di seguito. Su Glitch puoi fare il remix della demo del testo o della demo delle immagini per sperimentarle.

Il primo esempio mostra lo spostamento del testo all'interno e all'esterno degli appunti.

Per provare l'API con le immagini, utilizza questa demo. Ricorda che sono supportati solo i file PNG e soltanto in alcuni browser.

Ringraziamenti

L'API Async Clipboard è stata implementata da Darwin Huang e Gary Kačmarčík. Anche Darwin ha fornito la demo. Ringraziamo Kyarik e Gary Kačmarčík per aver letto alcune parti di questo articolo.

Immagine hero di Markus Winkler su Unsplash.