Если вы уже давно имеете дело с компьютерами, то, вероятно, знаете, что в буфере обмена (clipboard) могут храниться различные типы данных (изображения, текст с форматированием, файлы и так далее). Меня как разработчика ПО начало напрягать то, что я не знаю, как буфер обмена хранит и упорядочивает данные разных типов.

Недавно я решил решить эту загадку и написать пост на основании полученной информации. Мы рассмотрим буфер обмена в вебе и его API, а также коснёмся того, как он взаимодействует с буферами обмена операционных систем.

Начнём мы с изучения API веб-буферов обмена и их истории. API накладывают интересные ограничения, связанные с типами данных; мы узнаем, как некоторые из компаний обходят эти ограничения. Также мы рассмотрим некоторые из предложений, предназначенных для устранения этих ограничений (самое примечательное из них — это Web Custom Formats).

Если вы когда-нибудь задавались вопросом, как устроен веб-буфер обмена, то этот пост для вас.

Работа с async Clipboard API


При копировании контента с веб-сайта и вставке в Google Docs часть форматирования сохраняется, в частности, ссылки, размер и цвет шрифтов.


Но если открыть VS Code и вставить содержимое туда, то вставится только голый текст.


Буфер обмена обрабатывает эти две ситуации, обеспечивая хранение информации в нескольких representation (описаниях), связанных с типами MIME. Спецификация W3C Clipboard требует, чтобы при записи и чтении из буфера обмена поддерживались три типа данных:

  • text/plain для текста без форматирования.
  • text/html для HTML.
  • image/png для изображений PNG.

То есть когда я вставил содержимое в первом случае, Google Docs считал описание text/html и использовал его для сохранения форматирования текста. Редактору VS Code важен только голый текст, поэтому он считал описание text/plain. Всё логично.

Считать конкретное описание при помощи метода read async Clipboard API достаточно легко:

const items = await navigator.clipboard.read();

for (const item of items) {
  if (item.types.includes("text/html")) {
    const blob = await item.getType("text/html");
    const html = await blob.text();
    // Выполняем действия с HTML...
  }
}

Записывать различные описания в буфер обмена при помощи write чуть сложнее, но тоже довольно просто. Сначала мы создаём Blob для каждого описания, которое хотим записать в буфер обмена:

const textBlob = new Blob(["Hello, world"], { type: "text/plain" });
const htmlBlob = new Blob(["Hello, <em>world<em>"], { type: "text/html" });

После создания блобов мы передаём их в новый ClipboardItem в виде хранилища «ключ-значение», где типы данных — это ключи, а блобы — значения:

const clipboardItem = new ClipboardItem({
  [textBlob.type]: textBlob,
  [htmlBlob.type]: htmlBlob,
});

