Consolidate clipboard helpers into one Safari-safe copyToClipboard - #2033
Merged
Merged
Conversation
The LinkedIn page had its own execCommand-only copy helper (added for #1683, "iPadOS Safari: looks like it works but the clipboard is blank"), while ShareButtons, quote-share, and the `y` shortcut used a writeText-first helper. Merge them into src/utils/copy-to-clipboard.ts. The helper now runs textarea + execCommand('copy') synchronously inside the user gesture first, because it reports failure honestly, and only then falls back to navigator.clipboard.writeText (also called in the same tick). writeText-first can't detect #1683: the promise resolves, so the fallback never runs. In Playwright WebKit, writeText resolves but a subsequent paste still returns the old clipboard contents, which matches the bug report. The fix commit's code already called writeText synchronously, so lost user activation was not the cause. Adds e2e/clipboard.spec.ts, which clicks every copy surface in Chromium and WebKit and reads the clipboard back (readText in Chromium, paste in WebKit, which rejects readText). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
There were two clipboard helpers that disagreed:
src/scripts/linkedin-copy.ts: synchronous,execCommand('copy')only, with a comment saying Safari'swriteTextsilently fails.src/utils/copy-to-clipboard.ts:writeTextfirst, then anexecCommandfallback. Used by ShareButtons, quote-share, and theyshortcut.Now there's one:
src/utils/copy-to-clipboard.ts.linkedin-copy.tskeeps only its button wiring, and its unit tests moved intocopy-to-clipboard.test.ts.Why the Safari workaround existed
execCommand.writeTextas the first thing in the click handler. Noawaitcame before it, so lost transient user activation was not the cause.writeTextwithout writing came from Copilot and was never reproduced in that PR. LinkedIn Resume copy functionality not working #1683 has no follow-up confirming the fix on a device. I can't confirm the root cause either.writeText-first helper can't detect that failure. The promise resolves, so the fallback never runs.New order
execCommand('copy'), run synchronously before anyawait. It returnsfalsewhen it fails instead of pretending to succeed. This is the path that fixed LinkedIn Resume copy functionality not working #1683.navigator.clipboard.writeText, only if step 1 fails. It's called in the same tick, so it still has user activation.Other details:
Promise<boolean>everywhere.awaitahead of the copy.ClipboardItempromise path because no caller computes its text asynchronously.Verification
e2e/clipboard.spec.tsclicks every copy surface: LinkedIn field, LinkedIn description, share-bar link, inline quote, and theyshortcut. It asserts the success UI, logs which API ran, and reads the clipboard back.execCommanddid the copy, read back withreadText.execCommanddid the copy, read back by pasting into a textarea (WebKit rejectsreadTexteven inside a gesture).Standalone WebKit probes, not committed:
writeTextresolves, but a following paste still returns the previous clipboard contents, in both headless and headed runs. That's consistent with LinkedIn Resume copy functionality not working #1683, but it could also be a Playwright WebKit artifact: async clipboard writes and synthetic paste may not share a pasteboard in the automation build, and WebKit gives no other way to read the clipboard. So this is not a reproduction.writeTextsucceeds after a 50ms await and is rejected after 6s, so in Playwright WebKit the activation window ends somewhere between those two points. Short awaits are fine.Also ran: vitest (13 pass),
npm run check(0 errors), eslint on changed files, and knip (nothing new).Caveats
upgrade-insecure-requests. WebKit applies it tohttp://127.0.0.1, so no scripts load on a local preview. The new spec strips that directive when it fetches each page. The rest of theCROSS_BROWSER=1WebKit suite (for examplesearch-shortcuts) still fails locally for that reason. That was already broken before this PR and is left alone here.resume-linkedinandsearch-shortcutsnow skip on non-Chromium, because WebKit throws ongrantPermissions('clipboard-write').🤖 Generated with Claude Code