Sblocco dell'accesso agli appunti

Accesso alla clipboard più sicuro e sbloccato 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.

Va bene per piccoli pezzi di testo, ma in molti casi il blocco della pagina per il trasferimento negli appunti è un'esperienza negativa. Prima di poter incollare i contenuti in tutta sicurezza, potrebbe essere necessaria una procedura di disinfezione o decodifica delle immagini che richiede molto tempo. Il browser potrebbe dover caricare o inserire in linea le risorse collegate da un documento incollato. In questo modo la pagina viene bloccata mentre si attende sul disco o sulla rete. Immagina di aggiungere autorizzazioni nel mix, richiedendo che il browser blocchi la pagina mentre richiedi l'accesso agli appunti. Allo stesso tempo, le autorizzazioni implementate per document.execCommand() per l'interazione con la clipboard sono definite in modo vago e variano in base al 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. Esamina attentamente la panoramica della compatibilità del browser per ognuna delle sezioni seguenti.

Copia: scrittura dei 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 è richiedere l'immagine da un server utilizzando fetch(), quindi chiamare blob() sulla risposta.

La richiesta di un'immagine al server potrebbe non essere desiderabile o possibile per una serie di motivi. Fortunatamente, puoi anche disegnare l'immagine in 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 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 nell'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

Se un utente avvia una copia negli appunti e non chiama preventDefault(), l'evento copy include una proprietà clipboardData con gli elementi già nel formato corretto. Se vuoi implementare la tua logica, devi chiamare preventDefault() per impedire il comportamento predefinito a favore della tua implementazione. In questo caso, clipboardData sarà vuoto. Considera una pagina con testo e un'immagine. Quando l'utente seleziona tutto e avvia una copia negli appunti, la soluzione personalizzata deve ignorare il testo e copiare solo l'immagine. Puoi farlo come mostrato nell'esempio di codice di seguito. Questo esempio non illustra come eseguire il fallback 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

  • 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 dagli appunti, chiama navigator.clipboard.readText() e attendi che la promessa restituita venga risolta:

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

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

Ogni ClipboardItem può contenere i propri contenuti in tipi diversi, quindi dovrai eseguire un loop sull'elenco di tipi, sempre utilizzando un loop for...of. Per ogni tipo, chiama il metodo getType() con il tipo corrente come argomento per ottenere il 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 è necessario alcun altro codice di cablaggio.

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 di incollaggio

Come indicato in precedenza, sono in programma l'introduzione di eventi da utilizzare 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 della clipboard. 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

  • 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, non hai modo di conoscere le funzionalità dell'app in cui un utente vuole copiare testo o immagini e molte applicazioni supportano l'incollamento di dati strutturati come testo normale. In genere, viene presentata agli utenti come un'opzione del menu Modifica con un nome come Incolla e adatta lo stile o Incolla senza formattazione.

Il seguente esempio mostra come eseguire questa operazione. Questo esempio utilizza fetch() per ottenere i dati delle immagini, ma potrebbe anche provenire da un <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 alla clipboard ha sempre rappresentato un problema di sicurezza per i browser. Senza le autorizzazioni appropriate, una pagina potrebbe copiare in modo invisibile tutti i tipi di contenuti dannosi negli appunti di un utente e, una volta incollata, produrrebbe risultati catastrofici. 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 di accesso agli appunti.
La richiesta di autorizzazione per l'API Clipboard.

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

Come avviene con molte nuove API, l'API Clipboard è supportata solo per le pagine pubblicate tramite HTTPS. Per prevenire comportamenti illeciti, 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 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&#39;API si basa su promesse, tutto è completamente trasparente e se un utente nega l&#39;autorizzazione di accesso alla clipboard, la promessa viene rifiutata in modo che la pagina possa rispondere di conseguenza.

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

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

Integrazione dei criteri di autorizzazione

Per utilizzare l'API negli iframe, devi attivarla con le norme relative alle autorizzazioni, che definiscono un meccanismo che consente di attivare e disattivare in modo selettivo varie funzionalità e API del browser. Di fatto, devi superare 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'inserimento 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 provare l'API Async Clipboard nelle demo riportate di seguito. Su Glitch puoi remixare la demo di testo o la demo di immagini per sperimentarle.

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 fornito anche 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 Unsplash.