從 Chrome 120 開始,Async Clipboard API 中提供新的 unsanitized
選項。這個選項可在 HTML 的特殊情況下提供協助,在這種情況下,您需要將剪貼簿的內容貼上,並與複製時的內容相同。也就是說,沒有任何中間的清理步驟,而瀏覽器通常會 (且有充分理由) 套用這類步驟。請參閱本指南,瞭解如何使用這項功能。
使用 Async Clipboard API 時,在大多數情況下,開發人員不必擔心剪貼簿內容的完整性,並可假設他們寫入剪貼簿的內容 (複製) 與讀取剪貼簿資料 (貼上) 時取得的內容相同。
文字也是如此。請嘗試在 DevTools 主控台中貼上以下程式碼,然後立即重新聚焦頁面。(setTimeout()
是必要的,可讓您有足夠的時間將焦點放在頁面上,這是 Async 剪貼簿 API 的要求)。如您所見,輸入內容與輸出內容「完全相同」。
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);
圖片則稍有不同。為避免所謂的壓縮炸彈攻擊,瀏覽器會重新編碼 PNG 等圖片,但輸入和輸出圖片的視覺效果完全相同,每個像素都相同。
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);
不過,HTML 文字會發生什麼事?如您所知,HTML 的情況有所不同。在此情況下,瀏覽器會清理 HTML 程式碼,以防發生不良事件,例如從 HTML 程式碼 (以及 <meta>
、<head>
和 <style>
等其他程式碼) 中移除 <script>
標記,並內嵌 CSS。請參考以下範例,並在 DevTools 主控台中試用。您會發現輸出內容與輸入內容有明顯差異。
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);
一般來說,HTML 清理是件好事。在大多數情況下,您不希望允許未經過消毒的 HTML,以免發生安全性問題。不過,在某些情況下,開發人員會確切知道自己在做什麼,且 HTML 輸入和輸出內容的完整性對應用程式正確運作至關重要。在這種情況下,您有兩種選擇:
- 如果您同時控制複製和貼上作業,例如從應用程式中複製內容,然後同樣在應用程式中貼上內容,則應使用 適用於 Async 剪貼簿 API 的 Web 自訂格式。請停止閱讀這裡的內容,並查看連結的文章。
- 如果您只在應用程式中控制貼上端,而非複製端,可能是因為複製作業發生在原生應用程式中,而該應用程式不支援網頁自訂格式,因此您應使用
unsanitized
選項,這會在本文的其餘部分中說明。
清理作業包括移除 script
標記、內嵌樣式,以及確保 HTML 結構正確。這份清單並非完整,日後可能會新增更多步驟。
複製及貼上未經過淨化的 HTML
當您使用 Async Clipboard API 將 HTML write()
(複製) 到剪貼簿時,瀏覽器會透過 DOM 剖析器執行 HTML,並序列化產生的 HTML 字串,確保 HTML 格式正確,但不會在這個步驟中進行消毒。您不必採取任何行動。當您 read()
由其他應用程式放置在剪貼簿上的 HTML,且您的網頁應用程式選擇取得完整保真度內容,並需要在自己的程式碼中執行任何消毒作業時,您可以將選項物件傳遞至 read()
方法,其中包含屬性 unsanitized
和 ['text/html']
值。單獨看起來如下所示:navigator.clipboard.read({ unsanitized: ['text/html'] })
。下列程式碼範例幾乎與先前顯示的程式碼範例相同,但這次使用 unsanitized
選項。在開發人員工具控制台中試用時,您會發現輸入內容和輸出內容相同。
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);
瀏覽器支援和功能偵測
我們無法直接檢查是否支援這項功能,因此功能偵測功能會根據觀察行為來運作。因此,以下範例會偵測 <style>
標記是否仍存在,表示支援,或是已內嵌,表示不支援。請注意,為了讓這項功能正常運作,頁面必須已取得剪貼簿權限。
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);
};
示範
如要瞭解 unsanitized
選項的實際運作情形,請參閱 Glitch 上的示範,並查看其原始碼。
結論
如前言所述,大多數開發人員都不需要擔心剪貼簿的清理作業,只要使用瀏覽器提供的預設清理選項即可。在極少數需要開發人員處理的情況下,unsanitized
選項就會出現。
實用連結
特別銘謝
本文由 Anupam Snigdha 和 Rachel Andrew 共同審查。這個 API 是由 Microsoft Edge 團隊指定及實作。