Начиная с Chrome 120, в API Async Clipboard доступна новая опция unsanitized
. Эта опция может помочь в особых ситуациях с HTML, когда вам нужно вставить содержимое буфера обмена в том виде, в котором оно было при копировании. То есть без промежуточного этапа очистки, который браузеры обычно — и на то есть веские причины — применяют. Узнайте, как использовать ее, в этом руководстве.
При работе с API асинхронного буфера обмена разработчикам в большинстве случаев не нужно беспокоиться о целостности содержимого буфера обмена, и они могут предположить, что то, что они записывают в буфер обмена (копируют), — это то же самое , что они получат при чтении данных из буфера обмена (вставке).
Это определенно верно для текста. Попробуйте вставить следующий код в DevTools Console, а затем немедленно перефокусировать страницу. ( 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 асинхронного буфера обмена . Прекратите читать здесь и проверьте связанную статью.
- Если вы контролируете только окончание вставки в своем приложении, но не окончание копирования, возможно, потому, что операция копирования происходит в собственном приложении, которое не поддерживает пользовательские веб-форматы, вам следует использовать опцию
unsanitized
, которая объясняется в оставшейся части этой статьи.
Санация включает в себя такие вещи, как удаление тегов script
, встраивание стилей и обеспечение правильного форматирования HTML. Этот список не является исчерпывающим, и в будущем могут быть добавлены дополнительные шаги.
Копировать и вставить необработанный HTML-код
Когда вы write()
(копируете) HTML в буфер обмена с помощью API Async Clipboard, браузер проверяет, что он правильно сформирован, пропуская его через парсер DOM и сериализуя полученную строку HTML, но на этом этапе очистка не выполняется. Вам ничего не нужно делать. Когда вы read()
HTML, помещенный в буфер обмена другим приложением, и ваше веб-приложение выбирает получение контента полной точности и нуждается в выполнении какой-либо очистки в вашем собственном коде, вы можете передать объект options методу 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
опцию в действии, посмотрите демо-версию и ознакомьтесь с ее исходным кодом .
Выводы
Как было отмечено во введении, большинству разработчиков никогда не придется беспокоиться об очистке буфера обмена, и они могут просто работать с параметрами очистки по умолчанию, выбранными браузером. Для редких случаев, когда разработчикам нужно беспокоиться, существует unsanitized
опция.
Полезные ссылки
Благодарности
Эту статью рассмотрели Анупам Снигдха и Рэйчел Эндрю . API был определен и реализован командой Microsoft Edge.