Background: I would like to write an extension, where right-clicking on the extension icon and clicking on a context menu item copies something to the clipboard.
But I also want to avoid using the scripting
permission or injecting content_scripts
in the manifest.json
, because some corporate environments don't allow it (for good reason).
But how can I copy to the clipboard from a chrome.contextMenus.onClicked
handler if I don't use scripting
or content_scripts
? Neither document.execCommand
nor navigator.clipboard.write
are available in background scripts (where both document
and navigator.clipboard
are undefined) and there is no page to send a message to, since the action.default_popup
pages cannot be opened from background scripts and I don't want to use content injection.
Any ideas?
The cookbook.offscreen-clipboard-write example is a working full example of using chrome.offscreen. The essense is that offscreen was created for exactly this kind of purpose.
Here is a summary, but do see the two above links for more details:
First you add "offscreen"
to the permissions in manifest.json
.
Then in background.js
:
// This could also be chrome.contextMenus.onClicked.addListener()
chrome.action.onClicked.addListener(async () => {
await chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'Need to copy data to the clipboard',
});
chrome.runtime.sendMessage({
type: 'copy-data-to-clipboard',
target: 'offscreen-doc',
data: 'put this string on the clipboard'
});
});
offscreen.html
has:
<!DOCTYPE html>
<textarea id="text"></textarea>
<script src="offscreen.js"></script>
And offscreen.js
:
chrome.runtime.onMessage.addListener(handleMessages);
async function handleMessages(message) {
if (message.target !== 'offscreen-doc') {
return;
}
switch (message.type) {
case 'copy-data-to-clipboard':
handleClipboardWrite(message.data);
break;
default:
console.warn(`Unexpected message type received: '${message.type}'.`);
}
}
const textEl = document.querySelector('#text');
async function handleClipboardWrite(data) {
try {
if (typeof data !== 'string') {
throw new TypeError(
`Value provided must be a 'string', got '${typeof data}'.`
);
}
textEl.value = data;
textEl.select();
document.execCommand('copy');
} finally {
window.close();
}
}