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 ampiamente supportato, questo metodo di taglio e colla aveva un costo: l'accesso alla clipboard era sincrono e poteva solo leggere e scrivere nel DOM.

Questo va bene per frammenti di testo di piccole dimensioni, ma ci sono molti casi in cui il blocco pagina per il trasferimento degli appunti è un'esperienza negativa. La sanificazione richiede molto tempo potrebbe essere necessaria la decodifica delle immagini per poter incollare i contenuti in modo sicuro. Il browser potrebbe dover caricare o incorporare risorse collegate da un documento incollato. Ciò significherebbe bloccare la pagina mentre si attendono sul disco o sulla rete. Immagina di aggiungere delle autorizzazioni nel mix, richiedendo che il browser blocchi la pagina durante la richiesta. accesso agli appunti. Allo stesso tempo, le autorizzazioni messe in atto document.execCommand() per l'interazione con gli appunti è definito a basso livello e può variare tra i browser.

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

Copy: 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 si risolve o rifiuta a seconda che il testo passato 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

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

Origine

write()

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

Per scrivere un'immagine negli appunti, devi avere l'immagine come blob. Un modo per farlo richiedendo l'immagine a un server utilizzando fetch(), quindi chiamando blob() il risposta.

La richiesta di un'immagine dal server potrebbe non essere auspicabile o possibile per una serie di motivi. Fortunatamente, puoi anche disegnare l'immagine su un canvas e chiama la tela" toBlob() .

Quindi, passa un array di oggetti ClipboardItem come parametro a write() . Al momento puoi passare 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 per 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 in anticipo 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

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

Origine

L'evento di copia

Nel caso in cui un utente avvii una copia degli appunti e non chiama preventDefault(), Evento copy include una proprietà clipboardData con gli elementi già nel formato corretto. Se vuoi implementare la tua logica, devi chiamare preventDefault() a 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 li seleziona tutti e avvia una copia degli appunti, la soluzione personalizzata dovrebbe ignorare il testo e e copio l'immagine. Puoi farlo come mostrato nell'esempio di codice di seguito. Ciò che non viene trattato in questo esempio è come tornare API 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

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

Origine

Per ClipboardItem:

Supporto dei browser

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

Origine

Incolla: lettura dei dati degli appunti

readText()

Per leggere il testo negli appunti, chiama navigator.clipboard.readText() e attendi per risolvere 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

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

Origine

leggi()

Il metodo navigator.clipboard.read() è anche asincrono e restituisce una promessa. Per leggere un'immagine dagli appunti, recupera un elenco di ClipboardItem ed eseguire l'iterazione su di essi.

Ogni ClipboardItem può contenere contenuti di tipi diversi, quindi dovrai Esegui l'iterazione dell'elenco dei tipi, sempre utilizzando un loop for...of. Per ogni tipo, chiama il metodo getType() con il tipo corrente come argomento per ottenere blob corrispondente. Come in precedenza, questo codice non è legato 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

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

Origine

Lavorare con i file incollati

È utile per gli utenti poter utilizzare le scorciatoie da tastiera per gli appunti, come Ctrl+C e Ctrl+V. Chromium espone i file sola lettura negli appunti come descritto di seguito. Questo viene attivato quando l'utente preme la scorciatoia per incollare predefinita del sistema operativo o quando fa clic su Modifica e poi su Incolla nella barra dei menu del browser. Non sono necessari ulteriori codici idraulico.

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

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

Origine

L'evento Incolla

Come indicato in precedenza, è previsto l'introduzione di eventi che funzionino con l'API Clipboard, ma per il momento puoi utilizzare l'evento paste esistente. Funziona bene con i nuovi metodi asincroni per la lettura del testo negli appunti. Come per l'evento copy, non dimentica di chiamare preventDefault().

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

Supporto dei browser

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

Origine

Gestione di più tipi MIME

La maggior parte delle implementazioni inserisce più formati di dati nella clipboard per una singola operazione di taglio o copia. I motivi sono due: in qualità di sviluppatore di app, devi conoscere le funzionalità dell'app in cui un utente vuole copiare testo o immagini, e molte applicazioni supportano l'operazione di incollare dati strutturati come testo normale. In genere si tratta di una situazione presentati agli utenti con una voce di menu Modifica con un nome come Incolla e stile di corrispondenza o Incolla senza formattazione.