Примечание: мне нравится, что ClipboardItem принимает хранилище «ключ-значение». Это согласуется с принципом использования структуры данных, не позволяющей описывать недопустимые состояния (см. Parse, don't validate).

Далее мы вызываем write с только что созданным ClipboardItem:

await navigator.clipboard.write([clipboardItem]);

А как насчёт других типов данных?


HTML и изображения — это, конечно, здорово, но как насчёт обобщённых форматов обмена данными наподобие JSON? Если бы я писал приложение с поддержкой копирования и вставки, то, наверно, хотел бы записывать в буфер обмена JSON или какие-то двоичные данные.

Давайте попробуем записать в буфер обмена данные JSON:

// Создаём блоб JSON
const json = JSON.stringify({ message: "Hello" });
const blob = new Blob([json], { type: "application/json" });

// Записываем блоб JSON в буфер обмена
const clipboardItem = new ClipboardItem({ [blob.type]: blob });
await navigator.clipboard.write([clipboardItem]);

При запуске этого кода выбрасывается исключение:

Failed to execute 'write' on 'Clipboard':
  Type application/json not supported on write.

Хм, что происходит? Спецификация метода write сообщает нам, что типы данных, отличающиеся от text/plain, text/html и image/png, должны отклоняться:

Если type не относится к списку обязательных типов данных, то эти шаги отклоняются и отменяются.

Любопытно, что тип MIME application/json находился в списке обязательных типов данных с 2012 по 2021 год, но был удалён из спецификации в w3c/clipboard-apis#155. До этого изменения списки обязательных типов данных были гораздо длиннее: 16 обязательных типов данных для чтения из буфера обмена и 8 — для записи. После внесения изменения остались только text/plain, text/html и image/png.

Это изменение было внесено после того, как разработчики браузеров решили не поддерживать многие из обязательных типов из-за проблем с безопасностью. Это отражено в предупреждении, приведённом в разделе спецификации про обязательные типы данных:

Внимание! Список типов данных, которые могут записывать в буфер обмена ненадёжные скрипты, ограничен из-за соображений безопасности.

Ненадёжные скрипты могут пытаться использовать уязвимости безопасности в локальном ПО, помещая в буфер обмена данные, которые могут приводить к срабатыванию этих уязвимостей.

Итак, теперь мы можем записывать в буфер обмена только ограниченное множество типов данных. Но что это за ненадёжные скрипты? Можем ли мы как-то написать «надёжный» скрипт, позволяющий записывать в буфер другие типы данных?

Свойство isTrusted


Вероятно, термин «надёжный» (trusted) связан со свойством isTrusted событий. isTrusted — это свойство только для чтения, которое имеет значение true, только если событие вызывается user agent.

document.addEventListener("copy", (e) => {
  if (e.isTrusted) {
    // Это событие вызвано user agent
  }
})

«Вызвано user agent» означает, что оно вызвано пользователем, например, событие копирования, вызванное тем, что пользователь нажал на Ctrl+C. Это противоположно синтетическому событию, программно вызываемому через dispatchEvent():

document.addEventListener("copy", (e) => {
  console.log("e.isTrusted is " + e.isTrusted);
});

document.dispatchEvent(new ClipboardEvent("copy"));
//=> "e.isTrusted is false"

Давайте изучим события буфера обмена, чтобы понять, могут ли они позволить нам записывать в буфер обмена произвольные типы данных.

Clipboard Events API


ClipboardEvent вызывается для событий копирования, вырезания и вставки, оно содержит свойство clipboardData типа DataTransfer. Объект DataTransfer используется Clipboard Events API для хранения множественных описаний данных.

Запись в буфер обмена в событии copy выполняется очень просто:

document.addEventListener("copy", (e) => {
  e.preventDefault(); // Предотвращаем стандартное поведение копирования

  e.clipboardData.setData("text/plain", "Hello, world");
  e.clipboardData.setData("text/html", "Hello, <em>world</em>");
});

А чтение из буфера обмена в событии paste выполнить столь же легко:

document.addEventListener("paste", (e) => {
  e.preventDefault(); // Предотвращаем стандартное поведение вставки

  const html = e.clipboardData.getData("text/html");
  if (html) {
    // Выполняем действия с HTML...
  }
});

А теперь важный вопрос: можно ли записывать в буфер обмена JSON?

document.addEventListener("copy", (e) => {
  e.preventDefault();

  const json = JSON.stringify({ message: "Hello" });
  e.clipboardData.setData("application/json", json); // Ошибки нет
});

Нет никакого исключения, но действительно ли мы записали JSON в буфер обмена? Давайте убедимся в этом, написав обработчик вставки, итеративно обходящий все элементы в буфере обмена и выводящий их в лог:

document.addEventListener("paste", (e) => {
  for (const item of e.clipboardData.items) {
    const { kind, type } = item;
    if (kind === "string") {
      item.getAsString((content) => {
        console.log({ type, content });
      });
    }
  }
});

После добавления этих двух обработчиков и вызова копирования и вставки в лог выводятся следующие результаты:

{ "type": "application/json", content: "{\"message\":\"Hello\"}" }

Сработало! Похоже, что clipboardData.setData не ограничивает типы данных так, как это делает асинхронный метод write.

Но… почему? Почему мы можем считывать и записывать произвольные типы данных при помощи clipboardData, но не при помощи async Clipboard API?

История clipboardData


Относительно новый async Clipboard API был добавлен в 2017 году, однако clipboardData гораздо его старше. W3C draft для Clipboard API, датированный 2006 годом определяет clipboardData с его методами setData и getData (это показывает, что в то время не использовались типы MIME):

setData() получает один или два параметра. Первый должен иметь значение или 'text', или 'URL' (без учёта регистра).

getData() получает один параметр, позволяющий цели запрашивать конкретный тип данных.

Но оказалось, что clipboardData даже старше, чем draft 2006 года. Вот цитата из раздела «Status of this Document»:

По большей мере этот документ описывает функциональность, реализованную в Internet Explorer…

Цель этого документа заключается в перечислении того, что работает в современных браузерах, и в повышении удобства их взаимодействия, а не в добавлении новых функций.

В статье за 2003 год рассказывается, что на тот момент в Internet Explorer 4 и выше можно был использовать clipboardData для чтения буфера обмена пользователя без его согласия. Так как Internet Explorer 4 был выпущен в 1997 году, похоже, интерфейсу clipboardData не менее 26 лет.

Типы MIME появились в спецификации 2011 года:

Аргумент dataType — это, например, строка, но не ограниченная типом MIME...

Если скрипт вызывает getData('text/html')...

В то время спецификация не определяла, какие типы данных должны использоваться:

Хотя можно использовать в качестве аргумента типа setData() любую строку, рекомендуется придерживаться распространённых типов.

[Issue] Нужно ли перечислить некоторые из «распространённых типов»?

Возможность использовать любую строку для setData и getData сохраняется и сегодня. Вот такой код работает без проблем:

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("foo bar baz", "Hello, world");
});

