HTML chưa được dọn dẹp trong API Bảng nhớ tạm không đồng bộ

Từ Chrome 120, một tuỳ chọn unsanitized mới sẽ xuất hiện trong Bảng nhớ tạm không đồng bộ API. Tuỳ chọn này có thể hữu ích trong các trường hợp đặc biệt với HTML, khi bạn cần dán nội dung của bảng nhớ tạm giống với nội dung khi được sao chép. Tức là không có bất kỳ bước dọn dẹp trung gian nào mà các trình duyệt thường sử dụng—và vì lý do chính đáng—hãy đăng ký. Hãy tìm hiểu cách sử dụng chúng trong hướng dẫn này.

Khi làm việc với API Bảng nhớ tạm không đồng bộ, trong phần lớn trường hợp, nhà phát triển không cần lo lắng về tính toàn vẹn của nội dung trên bảng nhớ tạm và có thể giả định rằng những gì chúng viết vào bảng nhớ tạm (bản sao) tương tự với nội dung mà họ sẽ nhận được khi đọc dữ liệu từ bảng nhớ tạm (dán).

Điều này chắc chắn đúng với văn bản. Hãy thử dán mã sau vào Công cụ cho nhà phát triển Console, sau đó lấy nét lại trang ngay lập tức. (Cần có setTimeout() để bạn có đủ thời gian để tập trung vào trang, đây là một yêu cầu của API Bảng nhớ tạm.) Như bạn thấy, dữ liệu đầu vào chính xác giống với dữ liệu đầu ra.

setTimeout(async () => {
  const input = 'Hello';
  await navigator.clipboard.writeText(input);
  const output = await navigator.clipboard.readText();
  console.log(input, output, input === output);
  // Logs "Hello Hello true".
}, 3000);

Hình ảnh sẽ hơi khác một chút. Để ngăn chặn những điều gọi là tấn công bằng bom nén, trình duyệt mã hoá lại hình ảnh như PNG, nhưng hình ảnh đầu vào và đầu ra trực quan giống nhau, pixel trên pixel.

