Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-allow clipboard copy on non-https sites #17118

Merged
merged 8 commits into from
Oct 19, 2021
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 41 additions & 2 deletions web_src/js/features/clipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,36 @@ function onError(btn) {
btn.dataset.content = btn.dataset.original;
}

/** Use the document.execCommand to copy the value to clipboard */
function fallbackCopyViaSelect(elem) {
elem.select();

// if unsecure (not https), there is no navigator.clipboard, but we can still use document.execCommand to copy to clipboard
// it's also fine if we don't test it exists because of the try statement
return document.execCommand('copy');
}
/**
* Fallback to use if navigator.clipboard doesn't exist.
* Achieved via creating a temporary textarea element, selecting the text, and using document.execCommand.
*/
function fallbackCopyToClipboard(text) {
const tempTextArea = document.createElement('textarea');
tempTextArea.value = text;

// avoid scrolling
tempTextArea.style.top = 0;
tempTextArea.style.left = 0;
tempTextArea.style.position = 'fixed';

document.body.appendChild(tempTextArea);

const success = fallbackCopyViaSelect(tempTextArea);

document.body.removeChild(tempTextArea);

return success;
}
wxiaoguang marked this conversation as resolved.
Show resolved Hide resolved

export default async function initClipboard() {
for (const btn of document.querySelectorAll(selector) || []) {
btn.addEventListener('click', async () => {
Expand All @@ -28,8 +58,17 @@ export default async function initClipboard() {
if (!text) return;

try {
await navigator.clipboard.writeText(text);
onSuccess(btn);
if (navigator.clipboard && window.isSecureContext) {
Copy link
Member

@silverwind silverwind Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't navigator.clipboard.writeText throw an error in case this clipboard-write permission is being denied? It would be better to catch and handle such a error if it exists.

I think I recall copy works on non-secure origin http://localhost, so I think isSecureContext is not the only condition that a browser checks.

Copy link
Contributor Author

@tech-meppem tech-meppem Sep 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It checks the "clipboard-write" and "clipboard-read" permissions, however, navigator.permissions.query({name:"clipboard-write"}) isn't fully supported on all browsers yet.

On both firefox and chrome, the navigator.clipboard object only exists if the "clipboard-write" permission is granted.
It's just that on https sites, the clipboard permission is granted (for desktop at least) by default.

I think I recall copy works on non-secure origin http://localhost, so I think isSecureContext is not the only condition that a browser checks.

Also, window.isSecureContext is true on localhost.

Wouldn't navigator.clipboard.writeText throw an error in case this clipboard-write permission is being denied

The easiest way to resolve this confusion is to move the fallbackCopyToClipboard into the catch { }, as then any error would attempt the fallback.
(so I'll do that for next commit)

await navigator.clipboard.writeText(text);
onSuccess(btn);
} else {
const success = btn.dataset.clipboardTarget ? fallbackCopyViaSelect(document.querySelector(btn.dataset.clipboardTarget)) : fallbackCopyToClipboard(text);
if (success) {
onSuccess(btn);
} else {
onError(btn);
}
}
} catch {
onError(btn);
}
Expand Down