Odblokowuję dostęp do schowka

Bezpieczniejszy i odblokowany dostęp do schowka na tekst i obrazy

Tradycyjny sposób na uzyskiwanie dostępu do schowka systemowego odbywał się przez document.execCommand() do interakcji ze schowkiem. Choć ta metoda cięcia, wklejanie było płatne: dostęp do schowka był synchroniczny i możliwy był tylko odczyt i zapisz w DOM.

Jest to dobre rozwiązanie w przypadku małych fragmentów tekstu, ale w wielu przypadkach może zostać zablokowane przenoszenia schowka jest niesatysfakcjonująca. Czasochłonne dezynfekcję lub Do bezpiecznego wklejenia treści może być konieczne dekodowanie obrazu. Przeglądarka może wymagać wczytania lub wbudowania linków z wklejonego dokumentu. To byłaby zablokuj stronę podczas oczekiwania na dysk lub sieć. Wyobraź sobie, że dodajesz uprawnienia wymagający, by przeglądarka blokowała stronę w trakcie wysyłania żądania dostępu do schowka. Jednocześnie uprawnienia są stosowane wokół Funkcje document.execCommand() w przypadku interakcji ze schowek są luźno zdefiniowane i mogą się różnić między przeglądarkami.

Async Clipboard API pozwala rozwiązać te problemy, zapewniając jasno zdefiniowany model uprawnień, który nie zablokować stronę. Interfejs Async Clipboard API jest ograniczony do obsługi tekstu i obrazów w większości przeglądarek, ale ich obsługa jest różna. Uważnie sprawdź działanie przeglądarki ze zgodnością w każdej z poniższych sekcji.

Kopiuj: zapisywanie danych do schowka

writeText()

Aby skopiować tekst do schowka, wywołaj writeText(). Ten interfejs API jest asynchroniczna, funkcja writeText() zwraca obietnicę, która realizuje lub odrzuca w zależności od tego, czy przekazywany tekst został skopiowany:

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

Obsługa przeglądarek

  • 66
  • 79
  • 63
  • 13.1

Źródło

write()

writeText() to tylko udogodnienie dla ogólnych reguł write(). która pozwala skopiować obrazy do schowka. Podobnie jak writeText(), jest asynchroniczna i zwraca obietnicę.

Aby zapisać obraz w schowku, musisz go używać jako pliku blob Jednym ze sposobów jest to żądanie grafiki od serwera za pomocą polecenia fetch(), a następnie wywołanie blob() w: .

Żądanie grafiki od serwera może być niepożądane lub niemożliwe w przypadku z wielu różnych przyczyn. Na szczęście możesz to również narysować na płótnie, wywołaj płótno toBlob() .

Następnie przekaż tablicę obiektów ClipboardItem jako parametr do funkcji write() . Obecnie można przekazać tylko jeden obraz naraz, ale mamy nadzieję dodać dla wielu obrazów. ClipboardItem pobiera obiekt z typu MIME obrazu jako klucza i obiektu blob jako wartości. Do bloba obiekty uzyskane z metod fetch() lub canvas.toBlob(), właściwość blob.type automatycznie zawiera poprawny typ MIME obrazu.

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

Możesz też napisać obietnicę do obiektu ClipboardItem. W przypadku tego wzorca musisz wcześniej znać typ MIME danych.

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

Obsługa przeglądarek

  • 66
  • 79
  • 127
  • 13.1

Źródło

Zdarzenie kopiowania

W przypadku, gdy użytkownik zainicjuje kopiowanie ze schowka i nie wywołuje funkcji preventDefault(), parametr copy zdarzenie zawiera właściwość clipboardData z elementami w odpowiednim formacie. Jeśli chcesz wdrożyć własną logikę, musisz wywołać metodę preventDefault(), aby zapobiegać domyślnemu działaniu na korzyść Twojej implementacji. W tym przypadku pole clipboardData jest puste. Załóżmy, że mamy stronę z tekstem i obrazem, a użytkownik zaznacza wszystkie zainicjuje kopię schowka, niestandardowe rozwiązanie powinno odrzucać tekst i tylko skopiować obraz. Możesz to osiągnąć w sposób podany w przykładowym poniżej kodzie. W tym przykładzie nie omówiono sposobu powrotu do wcześniejszej wersji. Interfejsy API, gdy interfejs Clipboard API nie jest obsługiwany.

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

