클립보드 액세스 차단 해제

텍스트 및 이미지에 대한 더 안전하고 차단되지 않은 클립보드 액세스

기존에는 클립보드 상호작용을 위해 document.execCommand()를 통해 시스템 클립보드에 액세스했습니다. 이 절단 및 자르기 방법은 널리 지원되지만 붙여넣기로 인해 비용이 발생함: 클립보드 액세스가 동기식이었으며 DOM에 쓸 수 있습니다.

텍스트가 적은 경우에는 괜찮지만 클립보드 전송을 위해 페이지를 차단하는 것이 좋지 않은 환경인 경우가 많습니다. 시간이 오래 걸리는 정리 또는 콘텐츠를 안전하게 붙여넣으려면 이미지 디코딩이 필요할 수 있습니다. 브라우저 붙여넣은 문서에서 연결된 리소스를 로드하거나 인라인해야 할 수 있습니다. 그러면 디스크나 네트워크를 기다리는 동안 페이지가 차단됩니다. 권한 추가 상상하기 요청할 때 브라우저가 해당 페이지를 차단하도록 클립보드 액세스 동시에 클립보드 상호작용을 위해 document.execCommand() 주위에 설정된 권한은 느슨하게 정의되어 있으며 브라우저마다 다릅니다.

비동기 클립보드 API 잘 정의된 권한 모델을 제공하여 이러한 문제를 해결할 수 있습니다. 차단할 수 있습니다. Async Clipboard API는 텍스트와 이미지만 처리하도록 제한됩니다. 대부분의 브라우저에서 지원되지만, 지원되는 유형은 다를 수 있습니다. 다음 섹션별로 브라우저 호환성 개요를 주의 깊게 검토하세요.

복사: 클립보드에 데이터 쓰기

writeText()

텍스트를 클립보드에 복사하려면 writeText()를 호출합니다. 이 API는 비동기식이면 writeText() 함수는 전달된 텍스트가 복사되었는지 여부에 따라 거부됩니다.

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

브라우저 지원

  • Chrome: 66
  • Edge: 79. <ph type="x-smartling-placeholder">
  • Firefox: 63 <ph type="x-smartling-placeholder">
  • Safari 13.1.

소스

write()

실제로 writeText()는 일반 write()의 편의 메서드일 뿐입니다. 메서드를 사용하면 이미지를 클립보드에 복사할 수도 있습니다. writeText()와 마찬가지로 비동기식이며 Promise를 반환합니다.

클립보드에 이미지를 쓰려면 이미지가 blob여야 합니다. 이를 위한 한 가지 방법은 fetch()를 사용하여 서버에 이미지를 요청한 다음 blob() 있습니다.

서버에서 이미지를 요청하는 것이 바람직하지 않거나 불가능한 경우가 여러 가지 있습니다. 다행히 이미지를 캔버스에 그리고 캔버스의 toBlob() 메서드를 호출할 수도 있습니다.

다음으로 ClipboardItem 객체 배열을 write() 메서드에 매개변수로 전달합니다. 현재는 한 번에 하나의 이미지만 전달할 수 있지만 향후 여러 이미지를 지원할 예정입니다. ClipboardItem는 이미지의 MIME 유형을 키로, blob을 값으로 지정합니다. blob용 fetch() 또는 canvas.toBlob()에서 가져온 객체(blob.type 속성) 이미지의 올바른 MIME 유형을 자동으로 포함합니다.

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

또는 ClipboardItem 객체에 프로미스를 작성할 수 있습니다. 이 패턴의 경우 데이터의 MIME 유형을 미리 알아야 합니다.

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

브라우저 지원

  • Chrome: 76
  • Edge: 79 <ph type="x-smartling-placeholder">
  • Firefox: 127
  • Safari: 13.1. <ph type="x-smartling-placeholder">

소스

사본 이벤트

사용자가 클립보드 복사를 시작하는 경우 preventDefault()를 호출하지 않는 경우 일정 copy 에는 항목이 이미 올바른 형식으로 되어 있는 clipboardData 속성이 포함됩니다. 자체 로직을 구현하려면 preventDefault()를 호출하여 기본 동작이 실행되지 않도록 하고 자체 구현을 사용해야 합니다. 이 경우 clipboardData는 비어 있습니다. 텍스트와 이미지가 있는 페이지를 생각해 보세요. 사용자가 모두 선택하고 클립보드 복사를 시작하면 맞춤 솔루션은 텍스트를 삭제하고 이미지만 복사해야 합니다. 아래 코드 샘플과 같이 이를 실행할 수 있습니다. 이 예에서는 클립보드 API가 지원되지 않을 때 이전 API로 대체하는 방법을 다루지 않습니다.