document.addEventListener("paste", (e) => {
  const content = e.clipboardData.getData("foo bar baz");
  if (content) {
    console.log(content); // Выводит "Hello, world!"
  }
});

Если вставить этот код в DevTools, а затем нажать копировать и вставить, то в консоль будет выведено сообщение «Hello, world».

Вероятно, возможность использования любого типа данных clipboardData Clipboard Events API оставлена по историческим причинам. «Не ломайте веб».

Возвращаемся к isTrusted


Давайте снова вернёмся к цитате из раздела про обязательные типы данных:

Список типов данных, которые могут записывать в буфер обмена ненадёжные скрипты, ограничен из-за соображений безопасности.

Что же произойдёт, если мы попытаемся выполнить запись в буфер обмена в синтетическом (ненадёжном) событии буфера обмена?

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("text/plain", "Hello");
});

document.dispatchEvent(new ClipboardEvent("copy", {
  clipboardData: new DataTransfer(),
}));

Код успешно выполняется, но не изменяет буфер обмена. Как объяснено в спецификации, это ожидаемое поведение:

Синтетические события копирования и вырезания не должны модифицировать данные в системном буфере обмена.

Синтетические события вставки не должны давать скрипту доступ к данным в реальном системном буфере обмена.

То есть изменять буфер обмена могут события копирования и вставки, вызванные user agent. И это логично, я бы не хотел, чтобы веб-сайты свободно читали содержимое моего буфера обмена и похищали мои пароли.



Подведём промежуточный итог:

  • Появившийся в 2017 году async Clipboard API ограничивает количество записываемых и считываемых из буфера обмена типов данных. Однако если пользователь даст на это разрешение, он может считывать и записывать буфер обмена (и в документе большое внимание уделяется конфиденциальности).
  • Более старый Clipboard Events API не имеет особых ограничений по типам данных, которые могут выполнять чтение и запись в буфер обмена. Однако он может использоваться только в событиях копирования и вставки, вызванных user agent (то есть когда isTrusted имеет значение true).

Похоже, использование Clipboard Events API — это единственный способ записи в буфер обмена типов данных, отличающихся от текста без форматирования, HTML и изображений. В этом отношении он гораздо менее строг.

Но что, если мы хотим создать кнопку Copy, записывающую в буфер обмена произвольные данные? Похоже, мы не можем использовать Clipboard Events API, если пользователь не вызывает событие копирования. Ведь так?

Создаём кнопку копирования, записывающую произвольные типы данных


Я попробовал нажимать на кнопки Copy в разных веб-приложениях, а затем изучал, что записывалось в буфер обмена. Результаты получились интересными.

В Google Docs есть кнопка Copy, которую можно найти в меню правой клавиши мыши.


Эта кнопка копирования записывает в буфер обмена три описания:

  • text/plain,
  • text/html и
  • application/x-vnd.google-docs-document-slice-clip+wrapped

Примечание: третье описание содержит данные JSON.

Кнопка записывает произвольный тип данных в буфер обмена, а значит, не использует async Clipboard API. Как же это было реализовано через обработчик нажатия на пункт меню?

Я запустил профилировщик, нажал на кнопку копирования и изучил результаты. Оказалось, нажатие на эту кнопку запускает вызов document.execCommand("copy").


Это меня удивило. Первым делом я подумал: «Разве execCommand — это не старый нерекомендуемый способ копирования текста в буфер обмена?».

