clipboard: fix copy/paste on Firefox (#4003)

So, here's what's up:
- in Firefox, in version 127 `navigator.clipboard.write` support was
added:
https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/127
- previously, Firefox was going down an if/else branch where
`navigator.clipboard.write` isn't present, we use
`navigator.clipboard.writeText`
- Now, that Firefox is using the more common path, it now puts
MIME-types on the clipboard, both HTML and plaintext.
- _However_, on Firefox, it uses a different sanitization algorithm than
the Blink engine does and it ends up scrubbing out the `<tldraw>` fake
HTML tag:
https://developer.chrome.com/docs/web-platform/unsanitized-html-async-clipboard
- And, unfortunately, Firefox doesn't support setting `unsanitized` on
the ClipboardItem: https://caniuse.com/?search=unsanitized
- see also:
https://developer.chrome.com/docs/web-platform/unsanitized-html-async-clipboard
- So, the workaround here is to just use `<div data-tldraw>`. I'm not
completely happy with it since the ending `</div>` tag assumes there's
no nesting but ¯\\_(ツ)_/¯ it's fine in this case.
- Plus, I wanted to make sure that in the wild no one was relying on
this format being what was on the clipboard. Searching across all of
GitHub it seems like it'll be fine.

- The longer term, better solution, would be to use custom HTML formats:
https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api
- However, of course, Firefox doesn't support that yet either 🙃
https://caniuse.com/?search=web%20custom%20format
- see also:
https://developer.chrome.com/blog/web-custom-formats-for-the-async-clipboard-api

Talked with Alex, and what we could do down the line is copy SVG-in-HTML
and then include `data-info` attributes that had data we could extract
per shape. Something like that :handwavy: :)

I'll hotfix this once it lands.

### Change Type

<!--  Please select a 'Scope' label ️ -->

- [x] `sdk` — Changes the tldraw SDK
- [ ] `dotcom` — Changes the tldraw.com web app
- [ ] `docs` — Changes to the documentation, examples, or templates.
- [ ] `vs code` — Changes to the vscode plugin
- [ ] `internal` — Does not affect user-facing stuff

<!--  Please select a 'Type' label ️ -->

- [x] `bugfix` — Bug fix
- [ ] `feature` — New feature
- [ ] `improvement` — Improving existing features
- [ ] `chore` — Updating dependencies, other boring stuff
- [ ] `galaxy brain` — Architectural changes
- [ ] `tests` — Changes to any test code
- [ ] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
- [ ] `dunno` — I don't know

### Release Notes

- Clipboard: fix copy/paste in Firefox 127+
This commit is contained in:
Mime Čuvalo 2024-06-24 14:10:38 +01:00 committed by GitHub
parent 2d2a7ea76f
commit 9850ef93e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -323,7 +323,7 @@ async function handleClipboardThings(editor: Editor, things: ClipboardThing[], p
thing.source.then((text) => { thing.source.then((text) => {
// first, see if we can find tldraw content, which is JSON inside of an html comment // first, see if we can find tldraw content, which is JSON inside of an html comment
const tldrawHtmlComment = text.match(/<tldraw[^>]*>(.*)<\/tldraw>/)?.[1] const tldrawHtmlComment = text.match(/<div data-tldraw[^>]*>(.*)<\/div>/)?.[1]
if (tldrawHtmlComment) { if (tldrawHtmlComment) {
try { try {
@ -525,7 +525,7 @@ const handleNativeOrMenuCopy = async (editor: Editor) => {
.filter(isNonNull) .filter(isNonNull)
if (navigator.clipboard?.write) { if (navigator.clipboard?.write) {
const htmlBlob = new Blob([`<tldraw>${stringifiedClipboard}</tldraw>`], { const htmlBlob = new Blob([`<div data-tldraw>${stringifiedClipboard}</div>`], {
type: 'text/html', type: 'text/html',
}) })
@ -546,7 +546,7 @@ const handleNativeOrMenuCopy = async (editor: Editor) => {
}), }),
]) ])
} else if (navigator.clipboard.writeText) { } else if (navigator.clipboard.writeText) {
navigator.clipboard.writeText(`<tldraw>${stringifiedClipboard}</tldraw>`) navigator.clipboard.writeText(`<div data-tldraw>${stringifiedClipboard}</div data-tldraw>`)
} }
} }
} }