L'esempio seguente mostra come eseguire questa operazione. Questo esempio utilizza fetch() per ottenere ma potrebbe anche provenire da un <canvas> o 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]);
}

Sicurezza e autorizzazioni

L'accesso agli appunti ha sempre rappresentato un problema di sicurezza per i browser. Senza le autorizzazioni appropriate, una pagina potrebbe copiare silenziosamente nel portadocumenti di un utente ogni tipo di contenuti dannosi che produrrebbero risultati catastrofici al momento del loro incollaggio. Immagina una pagina web che copia silenziosamente rm -rf / o un'immagine bomba di decompressione negli appunti.

Richiesta del browser che chiede all&#39;utente l&#39;autorizzazione per gli appunti.
Il prompt di autorizzazione per l'API Clipboard.

Fornire alle pagine web l'accesso in lettura senza limitazioni agli appunti è ancora più fastidioso. Gli utenti copiano di routine informazioni sensibili come password e dettagli personali negli appunti, che possono essere letti da qualsiasi pagina a loro insaputa.

Come avviene con molte nuove API, l'API Clipboard è supportata soltanto per le pagine pubblicate su HTTPS. Per prevenire comportamenti illeciti, l'accesso agli appunti è consentito solo quando una pagina viene la scheda attiva. Le pagine nelle schede attive possono scrivere negli appunti senza richiedere l'autorizzazione, ma per leggere dagli appunti è sempre necessario autorizzazione.

Le autorizzazioni per copia e incolla sono state aggiunte all'API Permissions. L'autorizzazione clipboard-write viene concessa automaticamente alle pagine quando sono la scheda attiva. L'autorizzazione clipboard-read deve essere richiesta. Puoi farlo tentando di 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 attivare il taglio o la colla utilizzando l'opzione allowWithoutGesture. Il valore predefinito per questo valore varia a seconda del browser, quindi dovresti sempre includerlo.

È qui che la natura asincrona dell'API Clipboard è davvero utile: il tentativo di leggere o scrivere i dati degli appunti richiede automaticamente all'utente l'autorizzazione, se non è già stata concessa. Poiché l'API è basata sulle promesse, è completamente trasparente. Se un utente nega l'autorizzazione agli appunti, la promessa di rifiutare 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, puoi notare che alcuni degli esempi riportati qui non vengono eseguiti se sono stati incollati direttamente console del browser, poiché gli strumenti per sviluppatori sono la scheda attiva. C'è un trucco: rimandare accesso agli appunti utilizzando setTimeout(), quindi fai clic rapidamente all'interno della pagina per prima che le funzioni vengano chiamate:

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

Integrazione dei criteri di autorizzazione

Per utilizzare l'API in iframe, devi abilitarla con Criteri relativi alle autorizzazioni, che definisce un meccanismo che consente di abilitare selettivamente la disattivazione di varie funzionalità e API del browser. Nello specifico, devi passare uno o entrambi i valori clipboard-read o clipboard-write, a seconda delle esigenze della tua app.

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

Rilevamento di funzionalità

Per utilizzare l'API Async Clipboard supportando tutti i browser, verifica la presenza dinavigator.clipboard e utilizza i metodi precedenti. Ad esempio, ecco come potresti implementare l'opzione 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);
});

Ma non è tutto. Prima dell'API Async Clipboard, esisteva un insieme di implementazioni diverse di copia e incolla 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 qui sotto. Su Glitch you possono fare il remix della demo testuale o l'immagine demo per fare esperimenti con loro.

Il primo esempio mostra lo spostamento del testo dentro e fuori gli appunti.

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

Ringraziamenti

L'API Asynchronous Clipboard è stata implementata da Darwin Huang e Gary Kačmarčík. Darwin ha anche fornito la demo. Grazie a Kyarik e ancora a Gary Kačmarčík per aver esaminato parti di questo articolo.

Immagine hero di Markus Winkler su Rimuovi schermo.