admin管理员组

文章数量:1356260

I want clicking my Chrome extension’s browser action button to copy some text, but the methods used on websites don’t work.

(I realize there are some similar questions, however they answer copying text in injected scripts or browser action popup scripts, not browser action scripts.)

Boilerplate

manifest.json:

{
    "name": "Example",
    "version": "0.0.0",
    "manifest_version": 2,
    "permissions": ["clipboardWrite"],
    "browser_action": {"default_title": "Copy some text"},
    "background": {
        "scripts": ["events.js"],
        "persistent": false
    }
}

events.js:

chrome.browserAction.onClicked.addListener(_ => {
    copy(new Date().toISOString().slice(0, 19))
})

// Define copy() here.

Approach 1

Don’t define copy and hope it’s defined globally as in the console.

It isn’t.

Approach 2

function copy(text) {
    navigator.clipboard.writeText(text)
}

This fails with the error message “DOMException: Document is not focused”.

Approach 3

function copy(text) {
    focus()
    navigator.clipboard.writeText(text)
}

This behaves the same as approach 2.

Approach 4

function copy(text) {
    const ta = document.createElement('textarea')
    ta.value = text
    ta.select()
    document.execCommand('copy')
    ta.remove()
}

This fails without an error message.

Approach 5

Inject a copying script into the active tab. I didn’t code this one because it would break if there are no accessible active tabs, tabs messed with their globals, JS is paused on the active tab, etc, and would also need excessive permissions.

Approach 6

function copy(text) {
    open('copy.html?' + encodeURIComponent(text), '', 'width=1,height=1')
}

(Setting width and height forces opening a window, not a tab, in order to preserve the user’s tab selection and reduce visual impact.)

copy.html:

<!doctype html>
<meta charset="utf-8">
<title>Copying…</title>
<div></div>
<script src="copy.js"></script>

copy.js:

(async _ => {
    await navigator.clipboard.writeText(decodeURIComponent(location.search.slice(1)))
    close()
})()

This works but isn’t great because it’s visually glitchy, slow, and circuitous.

I want clicking my Chrome extension’s browser action button to copy some text, but the methods used on websites don’t work.

(I realize there are some similar questions, however they answer copying text in injected scripts or browser action popup scripts, not browser action scripts.)

Boilerplate

manifest.json:

{
    "name": "Example",
    "version": "0.0.0",
    "manifest_version": 2,
    "permissions": ["clipboardWrite"],
    "browser_action": {"default_title": "Copy some text"},
    "background": {
        "scripts": ["events.js"],
        "persistent": false
    }
}

events.js:

chrome.browserAction.onClicked.addListener(_ => {
    copy(new Date().toISOString().slice(0, 19))
})

// Define copy() here.

Approach 1

Don’t define copy and hope it’s defined globally as in the console.

It isn’t.

Approach 2

function copy(text) {
    navigator.clipboard.writeText(text)
}

This fails with the error message “DOMException: Document is not focused”.

Approach 3

function copy(text) {
    focus()
    navigator.clipboard.writeText(text)
}

This behaves the same as approach 2.

Approach 4

function copy(text) {
    const ta = document.createElement('textarea')
    ta.value = text
    ta.select()
    document.execCommand('copy')
    ta.remove()
}

This fails without an error message.

Approach 5

Inject a copying script into the active tab. I didn’t code this one because it would break if there are no accessible active tabs, tabs messed with their globals, JS is paused on the active tab, etc, and would also need excessive permissions.

Approach 6

function copy(text) {
    open('copy.html?' + encodeURIComponent(text), '', 'width=1,height=1')
}

(Setting width and height forces opening a window, not a tab, in order to preserve the user’s tab selection and reduce visual impact.)

copy.html:

<!doctype html>
<meta charset="utf-8">
<title>Copying…</title>
<div></div>
<script src="copy.js"></script>

copy.js:

(async _ => {
    await navigator.clipboard.writeText(decodeURIComponent(location.search.slice(1)))
    close()
})()

This works but isn’t great because it’s visually glitchy, slow, and circuitous.

Share Improve this question asked Feb 22, 2020 at 4:04 twhbtwhb 4,6042 gold badges23 silver badges24 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 7

The textarea element should be added to the live DOM first e.g. to document.body and focused because execCommand operates on document.activeElement. You can hide the textarea so it doesn't flicker.

function copy(text) {
  const ta = document.createElement('textarea');
  ta.style.cssText = 'opacity:0; position:fixed; width:1px; height:1px; top:0; left:0;';
  ta.value = text;
  document.body.appendChild(ta);
  ta.focus();
  ta.select();
  document.execCommand('copy');
  ta.remove();
}

Might wanna set a few more CSS props to none or 0 like border/padding/margin just in case.

@wOxxOm’s answer is correct for manifest v2, but manifest v3 unfortunately made it even worse. The manifest v3 solution:

manifest.json

{
    "manifest_version": 3,
    "name": "Copy on Action Example",
    "version": "1.0.0",
    "permissions": ["clipboardWrite", "offscreen"],
    "background": {"service_worker": "events.js"},
    "action": {"default_title": "Copy “Hello, world!”"}
}

events.js

chrome.action.onClicked.addListener((tab) => {
    copy('Hello, world!');
});

async function copy(text) {
    await chrome.offscreen.createDocument({
        url: chrome.runtime.getURL('copy.html'),
        reasons: ['CLIPBOARD'],
        justification: 'Required by Chrome to copy text.',
    });
    chrome.runtime.sendMessage(text);
}

copy.html

<!doctype html>
<title>Copy – Copy on Action Example</title>
<textarea id="ta"></textarea>
<script src="copy.js"></script>

copy.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    const ta = document.querySelector('#ta');
    ta.value = message;
    ta.select();
    document.execCommand('copy');
    close();
});

Explanation

  • As before, copy and navigator.clipboard.writeText are unavailable, even if you try to focus the document. Chrome seems to be planning on supporting navigator.clipboard.writeText in background pages at some point, maybe, so you might want to test it when you read this and leave a ment if it’s been added.
  • As before, approaches 5 and 6 are a bad idea.
  • The background page is now a service worker and doesn’t have access to a document, so the document.execCommand method no longer works in it. The chrome.offscreen API was added in 2023 to fill this gap.
  • Offscreen pages are closed soon after creation, but timing is unreliable, and trying to open the same URL twice at once throws an error. The simplest solution is to manually close the page each time you use it, but, if you expect this to be triggered a lot in quick succession, you could be a little more efficient (and a little more plex) by instead leaving it open and later using chrome.runtime.getContexts to see if it’s still around.
  • The example simply passes the string to copy as the message, but real code will likely want to pass something like {mand: 'copy', text: 'some text'}, to allow sendMessage to be used for other mands, too.
  • document.execCommand is actually deprecated, but I guess we’re meant to care about that about as much as Chrome does.

Links

  • A very similar example from the Chrome team
  • chrome.offscreen docs
  • Offscreen page caching example, in the above docs

本文标签: javascriptCopy text to clipboard when a Chrome extension’s browser action is clickedStack Overflow