Bỏ chặn quyền truy cập vào bảng nhớ tạm

Truy cập vào bảng nhớ tạm an toàn hơn và được bỏ chặn đối với văn bản và hình ảnh

Cách truyền thống để truy cập vào bảng nhớ tạm của hệ thống là thông qua document.execCommand() để tương tác với bảng nhớ tạm. Mặc dù được hỗ trợ rộng rãi, nhưng phương pháp cắt và dán này khá tốn kém: quyền truy cập vào bảng nhớ tạm đồng bộ và chỉ có thể đọc và ghi vào DOM.

Điều này cũng bình thường đối với các đoạn văn bản nhỏ, nhưng có nhiều trường hợp việc chặn trang để chuyển dữ liệu vào bảng nhớ tạm sẽ mang lại trải nghiệm không tốt. Bạn có thể cần nhiều thời gian để dọn dẹp hoặc giải mã hình ảnh trước khi có thể dán nội dung một cách an toàn. Trình duyệt có thể cần tải hoặc các tài nguyên được liên kết cùng dòng từ một tài liệu đã dán. Thao tác này sẽ chặn trang trong khi chờ trên ổ đĩa hoặc mạng. Hãy tưởng tượng bạn thêm quyền vào danh sách kết hợp, yêu cầu trình duyệt chặn trang trong khi yêu cầu quyền truy cập vào bảng nhớ tạm. Đồng thời, các quyền được thiết lập xung quanh document.execCommand() để tương tác với bảng nhớ tạm được xác định lỏng lẻo và có sự khác biệt giữa các trình duyệt.

API Bảng nhớ tạm không đồng bộ giải quyết những vấn đề này, cung cấp một mô hình quản lý quyền được xác định rõ ràng không chặn trang. API Bảng nhớ tạm không đồng bộ chỉ dùng được để xử lý văn bản và hình ảnh trên hầu hết các trình duyệt, nhưng khả năng hỗ trợ sẽ khác nhau. Hãy nhớ nghiên cứu kỹ tổng quan về khả năng tương thích của trình duyệt cho từng phần sau đây.

Sao chép: ghi dữ liệu vào bảng nhớ tạm

writeText()

Để sao chép văn bản vào bảng nhớ tạm, hãy gọi writeText(). Vì API này không đồng bộ, nên hàm writeText() sẽ trả về một Promise sẽ phân giải hoặc từ chối tuỳ thuộc vào việc văn bản đã truyền có được sao chép thành công hay không:

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

Hỗ trợ trình duyệt

  • 66
  • 79
  • 63
  • 13,1

Nguồn

ghi()

Thực ra, writeText() chỉ là một phương thức thuận tiện cho phương thức write() chung, phương thức này cũng cho phép bạn sao chép hình ảnh vào bảng nhớ tạm. Giống như writeText(), hàm này không đồng bộ và trả về một Promise.

Để ghi hình ảnh vào bảng nhớ tạm, bạn cần hình ảnh đó dưới dạng blob. Có một cách để thực hiện việc này là yêu cầu hình ảnh từ máy chủ bằng fetch(), sau đó gọi blob() trên phản hồi.

Việc yêu cầu hình ảnh từ máy chủ có thể không như mong muốn hoặc không thể vì nhiều lý do. May mắn là bạn cũng có thể vẽ hình ảnh lên canvas và gọi phương thức toBlob() của canvas đó.

Tiếp theo, hãy truyền một mảng các đối tượng ClipboardItem dưới dạng tham số vào phương thức write(). Hiện tại, bạn chỉ có thể truyền mỗi lần một hình ảnh, nhưng chúng tôi hy vọng có thể hỗ trợ thêm nhiều hình ảnh trong tương lai. ClipboardItem lấy một đối tượng có loại MIME của hình ảnh làm khoá và blob làm giá trị. Đối với các đối tượng blob lấy từ fetch() hoặc canvas.toBlob(), thuộc tính blob.type sẽ tự động chứa loại MIME chính xác cho hình ảnh.

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

Ngoài ra, bạn có thể viết lời hứa vào đối tượng ClipboardItem. Đối với mẫu này, bạn cần phải biết trước loại MIME của dữ liệu.

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

Hỗ trợ trình duyệt

  • 66
  • 79
  • 13,1

Nguồn

Sự kiện sao chép

Trong trường hợp người dùng bắt đầu một bản sao trong bảng nhớ tạm mà không gọi preventDefault(), thì copy sẽ bao gồm một thuộc tính clipboardData với các mục đã ở định dạng phù hợp. Nếu muốn triển khai logic của riêng mình, bạn cần gọi preventDefault() để ngăn hành vi mặc định có lợi cho cách bạn triển khai. Trong trường hợp này, clipboardData sẽ trống. Hãy xem xét một trang có văn bản và hình ảnh. Khi người dùng chọn tất cả và bắt đầu sao chép trong bảng nhớ tạm, giải pháp tuỳ chỉnh của bạn sẽ loại bỏ văn bản và chỉ sao chép hình ảnh. Bạn có thể thực hiện việc này như trong mã mẫu dưới đây. Nội dung không được đề cập trong ví dụ này là cách quay lại các API trước đó khi không hỗ trợ API Bảng nhớ tạm.

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

