Bug 222262
| Summary: | Javascript Clipboard API write() does not work after await | ||
|---|---|---|---|
| Product: | WebKit | Reporter: | Felipe Ruiz <fruiz> |
| Component: | UI Events | Assignee: | Nobody <webkit-unassigned> |
| Status: | NEW | ||
| Severity: | Major | CC: | ap, bfan2, bfulgham, cdumez, christian, eoconnor, jer.noble, m.andrewgunn100, mjs, nolan, rik, rniwa, tomac, webkit-bug-importer, wenson_hsieh |
| Priority: | P2 | Keywords: | InRadar |
| Version: | Safari 14 | ||
| Hardware: | All | ||
| OS: | All | ||
| See Also: |
https://bugs.webkit.org/show_bug.cgi?id=225559 https://bugs.webkit.org/show_bug.cgi?id=286969 |
||
Felipe Ruiz
I'm using javascript Clipboard API to copy an image to the clipboard. It works in Chrome and Edge but not in Safari in spite of official documentation of Safari says that it's supported.
Check the documentation: https://webkit.org/blog/10855/
In this example (not my real code), write() throws an error:
document.getElementById("copy").addEventListener("click", async function() {
const response = await fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png');
const blob = await response.blob();
navigator.clipboard.write([new ClipboardItem({ "image/png": blob })])
.then(function () { console.log('copied'); })
.catch(function (error) { console.log(error); });
});
The given error is:
NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
| Attachments | ||
|---|---|---|
| Add attachment proposed patch, testcase, etc. |
Ryosuke Niwa
(In reply to Felipe Ruiz from comment #0)
> I'm using javascript Clipboard API to copy an image to the clipboard. It
> works in Chrome and Edge but not in Safari in spite of official
> documentation of Safari says that it's supported.
The issue is not so much async Clipboard API and the difference in the way user activation is tracked between Blink and WebKit.
> In this example (not my real code), write() throws an error:
>
> document.getElementById("copy").addEventListener("click", async function() {
> const response = await
> fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/
> PNG_transparency_demonstration_1.png');
> const blob = await response.blob();
>
> navigator.clipboard.write([new ClipboardItem({ "image/png": blob })])
> .then(function () { console.log('copied'); })
> .catch(function (error) { console.log(error); });
> });
You need to initiate the write inside click event handler as in:
document.getElementById("copy-html").addEventListener("click", event => {
navigator.clipboard.write([
new ClipboardItem({
"text/png": async () => {
const response = await fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png');
return await response.blob();
}
}),
]);
});
Felipe Ruiz
(In reply to Ryosuke Niwa from comment #1)
> (In reply to Felipe Ruiz from comment #0)
> > I'm using javascript Clipboard API to copy an image to the clipboard. It
> > works in Chrome and Edge but not in Safari in spite of official
> > documentation of Safari says that it's supported.
>
> The issue is not so much async Clipboard API and the difference in the way
> user activation is tracked between Blink and WebKit.
>
> > In this example (not my real code), write() throws an error:
> >
> > document.getElementById("copy").addEventListener("click", async function() {
> > const response = await
> > fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/
> > PNG_transparency_demonstration_1.png');
> > const blob = await response.blob();
> >
> > navigator.clipboard.write([new ClipboardItem({ "image/png": blob })])
> > .then(function () { console.log('copied'); })
> > .catch(function (error) { console.log(error); });
> > });
>
> You need to initiate the write inside click event handler as in:
>
> document.getElementById("copy-html").addEventListener("click", event => {
> navigator.clipboard.write([
> new ClipboardItem({
> "text/png": async () => {
> const response = await
> fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/
> PNG_transparency_demonstration_1.png');
> return await response.blob();
> }
> }),
> ]);
> });
Hi Ryosuke,
first of all, thanks for your reply. Unfortunately, I have tested your solution and I get the same error message.
Wenson Hsieh
> > You need to initiate the write inside click event handler as in:
> >
> > document.getElementById("copy-html").addEventListener("click", event => {
> > navigator.clipboard.write([
> > new ClipboardItem({
> > "text/png": async () => {
> > const response = await
> > fetch('https://upload.wikimedia.org/wikipedia/commons/4/47/
> > PNG_transparency_demonstration_1.png');
> > return await response.blob();
> > }
> > }),
> > ]);
> > });
>
>
> Hi Ryosuke,
>
> first of all, thanks for your reply. Unfortunately, I have tested your
> solution and I get the same error message.
Hi Felipe,
I adapted the above code as a test case here: https://whsieh.github.io/examples/dice, and it seems to work in Safari on macOS with a couple of adjustments:
- Changing the "text/png" above to "image/png".
- Making the corresponding value for the "image/png" type a Promise instead of an async function.
Please make sure that you're also testing in a secure context (https), where the clipboard API is available.
Thanks!
Wenson
Radar WebKit Bug Importer
<rdar://problem/74860840>
Thomas Steiner
The different implementations unfortunately at the moment force developers to write code like this:
```js
copyButton.addEventListener('click', async () => {
try {
// Safari treats user activation differently:
// https://bugs.webkit.org/show_bug.cgi?id=222262.
navigator.clipboard.write([
new ClipboardItem({
'text/plain': new Promise(async (resolve) => {
const svg = svgOutput.innerHTML;
resolve(new Blob([svg], { type: 'text/plain' }));
}),
}),
]);
} catch {
// Chromium
const svg = svgOutput.innerHTML;
const blob = new Blob([svg], { type: 'text/plain' });
navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
}
});
```
Chromium is working on adding support for delayed generation of clipboard items (see https://crbug.com/1014310).
At the same time it would be great if WebKit didn't expire the user activation.
Alexey Proskuryakov
*** Bug 244812 has been marked as a duplicate of this bug. ***
Nolan Lawson
Just as a note for others stumbling on this error: the issue occurs when there's any async boundary between the user-initiated click event and the call to `navigator.clipboard.write`.
I ran into this same issue in my emoji picker library, due to firing an event from inside the shadow DOM (common for web components) combined with an async call to IndexedDB. The workaround is pretty awkward: https://github.com/nolanlawson/emoji-picker-element/?tab=readme-ov-file#emoji-click-sync
It would be great if WebKit allowed this API to work even with async boundaries. For the record, neither Chromium nor Firefox have this issue, so the special workaround is only required for WebKit. Luckily though, the Chromium issue Thomas mentioned above is now fixed, so the same workaround code works in all three engines.