<!-- 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 이벤트의 경우:

브라우저 지원

  • Chrome: 1. <ph type="x-smartling-placeholder">
  • Edge: 12.
  • Firefox: 22. <ph type="x-smartling-placeholder">
  • Safari: 3. <ph type="x-smartling-placeholder">

소스

ClipboardItem:

브라우저 지원

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

소스

붙여넣기: 클립보드에서 데이터 읽기

readText()

클립보드에서 텍스트를 읽으려면 navigator.clipboard.readText()를 호출하고 반환된 프로미스가 확인될 때까지 기다립니다.

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

브라우저 지원

  • Chrome: 66 <ph type="x-smartling-placeholder">
  • Edge: 79.
  • Firefox: 125.
  • Safari 13.1.

소스

read()

navigator.clipboard.read() 메서드도 비동기식이며 약속을 반환합니다. 클립보드에서 이미지를 읽으려면 ClipboardItem 객체 목록을 가져온 다음 이를 반복합니다.

ClipboardItem는 다양한 유형의 콘텐츠를 보유할 수 있으므로 다음을 실행해야 합니다. for...of 루프를 사용하여 유형 목록을 다시 반복합니다. 각 유형에 대해 현재 유형을 인수로 사용하여 getType() 메서드를 호출하여 상응하는 blob을 가져옵니다. 이전과 마찬가지로 이 코드는 이미지에 연결되지 않으며 향후 다른 파일 형식과도 호환됩니다.

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

브라우저 지원

  • Chrome: 76 <ph type="x-smartling-placeholder">
  • Edge: 79 <ph type="x-smartling-placeholder">
  • Firefox: 127. <ph type="x-smartling-placeholder">
  • Safari: 13.1.

소스

붙여넣은 파일 작업

사용자가 ctrl+cctrl+v와 같은 클립보드 단축키를 사용할 수 있으면 유용합니다. Chromium은 아래에 설명된 대로 클립보드에 읽기 전용 파일을 노출합니다. 이는 사용자가 운영체제의 기본 붙여넣기 바로가기를 누르거나 브라우저의 메뉴 바에서 수정을 클릭한 다음 붙여넣기를 클릭할 때 트리거됩니다. 추가 배관 코드가 필요하지 않습니다.

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

브라우저 지원

  • Chrome: 3.
  • Edge: 12.
  • Firefox: 3.6 <ph type="x-smartling-placeholder">
  • Safari: 4.

소스

붙여넣기 이벤트

앞서 언급했듯이 Clipboard API와 함께 작동하는 이벤트를 도입할 계획이지만 지금은 기존 paste 이벤트를 사용할 수 있습니다. 새로운 비동기 메서드를 지원합니다. copy 이벤트와 마찬가지로 preventDefault()를 호출하는 것을 잊지 마세요.

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

브라우저 지원

  • Chrome: 1. <ph type="x-smartling-placeholder">
  • Edge: 12. <ph type="x-smartling-placeholder">
  • Firefox: 22.
  • Safari: 3. <ph type="x-smartling-placeholder">

소스

여러 MIME 유형 처리

대부분의 구현에서는 한 번의 컷을 위해 클립보드에 여러 데이터 형식을 저장합니다. 복사 작업을 수행할 수 있습니다 그 이유는 두 가지입니다. 앱 개발자는 사용자가 텍스트나 이미지를 복사하려는 앱의 기능을 알 수 없으며, 많은 애플리케이션이 구조화된 데이터를 일반 텍스트로 붙여넣기를 지원하기 때문입니다. 일반적으로 사용자에게 붙여넣기 및 스타일 일치 또는 서식 없음 붙여넣기와 같은 이름의 수정 메뉴 항목으로 표시됩니다.

다음 예는 그 방법을 보여줍니다. 이 예에서는 fetch()를 사용하여 이미지 데이터를 가져오지만 <canvas> 또는 파일 시스템 액세스 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]);
}

보안 및 권한

클립보드 액세스는 항상 브라우저의 보안 문제를 야기했습니다. 제외 페이지를 통해 모든 종류의 악성 콘텐츠를 은밀하게 복사할 수 있습니다. 붙여넣으면 치명적인 결과를 초래할 수 있습니다. rm -rf / 또는 감압 폭탄 이미지 클립보드에 복사하겠습니다.