Да, это так, но у Google были свои причины использовать его. Особенность execCommand заключается в том. что он позволяет нам программно вызвать надёжное событие копирования, как будто пользователь сам вызвал команду копирования.

document.addEventListener("copy", (e) => {
  console.log("e.isTrusted is " + e.isTrusted);
});

document.execCommand("copy");
//=> "e.isTrusted is true"

Примечание: для вызова события копирования браузер Safari требует активного выбора execCommand("copy"). Такой выбор можно имитировать добавлением в DOM непустого элемента ввода и его выбором до вызова execCommand("copy"), после чего элемент ввода можно удалить из DOM.

Итак, использование execCommand позволяет нам записывать в буфер обмена произвольные типы данных. Круто!

А как насчёт вставки? Можно ли использовать execCommand("paste")?

Создаём кнопку вставки


Давайте попробуем нажать на кнопку Paste в Google Docs и посмотрим, что она делает.


На моём Macbook открылось всплывающее окно с сообщением, что для использования кнопки вставки нужно установить расширение.


Как ни странно, на ноутбуке с Windows кнопка без проблем заработала.

Забавно. Откуда такое расхождение? Проверить, действительно ли она работает, можно проверить, выполнив queryCommandSupported("paste"):

document.queryCommandSupported("paste");

На Macbook я получил false в Chrome и Firefox, но true в Safari.

Заботясь о конфиденциальности, Safari потребовал от меня подтвердить действие вставки. Думаю, это очень хорошая идея. Это чётко даёт понять, что веб-сайт будет выполнять считывание из моего буфера обмена.


На ноутбуке с Windows я получил true в Chrome и Edge, но false в Firefox. Расхождение в случае с Chrome меня удивило. Почему Chrome разрешает execCommand("paste") в Windows, но не в macOS? Мне не удалось найти никакой информации по этому поводу.

Меня удивило то, что Google не пытается откатиться к async Clipboard API, когда execCommand("paste") недоступен. Хотя он не смог бы считать с его помощью описание application/x-vnd.google-[...], HTML-описание содержит внутренние ID, которые можно использовать.

<!-- Слегка подчищенное HTML-описание -->
<meta charset="utf-8">
<b id="docs-internal-guid-[guid]" style="...">
  <span style="...">Copied text</span>
</b>

Кнопка вставки используется и в веб-приложении Figma, в котором был выбран совершенно иной подход. Давайте посмотрим, что сделали разработчики.

Копирование и вставка в Figma


Figma — это веб-приложение (в его нативном приложении используется Electron). Посмотрим, что его кнопка копирования записывает в буфер обмена.


Кнопка копирования Figma записывает в буфер обмена два описания: text/plain и text/html. Поначалу это меня удивило. Как Figma собирается представить всё разнообразие своих функций структуры и стилизации в голом HTML?

Но взглянув на HTML, мы можем увидеть два пустых элемента span со свойствами data-metadata и data-buffer:

<meta charset="utf-8">
<div>
  <span data-metadata="<!--(figmeta)eyJma[...]9ifQo=(/figmeta)-->"></span>
  <span data-buffer="<!--(figma)ZmlnL[...]P/Ag==(/figma)-->"></span>
</div>
<span style="white-space:pre-wrap;">Text</span>

Примечание: строка data-buffer для пустого фрейма имеет длину порядка 26 тысяч символов. Похоже, для непустых фреймов длина data-buffer растёт от этого размера линейно в зависимости от скопированного содержимого.

Похоже на base64. Фрагмент eyJ в начале чётко даёт нам понять, что data-metadata — это строка JSON, закодированная в base64. Выполнив JSON.parse(atob()) для data-metadata, получим:

{
  "fileKey": "4XvKUK38NtRPZASgUJiZ87",
  "pasteID": 1261442360,
  "dataType": "scene"
}

Примечание: я заменил реальные fileKey и pasteID.

Но что насчёт большого свойства data-buffer? Декодировав его по Base64, получаем следующее:

fig-kiwiF\x00\x00\x00\x1CK\x00\x00µ½\v\x9CdI[...]\x197Ü\x83\x03

Похоже на двоичный формат. Немного порыскав (воспользовавшись подсказкой в виде fig-kiwi), я выяснил, что это формат сообщений Kiwi (созданный сооснователем и бывшим CTO Figma Эваном Уоллесом), который используется для кодирования файлов .fig.

