Pano erişiminin engeli kaldırılıyor

Metin ve resimler için daha güvenli, engellenmemiş pano erişimi

Sistem panosuna erişmenin geleneksel yolu, pano etkileşimleri için document.execCommand() üzerinden sağlanıyordu. Bu kesme ve yapıştırma yöntemi yaygın olarak desteklense de bir maliyeti vardı: Pano erişimi eşzamanlıydı ve yalnızca DOM'a okuma ve yazma işlemi yapabiliyordu.

Bu, küçük metin parçaları için uygundur ancak panoya aktarım için sayfanın engellenmesinin kötü bir deneyim olduğu birçok durum vardır. İçeriğin güvenli bir şekilde yapıştırılabilmesi için zaman alan temizleme veya görüntü kod çözme işlemi gerekebilir. Tarayıcının, yapıştırılan bir belgedeki bağlantılı kaynakları yüklemesi veya satır içi olarak eklemesi gerekebilir. Bu durumda, disk veya ağ beklenirken sayfa engellenir. İzinlerin de işin içine girdiğini ve tarayıcının, pano erişimi isterken sayfayı engellemesi gerektiğini düşünün. Aynı zamanda, document.execCommand() ile ilgili olarak panoyla etkileşim için uygulanan izinler net bir şekilde tanımlanmamıştır ve tarayıcılar arasında farklılık gösterir.

Async Clipboard API, sayfayı engellemeyen iyi tanımlanmış bir izin modeli sağlayarak bu sorunları giderir. Async Clipboard API, çoğu tarayıcıda metin ve resimlerin işlenmesiyle sınırlıdır ancak destek değişebilir. Aşağıdaki bölümlerin her birinde tarayıcı uyumluluğuyla ilgili genel bilgileri dikkatlice inceleyin.

Kopyalama: Verileri panoya yazma

writeText()

Metni panoya kopyalamak için writeText() işlevini çağırın. Bu API eşzamansız olduğundan writeText() işlevi, iletilen metnin başarıyla kopyalanıp kopyalanmadığına bağlı olarak çözümlenen veya reddedilen bir Promise döndürür:

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

Browser Support

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

Source

write()

Aslında writeText(), resimleri panoya kopyalamanıza da olanak tanıyan genel write() yöntemi için yalnızca bir kolaylık yöntemidir. writeText() gibi, eşzamansızdır ve Promise döndürür.

Resmi panoya yazmak için resmin blob olarak kaydedilmiş olması gerekir. Bunu yapmanın bir yolu, fetch() kullanarak sunucudan resmi istemek ve ardından yanıtta blob() yöntemini çağırmaktır.

Sunucudan resim istemek çeşitli nedenlerle istenmeyebilir veya mümkün olmayabilir. Neyse ki resmi tuvale de çizebilir ve tuvalin toBlob() yöntemini çağırabilirsiniz.

Ardından, ClipboardItem yöntemine parametre olarak bir write() nesne dizisi iletin. Şu anda tek seferde yalnızca bir resim aktarabilirsiniz ancak gelecekte birden fazla resim aktarma desteği eklemeyi planlıyoruz. ClipboardItem, anahtar olarak resmin MIME türüne sahip bir nesneyi, değer olarak da blob'u alır. fetch() veya canvas.toBlob() kaynaklı blob nesneleri için blob.type özelliği, resim için doğru MIME türünü otomatik olarak içerir.

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

Alternatif olarak, ClipboardItem nesnesine bir söz yazabilirsiniz. Bu kalıbı kullanmak için verilerin MIME türünü önceden bilmeniz gerekir.

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

Browser Support

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

Source

Kopyalama etkinliği

Bir kullanıcının pano kopyalama işlemi başlattığı ve preventDefault() işlevini çağırmadığı durumlarda, copy etkinliği, doğru biçimde olan öğeleri içeren bir clipboardData özelliği içerir. Kendi mantığınızı uygulamak istiyorsanız kendi uygulamanız lehine varsayılan davranışı önlemek için preventDefault() işlevini çağırmanız gerekir. Bu durumda clipboardData boş olur. Metin ve resim içeren bir sayfada, kullanıcı tümünü seçip pano kopyalama işlemini başlattığında özel çözümünüz metni silmeli ve yalnızca resmi kopyalamalıdır. Bunu aşağıdaki kod örneğinde gösterildiği gibi yapabilirsiniz. Bu örnekte, Pano API'si desteklenmediğinde önceki API'lere nasıl geri dönüleceği açıklanmamıştır.

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