사용자에게 클립보드 권한을 요청하는 브라우저 메시지
Clipboard API 권한 메시지

웹페이지에 클립보드 액세스 권한을 무제한으로 부여하는 기능이 훨씬 더 많습니다. 있습니다. 사용자는 비밀번호 및 개인 세부정보와 같은 민감한 정보를 습관적으로 클립보드에 복사하며, 그러면 사용자 모르게 모든 페이지에서 이를 읽을 수 있습니다.

많은 새 API와 마찬가지로 클립보드 API는 HTTPS를 통해 제공되는 페이지에서만 지원됩니다. 악용을 방지하기 위해 클립보드 액세스가 다음 경우에만 허용됩니다. 확인할 수 있습니다 활성 탭의 페이지는 권한을 요청하지 않고 클립보드에 쓸 수 있지만 클립보드에서 읽으려면 항상 권한이 필요합니다.

복사 및 붙여넣기 권한이 Permissions API에 추가되었습니다. 다음과 같은 경우 clipboard-write 권한이 페이지에 자동으로 부여됩니다. 확인할 수 있습니다 clipboard-read 권한을 요청해야 합니다. 다음 작업을 할 수 있습니다. 할 수 있습니다. 다음은 후자를 보여주는 코드입니다.

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

allowWithoutGesture 옵션을 사용하여 자르기 또는 붙여넣기를 호출하는 데 사용자 동작이 필요한지 여부도 제어할 수 있습니다. 이 값의 기본값 브라우저에 따라 다르므로 항상 포함해야 합니다.

여기에서 Clipboard API의 비동기 특성이 유용하게 사용됩니다. 클립보드 데이터를 읽거나 쓰려고 하면 아직 권한이 부여되지 않은 경우 사용자에게 자동으로 권한 메시지가 표시됩니다. API는 프라미스 기반이므로 이는 완전히 투명하며 사용자가 클립보드 권한을 거부하면 페이지가 적절하게 응답할 수 있도록 거부할 것입니다.

브라우저는 페이지가 활성 탭일 때만 클립보드 액세스를 허용하므로 개발자 도구 자체가 활성 탭이므로 여기에 있는 일부 예시를 브라우저 콘솔에 직접 붙여넣으면 실행되지 않습니다. 한 가지 방법이 있습니다. setTimeout()를 사용하여 클립보드 액세스를 지연한 다음 함수가 호출되기 전에 페이지 내부를 빠르게 클릭하여 포커스를 맞춥니다.

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

권한 정책 통합

iframe에서 API를 사용하려면 다양한 브라우저 기능과 API를 선택적으로 사용 설정 및 사용 중지할 수 있는 메커니즘을 정의하는 권한 정책으로 API를 사용 설정해야 합니다. 구체적으로 또는 앱의 필요에 따라 clipboard-read 또는 clipboard-write 둘 다로 지정할 수 있습니다.

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

특성 감지

모든 브라우저를 지원하면서 Async Clipboard API를 사용하려면 navigator.clipboard를 테스트하고 이전 메서드로 대체합니다. 예를 들어 다른 브라우저를 포함하도록 붙여넣기를 구현할 수 있습니다.

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

그게 전부가 아닙니다. Async Clipboard API 이전에는 웹브라우저에서 서로 다른 복사하여 붙여넣기 구현 방법을 사용하는 경우 대부분의 브라우저에서는 document.execCommand('copy')document.execCommand('paste')를 사용하여 브라우저 자체의 복사 및 붙여넣기를 트리거할 수 있습니다. 텍스트가 DOM에 없는 문자열인 경우 DOM 및 선택됨:

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

데모

아래 데모에서 Async Clipboard API를 사용해 볼 수 있습니다. Glitch에서 텍스트 데모를 또는 이미지 데모를 실험해 봅니다.

첫 번째 예는 텍스트를 클립보드 안팎으로 이동하는 방법을 보여줍니다.

이미지로 API를 사용해 보려면 이 데모를 사용하세요. PNG만 지원된다는 사실을 기억하세요. 그리고 일부 브라우저에서만 지원됩니다.

감사의 말씀

Async Clipboard API는 다윈 황게리 카치마르치크가 구현했습니다. 또한 다윈은 데모를 제공했습니다. 이 도움말의 일부를 검토해 주신 카리크님과 게리 카치마르치크님께 다시 한번 감사드립니다.

UnsplashMarkus Winkler님 제공 히어로 이미지