W przypadku wydarzenia copy:

Obsługa przeglądarek

  • 1
  • 12
  • 22
  • 3

Źródło

Dla usługi ClipboardItem:

Obsługa przeglądarek

  • 76
  • 79
  • 127
  • 13.1

Źródło

Wklej: odczytywanie danych ze schowka

readText()

Aby odczytać tekst ze schowka, zadzwoń pod numer navigator.clipboard.readText() i zaczekaj dla zwróconej obietnicy do rozwiązania:

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

Obsługa przeglądarek

  • 66
  • 79
  • 125
  • 13.1

Źródło

read()

Metoda navigator.clipboard.read() jest również asynchroniczna i zwraca lub obiecywanie. Aby odczytać obraz ze schowka, pobierz listę ClipboardItem a potem iteracje.

Każdy plik ClipboardItem może przechowywać różne typy treści, dlatego musisz wykonać iterację na liście typów, ponownie za pomocą pętli for...of. W przypadku każdego typu wywołaj metodę getType() z bieżącym typem jako argumentem, aby uzyskać odpowiedni obiekt blob. Tak jak wcześniej, ten kod nie jest powiązany z obrazami i będzie z innymi typami plików, które pojawią się w przyszłości.

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

Obsługa przeglądarek

  • 66
  • 79
  • 127
  • 13.1

Źródło

Praca z wklejonymi plikami

Użytkownicy mogą korzystać ze skrótów klawiszowych, takich jak Ctrl + C i Ctrl + V. Chromium ujawnia w schowku pliki tylko do odczytu, jak opisano poniżej. To się uruchamia, gdy użytkownik kliknie domyślny skrót wklejania systemu operacyjnego. lub gdy użytkownik kliknie Edytuj, a następnie Wklej na pasku menu przeglądarki. Nie jest potrzebny dodatkowy kod instalacji.

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

Obsługa przeglądarek

  • 3
  • 12
  • 3.6
  • 4

Źródło

Zdarzenie wklejania

Jak już wspomnieliśmy, planujemy wprowadzić wydarzenia do współpracy z interfejsem Clipboard API, ale na razie możesz korzystać z istniejącego zdarzenia paste. Dobrze współpracuje z nowym narzędziem asynchronicznych metod odczytywania tekstu ze schowka. Tak jak w przypadku zdarzenia copy, nie Zapomnij o telefonie preventDefault().

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

Obsługa przeglądarek

  • 1
  • 12
  • 22
  • 3

Źródło

Obsługa wielu typów MIME

Większość implementacji umieszcza w schowku różne formaty danych, aby je zapisać w jednym cięciu. lub kopiowania. Są 2 powody. Jako deweloper nie ma możliwości sprawdzenia możliwości aplikacji, do której użytkownik chce skopiować tekst lub obrazy a wiele aplikacji obsługuje wklejanie uporządkowanych danych jako zwykły tekst. Zwykle jest to prezentowane użytkownikom z opcją Edytuj o nazwie, np. Wklej i styl dopasowania lub Wklej bez formatowania.

Z przykładu poniżej dowiesz się, jak to zrobić. W tym przykładzie użyto funkcji fetch(), aby uzyskać ale mogą też pochodzić z <canvas> lub File System Access API.

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

Bezpieczeństwo i uprawnienia

Dostęp do schowka zawsze stanowił zagrożenie dla przeglądarek. Bez odpowiednich uprawnień, strona może dyskretnie skopiować wszelkie szkodliwe treści do schowka użytkownika, co po wklejeniu może mieć katastrofalne skutki. Wyobraź sobie stronę internetową, która dyskretnie kopiuje treści rm -rf / lub obraz bomby dekompresyjnej do schowka.

Prośba przeglądarki o przyznanie uprawnień dostępu do schowka.
Prośba o uprawnienia dla interfejsu Clipboard API.

Zapewnianie stronom internetowym nieograniczony dostęp do odczytu do schowka to jeszcze więcej kłopotliwe. Użytkownicy regularnie kopiują informacje poufne, takie jak hasła osobiste dane do schowka, które mogą być odczytywane przez każdą stronę bez dzięki wiedzy użytkownika.