setTimeout(async () => {
  const dataURL =
    'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=';
  const input = await fetch(dataURL).then((response) => response.blob());
  await navigator.clipboard.write([
    new ClipboardItem({
      [input.type]: input,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read();
  const output = await clipboardItem.getType(input.type);
  console.log(input.size, output.size, input.type === output.type);
  // Logs "68 161 true".
}, 3000);

Tuy nhiên, điều gì sẽ xảy ra với văn bản HTML? Như bạn có thể đã đoán, với HTML, tình hình lại khác nhau. Ở đây, trình duyệt dọn dẹp mã HTML để ngăn chặn trang web không hợp lệ những thứ khác xảy ra, chẳng hạn như bằng cách tách thẻ <script> khỏi HTML mã (và các mã khác như <meta>, <head><style>) và bằng cách cùng dòng CSS. Hãy xem xét ví dụ sau đây và thử trong Bảng điều khiển công cụ cho nhà phát triển. Bạn sẽ lưu ý rằng đầu ra khác biệt khá đáng kể so với đầu vào.

setTimeout(async () => {
  const input = `<html>  
  <head>  
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
    <meta name="ProgId" content="Excel.Sheet" />  
    <meta name="Generator" content="Microsoft Excel 15" />  
    <style>  
      body {  
        font-family: HK Grotesk;  
        background-color: var(--color-bg);  
      }  
    </style>  
  </head>  
  <body>  
    <div>hello</div>  
  </body>  
</html>`;
  const inputBlob = new Blob([input], { type: 'text/html' });
  await navigator.clipboard.write([
    new ClipboardItem({
      'text/html': inputBlob,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read();
  const outputBlob = await clipboardItem.getType('text/html');
  const output = await outputBlob.text();
  console.log(input, output);
}, 3000);

Dọn dẹp HTML thường là một điểm tốt. Bạn không muốn tiết lộ bản thân vào các vấn đề bảo mật bằng cách cho phép HTML không được dọn dẹp trong phần lớn các trường hợp. Có là các tình huống mà trong đó nhà phát triển biết chính xác họ đang làm gì và trong đó tính toàn vẹn của HTML đầu vào và đầu ra rất quan trọng đối với việc hiển thị hoạt động của ứng dụng. Trong những trường hợp như vậy, bạn có hai lựa chọn:

  1. Nếu bạn kiểm soát cả thao tác sao chép và dán, ví dụ: nếu bạn sao chép từ bên trong ứng dụng để sau đó dán vào ứng dụng, bạn nên sử dụng Các định dạng tuỳ chỉnh trên web cho API Bảng nhớ tạm không đồng bộ. Hãy dừng đọc ở đây và xem bài viết được liên kết.
  2. Nếu bạn chỉ kiểm soát việc dán trong ứng dụng chứ không kiểm soát việc sao chép, có thể là do thao tác sao chép diễn ra trong một ứng dụng gốc không hỗ trợ trên web, bạn nên sử dụng tùy chọn unsanitized, tức là được giải thích trong phần còn lại của bài viết này.

Quy trình dọn dẹp bao gồm xoá thẻ script, kiểu cùng dòng và đảm bảo HTML được định dạng đúng. Danh sách này không đầy đủ và nhiều có thể được bổ sung trong tương lai.

Sao chép và dán HTML chưa được dọn dẹp

Khi bạn write() (sao chép) HTML vào bảng nhớ tạm bằng API Bảng nhớ tạm không đồng bộ, trình duyệt sẽ đảm bảo trình duyệt được định dạng đúng bằng cách chạy trình duyệt qua trình phân tích cú pháp DOM và chuyển đổi tuần tự chuỗi HTML thu được, nhưng quy trình dọn dẹp không diễn ra bước này. Bạn không cần phải làm gì cả. Khi bạn read() HTML đặt trên bảng nhớ tạm của một ứng dụng khác và ứng dụng web của bạn đang chọn nhận nội dung có độ trung thực đầy đủ và cần dọn dẹp trong mã của riêng bạn, bạn có thể truyền một đối tượng tuỳ chọn đến phương thức read() bằng một thuộc tính unsanitized và giá trị là ['text/html']. Riêng biệt, giao diện sẽ có dạng như sau: navigator.clipboard.read({ unsanitized: ['text/html'] }). Mã mẫu sau đây bên dưới gần giống với hình ảnh hiển thị trước đó, nhưng lần này là với unsanitized . Khi bạn dùng thử trong Bảng điều khiển công cụ cho nhà phát triển, bạn sẽ thấy rằng đầu vào và thì kết quả sẽ giống nhau.

setTimeout(async () => {
  const input = `<html>  
  <head>  
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
    <meta name="ProgId" content="Excel.Sheet" />  
    <meta name="Generator" content="Microsoft Excel 15" />  
    <style>  
      body {  
        font-family: HK Grotesk;  
        background-color: var(--color-bg);  
      }  
    </style>  
  </head>  
  <body>  
    <div>hello</div>  
  </body>  
</html>`;
  const inputBlob = new Blob([input], { type: 'text/html' });
  await navigator.clipboard.write([
    new ClipboardItem({
      'text/html': inputBlob,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read({
    unsanitized: ['text/html'],
  });
  const outputBlob = await clipboardItem.getType('text/html');
  const output = await outputBlob.text();
  console.log(input, output);
}, 3000);

Hỗ trợ trình duyệt và phát hiện tính năng

Không có cách trực tiếp nào để kiểm tra xem tính năng này có được hỗ trợ hay không, vì vậy dựa trên việc quan sát hành vi. Do đó, ví dụ sau dựa vào việc phát hiện thực tế là liệu thẻ <style> có còn tồn tại hay không, điều này biểu thị hỗ trợ, hoặc đang hiển thị cùng dòng, biểu thị việc không hỗ trợ. Lưu ý rằng để tính năng này hoạt động, trang cần phải có được bảng nhớ tạm quyền.

const supportsUnsanitized = async () => {
  const input = `<style>p{color:red}</style><p>a`;
  const inputBlob = new Blob([input], { type: 'text/html' });
  await navigator.clipboard.write([
    new ClipboardItem({
      'text/html': inputBlob,
    }),
  ]);
  const [clipboardItem] = await navigator.clipboard.read({
    unsanitized: ['text/html],
  });
  const outputBlob = await clipboardItem.getType('text/html');
  const output = await outputBlob.text();
  return /<style>/.test(output);
};

Bản minh hoạ

Để xem tuỳ chọn unsanitized trong thực tế, hãy xem bản minh hoạ về Glitch và xem bản minh hoạ mã nguồn.

Kết luận

Như đã nêu trong phần giới thiệu, hầu hết các nhà phát triển sẽ không bao giờ phải lo lắng về dọn dẹp bảng nhớ tạm và có thể hoạt động với các lựa chọn dọn dẹp mặc định do trình duyệt thực hiện. Trong một số ít trường hợp mà nhà phát triển cần quan tâm, Đã tồn tại lựa chọn unsanitized.

Xác nhận

Bài viết này do Anupam Snigdha xem xét và Rachel Andrew. API đã được chỉ định và do nhóm Microsoft Edge triển khai.