Так как Kiwi — это формат, основанный на схеме, похоже, мы не сможем спарсить его данные, не зная схему. Однако, к счастью, Эван создал публичный парсер файлов .fig. Давайте попробуем вставить в него буфер!

Для преобразования буфера в файл .fig я написал небольшой скрипт, генерирующий Blob URL:

const base64 = "ZmlnL[...]P/Ag==";
const blob = base64toBlob(base64, "application/octet-stream");

console.log(URL.createObjectURL(blob));
//=> blob:<origin>/1fdf7c0a-5b56-4cb5-b7c0-fb665122b2ab

Затем я скачал получившийся блоб как файл .fig, загрузил его в парсер файлов .fig, и вуаля:


То есть копирование в Figma работает так: создаётся маленький файл Figma, кодируется в base64, полученная строка base64 записывается в атрибут data-buffer пустого элемента HTML span и сохраняется в буфере обмена пользователя.

Преимущества копипастинга HTML


Поначалу это показалось мне глупым решением, но у такого подхода есть большое преимущество. Чтобы понять его, подумаем о том, как Clipboard API веба взаимодействуют с Clipboard API различных операционных систем.

Windows, macOS и Linux используют разные форматы записи данных в буфер обмена. Для записи в буфер обмена HTML в Windows есть CF_HTML, а в macOS — NSPasteboard.PasteboardType.html.

У всех операционных систем есть типы для «стандартных» форматов (текста без форматирования, HTML и изображений PNG). Но формат какой операционной системы должен использовать браузер, когда пользователь пытается записать в буфер обмена произвольный тип данных наподобие application/foo-bar?

Идеально подходящего варианта нет, поэтому браузер не записывает это описание в буфер обмена ОС в распространённых форматах. Это описание существует в буфере обмена ОС только в рамках собственного формата буфера обмена браузера. Благодаря этому можно копировать и вставлять произвольные типы данных между вкладками браузера, но не между приложениями.

Именно поэтому использование распространённых типов данных text/plain, text/html и image/png настолько удобно. Они согласованы с распространёнными форматами буферов обмена ОС, а потому легко могут считываться другими приложениями, благодаря чему копирование и вставка работают между приложениями. В случае Figma использование text/html позволяет копировать элемент Figma из figma.com в браузер, а затем вставлять его в нативное приложение Figma, и наоборот.

Что браузеры записывают в буфер обмена в случае произвольных типов данных?


Мы узнали, что можно записывать и считывать из буфера обмена произвольные типы данных между вкладками браузера, но не между приложениями. Но что же конкретно браузеры записывают в нативный буфер обмена ОС, когда мы записываем произвольные типы данных в веб-буфер обмена?

Я запустил следующий код в слушателе copy в каждом из популярных браузеров на моём Macbook:

document.addEventListener("copy", (e) => {
  e.preventDefault();
  e.clipboardData.setData("text/plain", "Hello, world");
  e.clipboardData.setData("text/html", "<em>Hello, world</em>");
  e.clipboardData.setData("application/json", JSON.stringify({ type: "Hello, world" }));
  e.clipboardData.setData("foo bar baz", "Hello, world");
});

Затем я изучил буфер обмена при помощи Pasteboard Viewer. Chrome добавляет в Pasteboard четыре элемента:

  • public.html содержит HTML-описание.
  • public.utf8-plain-text содержит описание в тексте без форматирования.
  • org.chromium.web-custom-data содержит произвольные описания.
  • org.chromium.source-url содержит URL веб-страницы, с которой было выполнено копирование.

В org.chromium.web-custom-data мы видим скопированные данные:


Предполагаю, что "î" с диакритикой и странные разрывы строк вызваны неправильным отображением каких-то ограничителей.

Firefox тоже создаёт элементы public.html и public.utf8-plain-text, но записывает произвольные данные в org.mozilla.custom-clipdata. URL источника он в отличие от Chrome не хранит.

Как можно было догадаться, Safari тоже создаёт элементы public.html и public.utf8-plain-text. Он записывает произвольные данные в com.apple.WebKit.custom-pasteboard-data; любопытно, что он также хранит там полный список описаний (включая текст без форматирования и HTML) и URL источника.

Примечание: Safari позволяет выполнять копирование и вставку произвольных данных между вкладками браузера, если URL источника (домен) одинаков, но не между разными доменами. Похоже, такого ограничения нет в Chrome и Firefox (хоть Chrome и хранит URL источника).

