Начиная с Chrome 120, в API Async Clipboard доступна новая unsanitized
опция. Эта опция может помочь в особых ситуациях с HTML, когда вам необходимо вставить содержимое буфера обмена, идентичное тому, каким оно было при копировании. То есть без какого-либо промежуточного этапа очистки, который браузеры обычно — и по уважительным причинам — применяют. Узнайте, как использовать его в этом руководстве.
При работе с Async Clipboard API в большинстве случаев разработчикам не нужно беспокоиться о целостности содержимого в буфере обмена и они могут предполагать, что то, что они записывают в буфер обмена (копируют), совпадает с тем, что они получат. когда они читают данные из буфера обмена (вставляют).
Это определенно верно для текста. Попробуйте вставить следующий код в консоль DevTools, а затем немедленно перефокусировать страницу. ( setTimeout()
необходим для того, чтобы у вас было достаточно времени, чтобы сфокусировать страницу, что является требованием 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-код, чтобы предотвратить возникновение плохих вещей, например, удаляя теги <script>
из HTML-кода (и другие, такие как <meta>
, <head>
и <style>
) и встраивая 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 имеет решающее значение для правильного функционирования приложения. В таких обстоятельствах у вас есть два варианта:
- Если вы управляете как копированием, так и окончанием вставки, например, если вы копируете из своего приложения, чтобы затем аналогичным образом вставить его в свое приложение, вам следует использовать пользовательские веб-форматы для API Async Clipboard . Прекратите читать здесь и проверьте связанную статью.
- Если вы управляете только окончанием вставки в своем приложении, но не окончанием копирования, возможно, потому, что операция копирования происходит в собственном приложении, которое не поддерживает пользовательские веб-форматы, вам следует использовать вариант
unsanitized
, который объясняется в остальной части статьи. Эта статья.
Очистка включает в себя такие вещи, как удаление тегов script
, встраивание стилей и обеспечение правильного формата HTML. Этот список не является исчерпывающим, и в будущем могут быть добавлены дополнительные шаги.
Скопируйте и вставьте необработанный HTML-код.
Когда вы write()
(копируете) HTML в буфер обмена с помощью API Async Clipboard, браузер проверяет его правильность, пропуская его через анализатор DOM и сериализуя полученную строку HTML, но на этом этапе не происходит никакой очистки. Вам не нужно ничего делать. Когда вы read()
HTML-код, помещенный в буфер обмена другим приложением, и ваше веб-приложение соглашается на получение полного содержимого и требует выполнения какой-либо очистки в вашем собственном коде, вы можете передать объект параметров методу read()
. со свойством unsanitized
и значением ['text/html']
. По отдельности это выглядит так: navigator.clipboard.read({ unsanitized: ['text/html'] })
. Следующий пример кода ниже почти такой же, как показанный ранее, но на этот раз с unsanitized
параметром. Когда вы попробуете это в консоли 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({
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
.
Полезные ссылки
Благодарности
Эту статью рецензировали Анупам Снигдха и Рэйчел Эндрю . API был указан и реализован командой Microsoft Edge.