Podobnie jak w przypadku wielu nowych interfejsów API, Clipboard API jest obsługiwany tylko w przypadku stron wyświetlanych przez HTTPS. Aby zapobiec nadużyciom, dostęp do schowka jest dozwolony tylko wtedy, gdy strona jest aktywnej karty. Strony na aktywnych kartach mogą zapisywać w schowku bez konieczności prosi o uprawnienia, ale odczyt ze schowka zawsze wymaga uprawnienia.

Uprawnienia do kopiowania i wklejania zostały dodane do sekcji Permissions API. Uprawnienie clipboard-write jest przyznawane automatycznie stronom, gdy są aktywnej karty. Musisz poprosić o uprawnienie clipboard-read, które możesz , próbując odczytać dane ze schowka. Poniższy kod pokazuje to ostatnie:

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

Możesz też określić, czy do wywołania wycięcia lub wywołania wymagany jest gest użytkownika do wklejania za pomocą opcji allowWithoutGesture. Domyślna wartość dla tej wartości zależy od przeglądarki, więc należy go zawsze dodawać.

Tutaj przydaje się asynchroniczna natura interfejsu Clipboard API: próba odczytania lub zapisania danych ze schowka automatycznie wyświetla użytkownikowi , jeśli nie zostało jeszcze przyznane. Ponieważ interfejs API opiera się na obietnicach, jest to całkowicie przejrzyste, a użytkownik odmówił przyznania dostępu do schowka obietnicę odrzucenia, aby strona mogła odpowiednio zareagować.

Przeglądarki zezwalają na dostęp do schowka tylko wtedy, gdy strona jest aktywną kartą, niektóre z tych przykładów nie działają po wklejeniu konsoli przeglądarki, ponieważ narzędzia dla programistów są aktywną kartą. Jeszcze rada: odłóż dostęp do schowka za pomocą funkcji setTimeout(), a następnie szybko kliknij wewnątrz strony, aby zaznacz go przed wywołaniem funkcji:

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

Integracja zasad dotyczących uprawnień

Aby używać interfejsu API w elementach iframe, musisz go włączyć za pomocą Zasady dotyczące uprawnień, definiujący mechanizm, który umożliwia selektywne włączanie wyłączenie różnych funkcji i interfejsów API w przeglądarkach. Konkretnie chodzi o to, by zaliczyć clipboard-read lub clipboard-write, w zależności od potrzeb aplikacji.

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

Wykrywanie cech

Aby korzystać z interfejsu Async Clipboard API i jednocześnie obsługiwać wszystkie przeglądarki, sprawdź navigator.clipboard i wróć do wcześniejszych metod. Aby na przykład można zastosować wklejanie, aby uwzględnić inne przeglądarki.

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

To nie wszystko. Przed wprowadzeniem interfejsu Async Clipboard API dostępne były sposób kopiowania i wklejania w różnych przeglądarkach. W większości przeglądarek można uruchomić kopiowanie i wklejanie przy użyciu funkcji document.execCommand('copy') i document.execCommand('paste'). Jeśli tekst to ciąg znaków, którego nie ma w DOM, musi zostać wstrzyknięty do DOM i wybrane:

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

Prezentacje

Z interfejsu Async Clipboard API możesz poeksperymentować w poniższych wersjach demonstracyjnych. W skrócie Może zremiksować wersję demonstracyjną tekstu lub prezentację obrazu, z nimi eksperymentować.

Pierwszy przykład pokazuje przenoszenie tekstu ze schowka i z niego.

Aby wypróbować interfejs API z obrazami, skorzystaj z tej wersji demonstracyjnej. Pamiętaj, że obsługiwany jest tylko format PNG i tylko w kilku przeglądarek.

Podziękowania

Interfejs Asynchroniczny Clipboard API został wdrożony przez Darwina Huang i Gary Kačmarčík. Darwin również zaprezentował prezentację. Podziękowania dla Kyarika i jeszcze raz Gary'ego Kačmarčíka za zapoznaj się z innymi częściami tego artykułu.

Baner powitalny projektu Markusa Winklera w Odchylenie.