Đối với sự kiện copy:

Hỗ trợ trình duyệt

  • 1
  • 12
  • 22
  • 3

Nguồn

Đối với ClipboardItem:

Hỗ trợ trình duyệt

  • 76
  • 79
  • 13,1

Nguồn

Dán: đọc dữ liệu từ bảng nhớ tạm

readText()

Để đọc văn bản từ bảng nhớ tạm, hãy gọi navigator.clipboard.readText() và đợi lời hứa được trả về được giải quyết:

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

Hỗ trợ trình duyệt

  • 66
  • 79
  • 13,1

Nguồn

đọc()

Phương thức navigator.clipboard.read() cũng không đồng bộ và trả về một lời hứa. Để đọc hình ảnh từ bảng nhớ tạm, hãy lấy danh sách các đối tượng ClipboardItem rồi lặp lại đối tượng đó.

Mỗi ClipboardItem có thể chứa nội dung thuộc nhiều kiểu. Vì vậy, bạn cần lặp lại danh sách các loại bằng cách sử dụng vòng lặp for...of. Đối với mỗi kiểu, hãy gọi phương thức getType() có kiểu hiện tại làm đối số để lấy blob tương ứng. Như trước đây, mã này không liên kết với hình ảnh và sẽ hoạt động với các loại tệp khác trong tương lai.

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

Hỗ trợ trình duyệt

  • 66
  • 79
  • 13,1

Nguồn

Làm việc với tệp được dán

Người dùng sẽ hữu ích khi có thể sử dụng các phím tắt trong bảng nhớ tạm như ctrl+cctrl+v. Chromium hiển thị các tệp chỉ có thể đọc trên bảng nhớ tạm như trình bày dưới đây. Lệnh này kích hoạt khi người dùng nhấn vào phím tắt dán mặc định của hệ điều hành hoặc khi người dùng nhấp vào Edit (Chỉnh sửa) rồi Paste (Dán) vào thanh trình đơn của trình duyệt. Không cần thêm mã cho hệ thống ống nước.

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

Hỗ trợ trình duyệt

  • 3
  • 12
  • 3.6
  • 4

Nguồn

Sự kiện dán

Như đã đề cập trước đó, chúng tôi dự định giới thiệu các sự kiện để hoạt động với API Bảng nhớ tạm nhưng hiện tại bạn có thể sử dụng sự kiện paste hiện có. Tính năng này hoạt động hiệu quả với các phương thức không đồng bộ mới để đọc văn bản trong bảng nhớ tạm. Tương tự như với sự kiện copy, đừng quên gọi preventDefault().

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

Hỗ trợ trình duyệt

  • 1
  • 12
  • 22
  • 3

Nguồn

Xử lý nhiều loại MIME

Hầu hết các phương thức triển khai đều đặt nhiều định dạng dữ liệu vào bảng nhớ tạm cho một thao tác cắt hoặc sao chép. Có 2 lý do dẫn đến điều này: là nhà phát triển ứng dụng, bạn không có cách nào để biết được khả năng của ứng dụng mà người dùng muốn sao chép văn bản hoặc hình ảnh vào đó và nhiều ứng dụng hỗ trợ dán dữ liệu có cấu trúc dưới dạng văn bản thuần tuý. Nội dung này thường hiển thị cho người dùng với một mục trong trình đơn Chỉnh sửa có tên như Dán và so khớp kiểu hoặc Dán không cần định dạng.

Ví dụ sau cho thấy cách thực hiện việc này. Ví dụ này sử dụng fetch() để lấy dữ liệu hình ảnh, nhưng cũng có thể đến từ <canvas> hoặc API Truy cập hệ thống tệp.

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

Tính bảo mật và quyền

Trình duyệt luôn lo ngại về vấn đề bảo mật khi truy cập bảng nhớ tạm. Nếu không có quyền thích hợp, một trang có thể ngầm sao chép tất cả các kiểu nội dung độc hại vào bảng nhớ tạm của người dùng. Điều này sẽ dẫn đến kết quả thảm khốc khi được dán. Hãy tưởng tượng một trang web tự động sao chép rm -rf / hoặc hình ảnh bom giải nén vào bảng nhớ tạm.

Lời nhắc của trình duyệt yêu cầu người dùng cấp quyền truy cập vào bảng nhớ tạm.
Lời nhắc cấp quyền đối với API Bảng nhớ tạm.

