- A área de transferência da web pode armazenar, junto com um único dado copiado, várias representações MIME, permitindo que cada app escolha apenas o formato de que precisa ao colar
- A async Clipboard API é limitada principalmente a
text/plain,text/htmleimage/png, então tipos personalizados comoapplication/jsontêm a gravação recusada por motivos de segurança - A antiga Clipboard Events API consegue lidar com strings de tipo arbitrárias, mas a área de transferência real do sistema só é alterada dentro de eventos de usuário confiáveis
- O Google Docs cria um evento de cópia confiável com
execCommand("copy"), e o Figma coloca dados personalizados em um atributo HTML em base64 para viabilizar a cópia entre navegador e apps nativos - Web Custom Formats libera tipos personalizados na async Clipboard API com o prefixo
"web ", mas exige navegadores baseados em Chromium e apps nativos atualizados
O modelo básico da área de transferência da web
- A área de transferência pode armazenar várias representações (representations) de uma única operação de cópia, junto com seus tipos MIME
text/plain: texto simplestext/html: HTMLimage/png: imagem PNG
- O Google Docs lê a representação
text/htmlpara preservar formatação como links, tamanho da fonte e cor - Apps como o VS Code, que precisam apenas do texto original, podem ler só a representação
text/plain
Restrições de tipos na async Clipboard API
navigator.clipboard.read()verifica ostypesdo item da área de transferência e então lê uma representação específica com algo comogetType("text/html")navigator.clipboard.write()pode gravar de uma vez váriosBlobs em umClipboardItem, usando os tipos MIME como chave- Ao tentar gravar
application/jsonem umClipboardItem, ocorre a exceçãoType application/json not supported on write - A especificação W3C Clipboard determina que tipos diferentes de
text/plain,text/htmleimage/pngsejam rejeitados emwrite application/jsonesteve na lista de tipos de dados obrigatórios de 2012 até 2021, mas foi removido em w3c/clipboard-apis#155- Antes da mudança, os tipos obrigatórios eram 16 para leitura e 8 para escrita
- A lista encolheu porque os navegadores não davam suporte a muitos tipos obrigatórios devido a preocupações de segurança
- A especificação alerta que scripts não confiáveis podem colocar na área de transferência dados capazes de explorar vulnerabilidades em softwares locais, por isso os tipos permitidos são restringidos
Clipboard Events API e isTrusted
ClipboardEventocorre em eventos de copy, cut e paste, e fornece um objetoDataTransferpela propriedadeclipboardData- Em um evento de copy, é possível gravar dados com
e.clipboardData.setData("text/plain", "...")esetData("text/html", "...") - Em um evento de paste, é possível ler a representação HTML com
e.clipboardData.getData("text/html") clipboardData.setData("application/json", json)não lança exceção, e o manipulador de paste também consegue ver o tipoapplication/jsone a string JSONclipboardDataé uma interface bem mais antiga que a async Clipboard API- A async Clipboard API foi adicionada à especificação em 2017
clipboardData,setDataegetDatajá apareciam no rascunho da W3C de 2006- Esse rascunho refletia em grande parte funcionalidades implementadas no Internet Explorer
- Como o Internet Explorer 4 foi lançado em 1997, a interface
clipboardDatapode ser vista como um recurso com pelo menos 26 anos na época em que o texto foi escrito
- Os tipos MIME entraram na especificação em 2011, mas a característica de aceitar qualquer string como argumento de tipo em
setData()permanece até hoje Event.isTrustedé uma propriedade somente leitura que só valetruequando o evento é gerado pelo user agent- Um evento de copy causado pelo usuário ao pressionar Command+C terá
isTrustedigual atrue - Um evento sintético criado com
dispatchEvent()teráisTrustedigual afalse
- Um evento de copy causado pelo usuário ao pressionar Command+C terá
- Eventos sintéticos de copy/cut não podem modificar a área de transferência do sistema, e eventos sintéticos de paste não podem acessar os dados reais da área de transferência do sistema
- Portanto, a Clipboard Events API consegue lidar com tipos arbitrários, mas a interação com a área de transferência real fica restrita aos manipuladores de eventos copy/paste gerados pelo user agent
Gravar tipos personalizados a partir de um botão Copy
- O botão Copy do menu de clique direito do Google Docs grava três representações na área de transferência
text/plaintext/htmlapplication/x-vnd.google-docs-document-slice-clip+wrapped
- A terceira representação é um tipo personalizado que contém dados JSON e não pode ser gravada apenas com a async Clipboard API
- Ao clicar no botão Copy, o Google Docs chama
document.execCommand("copy") execCommand("copy")consegue disparar programaticamente um evento copy confiável, como se o usuário tivesse executado um comando de cópia- No Safari,
execCommand("copy")precisa de uma seleção ativa para disparar o evento copy- É possível criar essa seleção adicionando ao DOM um input não vazio, selecionando-o, chamando
execCommand("copy")e depois removendo-o
- É possível criar essa seleção adicionando ao DOM um input não vazio, selecionando-o, chamando
Diferenças entre navegador e sistema operacional no botão Paste
- O botão Paste do Google Docs exigia a instalação de uma extensão no macOS, mas funcionava em um notebook com Windows
- É possível verificar se o comando paste é suportado com
document.queryCommandSupported("paste")- No macOS, Chrome e Firefox retornam
false, e Safari retornatrue - No Windows, Chrome e Edge retornam
true, e Firefox retornafalse
- No macOS, Chrome e Firefox retornam
- O Safari mostra um prompt de confirmação para deixar claro que o site está lendo a área de transferência
- Não foi confirmado por que o Chrome permite
execCommand("paste")no Windows, mas não no macOS - Quando não pode usar
execCommand("paste"), o Google Docs não faz fallback para a async Clipboard API- A async Clipboard API não consegue ler a representação
application/x-vnd.google-[...] - A representação HTML contém IDs internos
- A async Clipboard API não consegue ler a representação
O copiar e colar baseado em HTML do Figma
- O botão Copy do Figma grava duas representações na área de transferência
text/plaintext/html
- A representação HTML contém dois
spanvazios, cada um com os atributosdata-metadataedata-buffer data-metadataé uma string JSON codificada em base64; ao decodificá-la, aparecem valores comofileKey,pasteIDedataTypedata-buffertem cerca de 26.000 caracteres mesmo ao copiar apenas um frame vazio, e seu tamanho parece crescer linearmente conforme aumenta a quantidade de conteúdo copiado- Ao decodificar
data-bufferde base64, surgem dados binários que começam comfig-kiwi, que correspondem ao formato de mensagens Kiwi- Kiwi é um formato criado por Evan Wallace, cofundador e ex-CTO do Figma
- Ele é usado na codificação de arquivos
.fig
- Com o parser de arquivos
.figpublicado por Evan Wallace, é possível converterdata-bufferem um arquivo.figpara inspecioná-lo - O Figma cria um pequeno arquivo Figma, codifica esse arquivo em base64 e o coloca no atributo
data-bufferde umspanHTML vazio para armazená-lo na área de transferência
Por que usar a representação HTML
- Windows, macOS e Linux oferecem formatos diferentes do sistema operacional para gravar dados na área de transferência
- O Windows oferece
CF_HTMLpara HTML - O macOS oferece
NSPasteboard.PasteboardType.html
- O Windows oferece
- Texto simples, HTML e imagens PNG podem ser mapeados para formatos padrão em vários sistemas operacionais
- Tipos arbitrários como
application/foo-barnão se encaixam bem em formatos comuns da área de transferência do SO - Os navegadores não gravam essas representações personalizadas nos formatos comuns do SO; em vez disso, as armazenam em formatos personalizados de área de transferência específicos do navegador
- Como resultado, tipos personalizados podem ser copiados e colados entre abas do navegador
- Mas não funcionam em operações de copiar e colar entre aplicativos diferentes
- Ao usar
text/html, o conteúdo pode ser mapeado para um formato comum do SO, ficando mais fácil para outros aplicativos lerem - O Figma usa
text/htmlpara permitir copiar e colar elementos entrefigma.comno navegador e o app nativo do Figma
Como cada navegador armazena dados personalizados
- No macOS, ao gravar
text/plain,text/html,application/jsonefoo bar bazdentro de um evento copy e inspecionar no Pasteboard Viewer, o modo de armazenamento varia entre navegadores - O Chrome adiciona quatro itens ao Pasteboard
public.html: representação HTMLpublic.utf8-plain-text: representação de texto simplesorg.chromium.web-custom-data: representação personalizadaorg.chromium.source-url: URL da página web onde a cópia foi feita
- O Firefox também cria
public.htmlepublic.utf8-plain-text, mas grava os dados personalizados emorg.mozilla.custom-clipdata- Diferentemente do Chrome, ele não armazena a URL de origem
- O Safari também cria
public.htmlepublic.utf8-plain-text, e grava os dados personalizados emcom.apple.WebKit.custom-pasteboard-data- Nesse item, ele também armazena a lista completa de representações, incluindo texto simples e HTML, além da URL de origem
- O Safari só permite copiar e colar tipos personalizados entre abas do navegador quando o domínio da URL de origem é o mesmo
- No Chrome e no Firefox, essa mesma restrição não é observada
Raw Clipboard Access e Web Custom Formats
- Raw Clipboard Access foi uma API proposta em 2019 para dar a aplicações web acesso bruto de leitura e escrita à área de transferência nativa do sistema operacional
- Raw Clipboard Access tratava do problema de aplicações web ficarem limitadas a poucos formatos, o que dificulta a interoperabilidade do Figma e do Photopea com a maioria dos formatos de imagem
- A proposta não avançou por causa de preocupações de segurança, como execução remota de código em aplicativos nativos
- Web Custom Formats é uma proposta para permitir tipos personalizados na async Clipboard API, implementada pelo Chromium em 2022
- Web Custom Formats usa o prefixo
"web "antes do tipo MIME- Exemplo:
web application/json - Na leitura, também se usa a async Clipboard API com
read()egetType("web application/json")
- Exemplo:
- Web Custom Formats grava dois tipos de dados na área de transferência nativa do sistema operacional
- Um mapeamento entre o tipo de dado e o nome do item da área de transferência
- Um item da área de transferência para cada tipo de dado
- No macOS, o mapeamento é armazenado em
org.w3.web-custom-format.map- Exemplo:
"application/json": "org.w3.web-custom-format.type-0"
- Exemplo:
- O item
org.w3.web-custom-format.type-[index]contém os dados brutos do Blob - Essa abordagem impede que aplicações web gravem diretamente dados brutos no formato de área de transferência do SO que quiserem, evitando os problemas de segurança do Raw Clipboard Access
- Em compensação, a interoperabilidade com aplicativos nativos legados sem atualização não é um objetivo
- Os aplicativos nativos precisam ser atualizados para ler o formato Web Custom Formats
- Web Custom Formats pode ser usado em navegadores baseados em Chromium desde 2022, mas ainda não foi implementado em outros navegadores
Opção unsanitized
- Ao ler a área de transferência com a async Clipboard API, o navegador pode sanitizar os dados
- Pode remover tags script potencialmente perigosas do HTML
- Pode recodificar imagens PNG para evitar ataques como zip bomb
- A opção
unsanitizedemread()permite solicitar dados não sanitizados - Atualmente, essa opção é suportada apenas em navegadores baseados em Chromium
- Foi adicionada no fim de 2023
- Outros navegadores podem oferecer suporte no futuro, mas há sinais de que isso é pouco provável no Safari
Opções realistas
- Ainda não existe um jeito de gravar tipos personalizados na área de transferência que funcione bem em todos os navegadores
- A abordagem do Figma, de colocar uma string em base64 dentro da representação HTML, é rudimentar, mas contorna várias limitações da Clipboard API e permite transportar dados personalizados pela área de transferência
- Web Custom Formats aponta para um caminho mais seguro e prático para usar tipos personalizados, mas ainda depende de implementação ampla nos principais navegadores
1 comentários
Opiniões do Hacker News
Bom texto. Entendi por que colar com/sem formatação no Google Docs no Firefox do Mac não funcionava direito
É interessante que o Google esteja usando uma API obsoleta para tentar inserir seu próprio formato de conteúdo na área de transferência
A forma mais confiável de enviar dados personalizados de um app para a área de transferência parece ser embutir os dados no HTML, como o Figma faz
Também há a vantagem de poder definir, por meio de uma mensagem em HTML, até o comportamento de falha quando o app receptor não dá suporte
O WordPress também esbarra nesse problema. Se você recortar e colar vários parágrafos no modo editor, pode dar muito errado
Provavelmente porque os parágrafos, na prática, são áreas DIV diferentes, sob lógicas de controle e efeitos diferentes
Além disso, a máquina acaba promovendo o que eu achava que era ASCII para UTF-8 ou ISO Latin-1 e, como o Clippy, faz algo do tipo “deixei suas aspas mais bonitas, você vai me agradecer depois”, embora eu nunca tenha pedido isso. Se eu quisesse `this', não teria digitado 'this'
Quando eu era estudante, JavaScript conseguia ler a área de transferência sem consentimento do usuário, e criei um site chamado getpasted
Era um site que colava automaticamente o conteúdo da área de transferência e o enviava para um banco de dados público; naturalmente, algumas pessoas não gostaram
Ainda assim, como projeto para mostrar que a área de transferência podia ser lida a qualquer momento, foi válido
Sindre Sorhus, criador do Pasteboard Manager[0], também fez a biblioteca de atalhos Actions[1] e vários apps para iPhone e Mac. Não sei se ele está no HN
0. https://apps.apple.com/us/app/pasteboard-viewer/id1499215709
https://www.npmjs.com/~sindresorhus
A combinação de amplitude e profundidade é especialmente boa. O texto cobre diferenças entre sistemas e navegadores, mas também explica armadilhas e alternativas de contorno
Sobre a parte de obter dados “privados” no navegador, o web app do meu banco exibiu meu hostname, aluminium, ao fazer login e exigiu autenticação de dois fatores
Não sei como ele descobriu isso. No celular, ao acessar, ele também conseguia ver a mensagem de texto com o código de 2FA e colá-lo automaticamente. Era um ambiente Pixel+Chrome ou Linux+Chrome
O hostname é mais difícil. Seria bom verificar se você realmente nunca passou essa informação ao banco em alguma forma, como um nome de usuário. Também fiquei curioso para saber qual era o banco
Este texto mostra bem por que web apps nunca podem ser tão bons quanto apps nativos
Web apps são sempre código “não confiável”, então o acesso aos recursos da máquina local é limitado de forma arbitrária e artificial
Não estamos mais na internet inicial, quando script kiddies faziam bagunça, nem em um espaço usado só por geeks. Estamos em uma era em que organizações ligadas a governos atacam infraestrutura crítica com ransomware para financiar programas nucleares. Acesso arbitrário aos recursos da máquina local é exatamente o que torna esse tipo de coisa possível
Web apps em navegadores modernos naturalmente ficam dentro de uma sandbox mais forte, mas, em sistemas operacionais modernos, apps nativos também são tratados como não confiáveis. Ao executar um app novo, aparece uma caixa de diálogo de confirmação antes que ele acesse algo fora do diretório isolado de dados do app
Mas fiquei curioso para saber qual era a extensão do Chrome que disparou o alerta no MacBook