fix: make ody-copy-button work in cross-origin iframes - #12
Merged
Conversation
Use a synchronous execCommand('copy') as the source of truth so copying
succeeds inside cross-origin iframes (where the async Clipboard API is
blocked by default) and legacy contexts. The async
navigator.clipboard.writeText becomes a best-effort enhancement attempted
only when the sync path can't run; its late result never touches the UI,
since it settles after the user-gesture window.
The copy CustomEvent shape ({ value, ok }, bubbles) and all state/feedback
behavior are unchanged. Behavior-only fix, no public API change.
Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Summary
<ody-copy-button>relied solely onnavigator.clipboard.writeText, which is blocked by default in cross-origin iframes (Chrome, noallow="clipboard-write"). The promise rejected, the button flipped to its red error state, and nothing was copied — with no fallback.This makes a synchronous
document.execCommand('copy')the source of truth, so the copy runs inside the user-gesture window and works in cross-origin iframes and legacy contexts. The async Clipboard API becomes a best-effort enhancement, attempted only when the sync path can't run; its late result never touches the UI (it settles after the gesture window closes).Changes
src/ui/components/copy-button.ts: add#execCopy(value)(off-screen<textarea>+execCommand('copy'), guarded for empty value and SSR/no-DOM); rewrite#onClickto copy synchronously and reflect the result immediately, falling back to best-effort async only when sync can't run.test/ui/batch3b.test.ts: rewrite the copy-button suite for the sync-first behavior — sync success, sync-fail → best-effort async (async resolve and reject; late result never flips the UI), both paths unavailable, empty value short-circuit, throwingexecCommand, timer-clear, disconnect-during-revert.Unchanged contracts
copyCustomEventdetail{ value, ok },bubbles: true.#feedback,#state,success-duration, auto-reset timer, icons, appearance classes.Verification
tsc --noEmit: cleaneslint .: cleanvitest run --coverage: 617 passed, 95% gate green (copy-button.ts100% stmts/funcs/lines)allow="clipboard-write"in Chrome and confirm the green check.Once this ships, the downstream n8n connector's
navigator.clipboard.writeTextmonkeypatch workaround can be deleted.🤖 Generated with Claude Code