Việc cung cấp cho các trang web quyền đọc không bị hạn chế vào bảng nhớ tạm thậm chí còn gây rắc rối hơn. Người dùng thường xuyên sao chép thông tin nhạy cảm như mật khẩu và thông tin cá nhân vào bảng nhớ tạm. Sau đó, bất kỳ trang nào cũng có thể đọc thông tin này mà người dùng không biết.

Giống như nhiều API mới, API Bảng nhớ tạm chỉ được hỗ trợ cho các trang được phân phát qua HTTPS. Để ngăn chặn hành vi sai trái, chúng tôi chỉ cho phép truy cập vào bảng nhớ tạm khi một trang là thẻ đang hoạt động. Các trang trong thẻ đang hoạt động có thể ghi vào bảng nhớ tạm mà không cần yêu cầu quyền, nhưng việc đọc từ bảng nhớ tạm luôn yêu cầu quyền.

Các quyền sao chép và dán đã được thêm vào Permissions API (API Quyền). Quyền clipboard-write được tự động cấp cho các trang khi chúng là thẻ đang hoạt động. Bạn phải yêu cầu quyền clipboard-read. Bạn có thể thực hiện việc này bằng cách cố gắng đọc dữ liệu từ bảng nhớ tạm. Mã bên dưới cho thấy nội dung sau:

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

Bạn cũng có thể kiểm soát việc có bắt buộc phải dùng một cử chỉ của người dùng để gọi thao tác cắt hoặc dán hay không bằng cách sử dụng tuỳ chọn allowWithoutGesture. Giá trị mặc định cho giá trị này thay đổi theo trình duyệt, vì vậy, bạn phải luôn đưa giá trị này vào.

Sau đây là lúc tính chất không đồng bộ của API Bảng nhớ tạm thực sự hữu ích: việc cố gắng đọc hoặc ghi dữ liệu trong bảng nhớ tạm sẽ tự động nhắc người dùng cấp quyền nếu chưa được cấp quyền. Vì API này dựa trên lời hứa, nên đây là hoàn toàn minh bạch và việc người dùng từ chối cấp quyền vào bảng nhớ tạm sẽ khiến lời hứa từ chối để trang có thể phản hồi một cách phù hợp.

Vì các trình duyệt chỉ cho phép truy cập vào bảng nhớ tạm khi trang là thẻ đang hoạt động, nên bạn sẽ thấy một số ví dụ ở đây sẽ không chạy nếu được dán trực tiếp vào bảng điều khiển của trình duyệt, vì bản thân các công cụ dành cho nhà phát triển là một thẻ đang hoạt động. Có một mẹo: trì hoãn quyền truy cập vào bảng nhớ tạm bằng setTimeout(), sau đó nhấp nhanh vào bên trong trang để lấy tiêu điểm vào trước khi các hàm được gọi:

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

Tích hợp chính sách về quyền

Để sử dụng API trong iframe, bạn cần bật API này bằng Chính sách về quyền. Chính sách này xác định cơ chế cho phép bật và tắt nhiều tính năng và API của trình duyệt một cách có chọn lọc. Cụ thể, bạn cần truyền một hoặc cả hai clipboard-read hoặc clipboard-write, tuỳ vào nhu cầu của ứng dụng.

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

Phát hiện tính năng

Để sử dụng API Bảng nhớ tạm không đồng bộ trong khi hỗ trợ tất cả trình duyệt, hãy kiểm thử navigator.clipboard và quay lại các phương thức trước đó. Ví dụ: dưới đây là cách bạn có thể triển khai thao tác dán để đưa các trình duyệt khác vào.

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

Đó chưa phải là toàn bộ câu chuyện. Trước API Bảng nhớ tạm Async, có nhiều cách triển khai sao chép và dán trên các trình duyệt web. Trong hầu hết các trình duyệt, bạn có thể kích hoạt tính năng sao chép và dán của riêng trình duyệt bằng cách sử dụng document.execCommand('copy')document.execCommand('paste'). Nếu văn bản cần sao chép là một chuỗi không có trong DOM, thì văn bản đó phải được chèn vào DOM và chọn:

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

Bản thu thử

Bạn có thể dùng API Bảng nhớ tạm không đồng bộ trong các bản minh hoạ dưới đây. Trên Glitch, bạn có thể phối lại bản minh hoạ văn bản hoặc bản minh hoạ hình ảnh để thử nghiệm.

Ví dụ đầu tiên minh hoạ việc di chuyển văn bản trên và ra khỏi bảng nhớ tạm.

Để dùng thử API bằng hình ảnh, hãy dùng bản minh hoạ này. Hãy nhớ rằng định dạng PNG chỉ được hỗ trợ và chỉ có trong một số trình duyệt.

Xác nhận

API Bảng nhớ tạm không đồng bộ do Darwin HuangGary Kačmarčík triển khai. Darwin cũng đưa ra bản minh hoạ. Cảm ơn Kyarik và một lần nữa Gary Kačmarčík đã xem lại các phần của bài viết này.

Hình ảnh chính của Markus Winkler trên Unsplash.