Skip to content

Fix copy-url extension clipboard on recent Chromium/Brave#4890

Open
mads397 wants to merge 1 commit intobasecamp:devfrom
mads397:fix-copy-url-clipboard
Open

Fix copy-url extension clipboard on recent Chromium/Brave#4890
mads397 wants to merge 1 commit intobasecamp:devfrom
mads397:fix-copy-url-clipboard

Conversation

@mads397
Copy link
Copy Markdown

@mads397 mads397 commented Mar 3, 2026

Problem

The copy-url extension stopped working on recent Chromium and Brave versions. The extension uses navigator.clipboard.writeText() injected into the active page via scripting.executeScript(), but recent browser updates tightened the Clipboard API's user activation requirement — an extension keyboard shortcut no longer counts as a user gesture in the page context, so the write silently fails.

Additionally, the old approach fails entirely on restricted pages (chrome://, brave://, extension pages, Web Store).

Fix

Switch to an offscreen document approach:

  1. Read tab.url from the tabs API (no page injection needed)
  2. Create a hidden offscreen document with the CLIPBOARD reason
  3. Use document.execCommand('copy') in the offscreen context, backed by the clipboardWrite permission
  4. Report success/failure back via sendResponse — only show "copied" notification on success
  5. Close the offscreen document after use to avoid keeping an extra context alive
  6. Drop the unused scripting permission (no longer injecting into pages)

This works on all pages regardless of their clipboard permissions or activation state.

Changes

  • manifest.json: Replace scripting with clipboardWrite and offscreen permissions, bump version to 1.1
  • background.js: Replace executeScript + navigator.clipboard with offscreen document message passing; only notify on confirmed success; close offscreen document after copy
  • offscreen.html / offscreen.js: New files — minimal offscreen document that handles clipboard writes and reports success/failure

Testing

Tested on Brave 1.76 (Chromium 134) on Linux (Hyprland/Wayland). Confirmed working on:

  • Regular web pages
  • PWAs
  • Pages that previously triggered clipboard permission prompts

Copilot AI review requested due to automatic review settings March 3, 2026 15:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Omarchy’s bundled Chromium “copy-url” extension to use an offscreen document for clipboard writes, and also adds new touchpad-toggle + Zellij configuration/theming integrations.

Changes:

  • Switch copy-url from scripting.executeScript + navigator.clipboard to an offscreen document copy flow.
  • Add touchpad toggle support via a polkit rule + sysfs toggle script, with a Waybar indicator and click action.
  • Add a default Zellij config and a theme sync script that generates Zellij themes from Omarchy colors.

Reviewed changes

Copilot reviewed 10 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
install/config/touchpad/install.sh Installs polkit rule for touchpad toggle via pkexec
default/waybar/indicators/touchpad.sh Waybar module script to show touchpad disabled state
default/touchpad/toggle-impl Root-side sysfs toggle implementation for touchpad inhibit flag
bin/omarchy-toggle-touchpad User command to toggle touchpad + manage cursor + notify Waybar
config/waybar/config.jsonc Adds custom/touchpad-indicator module and click handler
config/waybar/style.css Styles the new touchpad indicator (disabled state)
default/chromium/extensions/copy-url/background.js Uses offscreen document + tabs API to copy URL
default/chromium/extensions/copy-url/offscreen.html Offscreen document entrypoint
default/chromium/extensions/copy-url/offscreen.js Implements the offscreen copy operation
default/chromium/extensions/copy-url/manifest.json Adds permissions for offscreen + clipboardWrite, bumps version
config/zellij/config.kdl Introduces a default Zellij config
bin/omarchy-theme-set-zellij Generates Zellij theme KDL from Omarchy theme colors and applies it
bin/omarchy-theme-set Calls the new Zellij theme sync during theme changes
bin/omarchy-refresh-zellij Refreshes Zellij config and regenerates theme
bin/omarchy-restart-zellij No-op restart shim for convention compatibility

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread install/config/touchpad/install.sh Outdated
if (action.id == "org.freedesktop.policykit.exec" &&
subject.isInGroup("wheel")) {
var program = action.lookup("program");
if (program && program.indexOf("/default/touchpad/toggle-impl") !== -1) {
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The polkit rule matches the requested program using a substring check (indexOf). This can unintentionally authorize running other executables whose path merely contains "/default/touchpad/toggle-impl", which is broader than intended and can create a privilege-escalation path. Prefer an exact match against the expected absolute path (or a strictly anchored pattern), and consider resolving symlinks/realpath before comparison if supported in polkit JS.

Suggested change
if (program && program.indexOf("/default/touchpad/toggle-impl") !== -1) {
if (program === "/default/touchpad/toggle-impl") {

Copilot uses AI. Check for mistakes.
"version": "1.1",
"description": "Copy current URL to clipboard, this extension is installed by Omarchy",
"permissions": ["activeTab", "scripting", "notifications"],
"permissions": ["activeTab", "scripting", "notifications", "clipboardWrite", "offscreen"],
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The extension no longer uses the Scripting API (chrome.scripting), but the manifest still requests the "scripting" permission. Dropping unused permissions reduces review surface area and avoids prompting users for capabilities the extension doesn’t need.

Suggested change
"permissions": ["activeTab", "scripting", "notifications", "clipboardWrite", "offscreen"],
"permissions": ["activeTab", "notifications", "clipboardWrite", "offscreen"],

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +22
});
if (!contexts.length) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['CLIPBOARD'],
justification: 'Copy URL to clipboard'
});
}
await chrome.runtime.sendMessage({ target: 'offscreen', text: tab.url });

Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The offscreen document is created (if missing) but never closed. Offscreen documents can keep resources alive unnecessarily; consider closing it after the copy completes (or implementing a short idle timeout / reuse strategy) so the extension doesn’t keep an extra context around indefinitely.