Доступ к сырому буферу данных для веба


Предложение Raw Clipboard Access было создано в 2019 году, в нём сформулирована идея API, предоставляющего веб-приложениям сырой доступ для чтения и записи к нативным буферам обмена ОС.

В отрывке из раздела Motivation на chromestatus.com фичи Raw Clipboard Access лаконично изложены её преимущества:

Без Raw Clipboard Access веб-приложения ограничены маленьким подмножеством форматов и неспособны взаимодействовать с длинным хвостом форматов. Например, Figma и Photopea неспособны взаимодействовать с большинством форматов изображений.

Однако в итоге предложение Raw Clipboard Access не было принято из-за опасений за безопасность, связанных с возможностью эксплойтов, например, удалённого исполнения кода в нативных приложениях.

Самое новое из предложений по записи произвольных типов данных в буфер обмена — это Web Custom Formats (часто называемое pickling).

Web Custom Formats (Pickling)


В 2022 году в Chromium была реализована поддержка Web Custom Formats в async Clipboard API.

Это позволяет веб-приложениям записывать произвольные типы данных через async Clipboard API благодаря добавлению к типу данных префикса "web ":

// Создаём блоб JSON
const json = JSON.stringify({ message: "Hello, world" });
const jsonBlob = new Blob([json], { type: "application/json" });

// Записываем блоб JSON в буфер обмена как Web Custom Format
const clipboardItem = new ClipboardItem({
  [`web ${jsonBlob.type}`]: jsonBlob,
});
navigator.clipboard.write([clipboardItem]);

Они считываются при помощи async Clipboard API как любой другой формат данных:

const items = await navigator.clipboard.read();
for (const item of items) {
  if (item.types.includes("web application/json")) {
    const blob = await item.getType("web application/json");
    const json = await blob.text();
    // Выполняем действия с JSON...
  }
}

Любопытнее то, что записывается в нативный буфер обмена. При записи произвольных веб-форматов в нативный буфер обмена ОС записывается следующее:

  • Таблица сопоставления типов данных с именами элементов в буфере обмена
  • Элементы буфера обмена для каждого типа данных

В macOS таблица сопоставления записывается в org.w3.web-custom-format.map, содержимое которого выглядит так:

{
  "application/json": "org.w3.web-custom-format.type-0",
  "application/octet-stream": "org.w3.web-custom-format.type-1"
}

Ключи org.w3.web-custom-format.type-[index] соответствуют элементам буфера обмена ОС, содержащим несанированные данные из блобов. Это позволяет нативным приложениям проверять в таблице, доступно ли конкретное описание, а затем считывать несанированное содержимое из соответствующего элемента буфера обмена.

Примечание: в Windows и Linux используются разные стандарты наименований в таблице сопоставления и элементах буфера обмена.

Это позволяет избежать проблем, связанных с доступом к сырому буферу обмена, так как веб-приложения не могут записывать несанированные данные в том формате буфера обмена ОС, который им нужен. Это проводит к возникновению компромисса во взаимодействиях, который в явном виде сформулирован в спецификации Pickling for Async Clipboard API:

Что не является целью спецификации


Обеспечение возможности взаимодействия с нативными легаси-приложениями без обновления. Этот вопрос был исследован в предложении о работе с сырыми буферами обмена и может быть глубже исследован в будущем, но он предполагает большие сложности для безопасности (удалённое исполнение кода в нативных для системы приложениях).

Это означает, что для взаимодействия буфера обмена с веб-приложениями при использовании произвольных типов данных нативные приложения необходимо обновлять.

Web Custom Formats доступны в браузерах на основе Chromium с 2022 года, но в других браузерах это предложение пока не реализовано.

В заключение


На текущий момент не существует идеального способа записи произвольных типов данных в буфер обмена, работающего во всех браузерах. Методика Figma с записью строк base64 в HTML-описание — это грубый, но эффективный способ, позволяющий обходить множество ограничений API буфера обмена. Мне кажется, это хороший способ перемещения произвольных типов данных при помощи буфера обмена.

Предложение Web Custom Formats кажется мне перспективным, и я надеюсь, что его реализуют во всех популярных браузерах. Похоже, оно позволит записывать в буфер обмена произвольные типы данных безопасным и практичным образом.

Комментарии (1)


  1. Kandimus
    12.09.2024 09:43
    +1

    Кто у кого украл?
    https://habr.com/ru/companies/beget/articles/841446/