copy etkinliği için:

Browser Support

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

Source

ClipboardItem için:

Browser Support

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

Source

Yapıştırma: Panodaki verileri okuma

readText()

Panodaki metni okumak için navigator.clipboard.readText() işlevini çağırın ve döndürülen sözün çözülmesini bekleyin:

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

Browser Support

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

Source

read()

navigator.clipboard.read() yöntemi de eşzamansızdır ve bir söz döndürür. Panodan bir resmi okumak için ClipboardItem nesne listesini alın ve bunları yineleyin.

Her ClipboardItem, içeriklerini farklı türlerde tutabilir. Bu nedenle, türler listesini yine bir for...of döngüsü kullanarak yinelemeniz gerekir. Her tür için, getType() yöntemini geçerli türü bağımsız değişken olarak kullanarak çağırıp ilgili blob'u alın. Bu kod, daha önce olduğu gibi resimlerle bağlantılı değildir ve gelecekteki diğer dosya türleriyle de çalışır.

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

Browser Support

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

Source

Yapıştırılan dosyalarla çalışma

Kullanıcıların ctrl+c ve ctrl+v gibi pano klavye kısayollarını kullanabilmesi faydalıdır. Chromium, panodaki salt okunur dosyaları aşağıda belirtildiği şekilde gösterir. Bu, kullanıcı işletim sisteminin varsayılan yapıştırma kısayoluna bastığında veya tarayıcının menü çubuğunda Düzenle'yi, ardından Yapıştır'ı tıkladığında tetiklenir. Başka bir tesisat kodu gerekmez.

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

Browser Support

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

Source

Yapıştırma etkinliği

Daha önce belirtildiği gibi, Pano API'siyle çalışacak etkinlikler sunma planları var ancak şimdilik mevcut paste etkinliğini kullanabilirsiniz. Bu özellik, pano metnini okumak için kullanılan yeni eşzamansız yöntemlerle sorunsuz bir şekilde çalışır. copy etkinliğinde olduğu gibi preventDefault() işlevini çağırmayı unutmayın.

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

Browser Support

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

Source

Birden fazla MIME türünü işleme

Çoğu uygulama, tek bir kesme veya kopyalama işlemi için panoya birden fazla veri biçimi yerleştirir. Bunun iki nedeni vardır: Uygulama geliştirici olarak, kullanıcının metin veya resim kopyalamak istediği uygulamanın özelliklerini bilemezsiniz ve birçok uygulama, yapılandırılmış verilerin düz metin olarak yapıştırılmasını destekler. Bu genellikle kullanıcılara Düzenle menü öğesiyle birlikte Biçimi yapıştır ve eşleştir veya Biçimlendirme olmadan yapıştır gibi bir adla sunulur.

Bunu nasıl yapacağınız aşağıdaki örnekte gösterilmektedir. Bu örnekte, resim verilerini almak için fetch() kullanılır ancak bu veriler <canvas> veya File System Access API'den de gelebilir.

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

Güvenlik ve izinler

Pano erişimi, tarayıcılar için her zaman bir güvenlik sorunu olmuştur. Gerekli izinler olmadan bir sayfa, her türlü kötü amaçlı içeriği kullanıcının panosuna sessizce kopyalayabilir. Bu içerik, yapıştırıldığında felaketle sonuçlanabilir. rm -rf / veya sıkıştırmayı açma bombası resmini panonuza sessizce kopyalayan bir web sayfası olduğunu düşünün.

Tarayıcı, kullanıcıdan pano izni ister.
Pano API'si için izin istemi.

Web sayfalarına panoya sınırsız okuma erişimi vermek daha da sorunludur. Kullanıcılar, şifreler ve kişisel bilgiler gibi hassas bilgileri düzenli olarak panoya kopyalar. Bu bilgiler daha sonra kullanıcının bilgisi olmadan herhangi bir sayfa tarafından okunabilir.

Birçok yeni API'de olduğu gibi, Pano API'si yalnızca HTTPS üzerinden sunulan sayfalar için desteklenir. Kötüye kullanımı önlemek için pano erişimine yalnızca bir sayfa etkin sekme olduğunda izin verilir. Etkin sekmelerdeki sayfalar, izin istemeden panoya yazabilir ancak panodan okuma işlemi için her zaman izin gerekir.

Kopyalama ve yapıştırma izinleri Permissions API'ye eklendi. clipboard-write izni, etkin sekme olduklarında sayfalara otomatik olarak verilir. clipboard-read izni istenmelidir. Bu izni, panodan veri okumaya çalışarak alabilirsiniz. Aşağıdaki kodda ikincisi gösterilmektedir:

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

Ayrıca, allowWithoutGesture seçeneğini kullanarak kesme veya yapıştırma işlemlerini başlatmak için kullanıcı hareketi gerekip gerekmediğini de kontrol edebilirsiniz. Bu değerin varsayılanı tarayıcıya göre değişir. Bu nedenle, değeri her zaman eklemeniz gerekir.

Pano API'sinin eşzamansız yapısı şu durumlarda gerçekten işe yarar: Pano verilerini okuma veya yazma girişimi, izin verilmemişse kullanıcıdan otomatik olarak izin ister. API, söz tabanlı olduğundan bu işlem tamamen şeffaftır. Kullanıcı pano iznini reddettiğinde söz reddedilir, böylece sayfa uygun şekilde yanıt verebilir.

Tarayıcılar yalnızca bir sayfa etkin sekme olduğunda pano erişimine izin verdiğinden, geliştirici araçları etkin sekme olduğundan buradaki örneklerden bazılarının doğrudan tarayıcının konsoluna yapıştırıldığında çalışmadığını göreceksiniz. Bir püf nokta var: setTimeout() kullanarak pano erişimini erteleyin, ardından işlevler çağrılmadan önce sayfaya odaklanmak için hızlıca sayfanın içine tıklayın:

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

İzin politikası entegrasyonu

API'yi iFrame'lerde kullanmak için Permissions Policy ile etkinleştirmeniz gerekir. Bu politika, çeşitli tarayıcı özelliklerinin ve API'lerinin seçmeli olarak etkinleştirilmesine ve devre dışı bırakılmasına olanak tanıyan bir mekanizma tanımlar. Uygulamanızın ihtiyaçlarına bağlı olarak clipboard-read veya clipboard-write ya da her ikisini de iletmeniz gerekir.

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

Özellik algılama

Tüm tarayıcıları desteklerken Async Clipboard API'yi kullanmak için navigator.clipboard testini yapın ve önceki yöntemlere geri dönün. Örneğin, diğer tarayıcıları da dahil etmek için yapıştırma özelliğini şu şekilde uygulayabilirsiniz.

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

Ancak bu, hikayenin tamamı değil. Async Clipboard API'den önce, web tarayıcılarında farklı kopyalama ve yapıştırma uygulamaları vardı. Çoğu tarayıcıda, tarayıcının kendi kopyalama ve yapıştırma işlevi document.execCommand('copy') ve document.execCommand('paste') kullanılarak tetiklenebilir. Kopyalanacak metin, DOM'da bulunmayan bir dizeyse DOM'a yerleştirilip seçilmelidir:

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

Demolar

Aşağıdaki demolarla Async Clipboard API'yi deneyebilirsiniz. İlk örnekte, metnin panoya taşınması ve panodan kaldırılması gösterilmektedir.

API'yi resimlerle denemek için bu demoyu kullanın. Yalnızca PNG'lerin ve yalnızca birkaç tarayıcıda desteklendiğini unutmayın.

Teşekkür

Asynchronous Clipboard API, Darwin Huang ve Gary Kačmarčík tarafından uygulanmıştır. Darwin da demoyu sağladı. Bu makalenin bazı bölümlerini inceleyen Kyarik'e ve Gary Kačmarčík'e tekrar teşekkür ederiz.

Markus Winkler'ın Unsplash'teki hero resmi.