Suggested change
});
if (!contexts.length) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['CLIPBOARD'],
justification: 'Copy URL to clipboard'
});
}
await chrome.runtime.sendMessage({ target: 'offscreen', text: tab.url });
});
let createdOffscreen = false;
if (!contexts.length) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['CLIPBOARD'],
justification: 'Copy URL to clipboard'
});
createdOffscreen = true;
}
await chrome.runtime.sendMessage({ target: 'offscreen', text: tab.url });
if (createdOffscreen) {
// Close the offscreen document shortly after the copy request to
// avoid keeping an extra context alive indefinitely.
setTimeout(() => {
try {
chrome.offscreen.closeDocument();
} catch (err) {
console.error('copy-url: failed to close offscreen document', err);
}
}, 1000);
}

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
chrome.runtime.onMessage.addListener((msg) => {
if (msg.target !== 'offscreen' || !msg.text) return;
const ta = document.createElement('textarea');
ta.value = msg.text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The background script always shows a "URL copied" notification, but the offscreen handler doesn’t report success/failure and ignores the return value from document.execCommand('copy'). If copying fails (e.g., execCommand returns false), the user gets a false-positive. Have the offscreen listener return an explicit result (sendResponse/promise) and only notify on success (or show an error notification on failure).

Suggested change
chrome.runtime.onMessage.addListener((msg) => {
if (msg.target !== 'offscreen' || !msg.text) return;
const ta = document.createElement('textarea');
ta.value = msg.text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.target !== 'offscreen' || !msg.text) return;
const ta = document.createElement('textarea');
ta.value = msg.text;
document.body.appendChild(ta);
ta.select();
let success = false;
try {
success = document.execCommand('copy');
} catch (e) {
success = false;
}
ta.remove();
sendResponse({ success });
return true;

Copilot uses AI. Check for mistakes.
Comment thread config/zellij/config.kdl Outdated
Comment on lines +1 to +4
keybinds clear-defaults=true {
locked {
bind "Ctrl g" { SwitchToMode "normal"; }
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The PR title/description are focused on fixing the Chromium copy-url extension, but this change set also introduces touchpad toggle (polkit rule + scripts + waybar module) and a full Zellij config/theme integration. This mismatch makes the PR harder to review/revert; consider splitting into separate PRs or updating the PR description/title to cover all included features.

Copilot uses AI. Check for mistakes.
The extension uses navigator.clipboard.writeText() injected via
scripting.executeScript(), but recent Chromium updates tightened the
Clipboard API's user activation requirement — an extension keyboard
shortcut no longer counts as a user gesture in the page context, so
the write silently fails. Also fails on restricted pages (chrome://,
brave://, extension pages).

Switch to an offscreen document approach:
- Read tab.url from the tabs API (no page injection needed)
- Create a hidden offscreen document with the CLIPBOARD reason
- Use document.execCommand('copy') backed by clipboardWrite permission
- Report success/failure back to background.js via sendResponse
- Close the offscreen document after use
- Drop unused scripting permission

Tested on Brave 1.76 (Chromium 134) on Linux (Hyprland/Wayland).
@mads397 mads397 force-pushed the fix-copy-url-clipboard branch from 52d51fa to da58b0f Compare March 4, 2026 05:10
mads397 added a commit to mads397/omarchy that referenced this pull request Mar 4, 2026
…ubstring match

- Indicator script now returns an icon for both enabled (󰟸) and disabled (󰤳)
  states instead of empty text, so waybar always shows the module.
- Polkit rule: use endsWith instead of indexOf to prevent path prefix bypass
  (Copilot review feedback from PR basecamp#4890).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dhh
Copy link
Copy Markdown
Member

dhh commented Mar 7, 2026

What version are you having trouble with? Seems to be working here on Chromium 145.

@mads397
Copy link
Copy Markdown
Author

mads397 commented Mar 8, 2026

Thanks for checking — I'm on Brave (v145.1.87.191), not Chromium. I can't pinpoint which Brave version introduced the issue unfortunately. Submitted the PR in case it's useful to others hitting the same thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants