-
Notifications
You must be signed in to change notification settings - Fork 188
Description
Description
The Copy to clipboard button in the Export section of a record's landing page does not work in Safari. Clicking it produces:
Unhandled Promise Rejection: NotAllowedError: The request is not allowed by the user agent
or the platform in the current context, possibly because the user denied permission.
This was reported downstream in zenodo/zenodo-rdm#1266.
Root Cause
In CopyButton.js, the SimpleCopyButton.handleClick method is async and calls await this.fetchUrl(url) before navigator.clipboard.writeText(). Safari requires clipboard writes to occur synchronously within the user-gesture call stack. The await fetch() suspends execution, so the subsequent writeText() is no longer in the user gesture's context, and Safari rejects it with NotAllowedError.
// Current broken code (for Safari)
handleClick = async () => {
const { url, text, onCopy } = this.props;
let textToCopy = text;
if (url) {
textToCopy = await this.fetchUrl(url); // breaks user-gesture chain
}
await navigator.clipboard.writeText(textToCopy); // Safari rejects
onCopy(text);
};DOI/Citation copy buttons work fine because they pass text directly (no fetch needed). Only the Export copy button is affected because it uses the url prop, triggering the async fetch.
Introduced In
PR #3106 (merged July 16, 2025) replaced react-copy-to-clipboard with direct navigator.clipboard.writeText() calls. The react-copy-to-clipboard library handled this case correctly; the replacement did not account for Safari's stricter clipboard security model.
Proposed Fix
Use the ClipboardItem API, which accepts a Promise as its value. This keeps navigator.clipboard.write() synchronous within the user gesture while resolving the fetch data asynchronously inside the Clipboard API's internal handling:
handleClick = () => {
const { url, text, onCopy } = this.props;
if (url) {
const textPromise = fetch(url).then((r) => r.text());
if (typeof ClipboardItem !== "undefined") {
// Safari & Chrome: ClipboardItem accepts a Promise
const item = new ClipboardItem({
"text/plain": textPromise.then(
(t) => new Blob([t], { type: "text/plain" })
),
});
navigator.clipboard.write([item]).then(() => onCopy(text));
} else {
// Firefox fallback: ClipboardItem may not support Promise values
textPromise.then((t) =>
navigator.clipboard.writeText(t).then(() => onCopy(text))
);
}
} else {
navigator.clipboard.writeText(text).then(() => onCopy(text));
}
};Reference: How to use Clipboard API in Safari
Affected File
invenio_app_rdm/theme/assets/semantic-ui/js/invenio_app_rdm/components/CopyButton.js
Browser Compatibility
| Browser | ClipboardItem with Promise |
async/await + writeText after fetch |
|---|---|---|
| Safari 13.1+ | ✅ | ❌ (user gesture lost) |
| Chrome 76+ | ✅ | ✅ |
| Firefox 127+ | ✅ |
I'll submit a PR with the fix shortly.