Make the Copy URL extension reliable across install and upgrade

The Copy URL extension was intermittently failing to copy anything, and
its notification looked oversized with no icon. Several distinct problems:

- The MV3 service worker was pinned to stale cached code. Chromium caches
  the worker script for a --load-extension command-line extension and does
  NOT re-register it when the file changes in place — not on restart, not on
  a manifest version bump, not even after a clean shutdown. So updated code
  never took effect: the old worker kept calling chrome.scripting.executeScript,
  which throws once the scripting permission is gone, and the copy silently
  failed. Renaming the worker to a versioned filename (background-2.js) is a
  new script URL, which forces a fresh registration for everyone — new
  installs and existing installs alike. Rename it again on any future worker
  change (see the note at the top of background-2.js).

- Clipboard writes now go through an offscreen document (MV3's sanctioned
  path) using a textarea + execCommand('copy'), replacing executeScript +
  navigator.clipboard. This works on chrome:// pages and needs no scripting
  permission. execCommand is used deliberately: navigator.clipboard.writeText
  rejects in an unfocused offscreen document.

- Add a toolbar action (chrome.action.onClicked) so the extension is
  clickable, not keyboard-only — it was greyed out in the extensions menu
  with nothing to invoke.

- Slim the "URL copied" notification: lead the summary with a glyph so the
  omarchy notification shell collapses it to a single-line toast with an
  icon, instead of an oversized card with a blank icon slot.

- Guard the Quattro shortcut-repair migration: it edits Chromium Preferences
  to move the Alt+Shift+L binding to the new extension id, but a running
  browser rewrites Preferences on exit and reverts the edit. Prompt (via gum)
  to close the browser first, only when a browser is running and there is
  actually a stale binding to repair.
This commit is contained in:
David Heinemeier Hansson
2026-07-17 16:28:18 -07:00
parent 182330465a
commit 28d853402e
7 changed files with 135 additions and 27 deletions
+23 -3
View File
@@ -1716,6 +1716,11 @@ CHROMIUM_FLAGS_PATCH_PY
done
}
# True if any Chromium-family browser that stores the Copy URL shortcut is running.
browser_is_running() {
pgrep -x 'chromium|chrome|brave|msedge|vivaldi-bin|vivaldi|opera|helium' >/dev/null 2>&1
}
repair_chromium_copy_url_shortcuts() {
local profile_root preferences
local -a profile_roots=(
@@ -1730,6 +1735,7 @@ repair_chromium_copy_url_shortcuts() {
"$HOME/.config/opera"
"$HOME/.config/helium"
)
local -a pending=()
for profile_root in "${profile_roots[@]}"; do
[[ -d $profile_root ]] || continue
@@ -1737,8 +1743,23 @@ repair_chromium_copy_url_shortcuts() {
for preferences in "$profile_root"/*/Preferences; do
[[ -f $preferences ]] || continue
grep -qF 'bocglpkldciamkbmlphanhkfnhpmnbma' "$preferences" || continue
pending+=("$preferences")
done
done
python3 - "$preferences" "$preferences.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak" <<'CHROMIUM_SHORTCUTS_PATCH_PY'
(( ${#pending[@]} )) || return 0
# A running browser holds Preferences in memory and rewrites it on exit,
# which would revert this repair. Ask the user to close it first.
if browser_is_running && (( ! yes )) && [[ -r /dev/tty ]]; then
if ! gum confirm "Close all browser windows before the Copy URL shortcut is repaired, then continue"; then
echo "Skipped Copy URL shortcut repair; re-run after closing your browser." >&2
return 0
fi
fi
for preferences in "${pending[@]}"; do
python3 - "$preferences" "$preferences.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak" <<'CHROMIUM_SHORTCUTS_PATCH_PY'
import json
import shutil
import sys
@@ -1768,7 +1789,6 @@ if changed:
shutil.copy2(path, backup_path)
path.write_text(json.dumps(preferences, separators=(",", ":")))
CHROMIUM_SHORTCUTS_PATCH_PY
done
done
}
@@ -1935,7 +1955,7 @@ repair_sleep_lock_unit_override
install_bash_startup
unset rel file backup retired_config_files always_copy_config_files uwsm_env known_config_default_hashes_by_key
unset -f file_sha256 known_config_default_hashes default_hash_paths is_known_default_hash backup_config_file migrate_uwsm_env_customizations is_retired_config_file copy_config_default copy_missing_config_defaults refresh_known_config_defaults mark_removed_preinstalls_from_legacy_bindings copy_always_config_defaults repair_chromium_copy_url_extension_flags repair_chromium_copy_url_shortcuts repair_sleep_lock_unit_override is_known_bashrc_default install_bash_startup
unset -f file_sha256 known_config_default_hashes default_hash_paths is_known_default_hash backup_config_file migrate_uwsm_env_customizations is_retired_config_file copy_config_default copy_missing_config_defaults refresh_known_config_defaults mark_removed_preinstalls_from_legacy_bindings copy_always_config_defaults repair_chromium_copy_url_extension_flags browser_is_running repair_chromium_copy_url_shortcuts repair_sleep_lock_unit_override is_known_bashrc_default install_bash_startup
mkdir -p "$HOME/.agents/skills" "$HOME/.claude/skills" "$HOME/.codex/skills" "$HOME/.pi/agent/skills"
if [[ -d $root/default/omarchy-skill ]]; then
@@ -0,0 +1,65 @@
// NOTE: this file is intentionally version-numbered (background-2.js). Chromium
// caches the service worker for a command-line --load-extension and does NOT
// re-register it when the script changes in place, so an updated worker never
// takes effect on existing installs. Changing the filename is a new script URL,
// which forces a fresh registration. If you change this worker's code, rename it
// (background-3.js, ...) and update manifest.json's background.service_worker.
let creatingOffscreenDocument;
async function ensureOffscreenDocument() {
if (await chrome.offscreen.hasDocument()) return;
if (!creatingOffscreenDocument) {
creatingOffscreenDocument = chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['CLIPBOARD'],
justification: 'Copy the active tab URL to the clipboard'
}).finally(() => {
creatingOffscreenDocument = undefined;
});
}
await creatingOffscreenDocument;
}
async function copyUrl(url) {
if (!url) return;
try {
await ensureOffscreenDocument();
const copied = await chrome.runtime.sendMessage({
target: 'offscreen',
type: 'copy-url',
url
});
if (!copied) return;
// The omarchy notification shell renders a chromium toast slim (no icon
// slot) when the summary begins with a glyph followed by 2+ spaces and the
// body is empty. iconUrl is a required field, so keep a 1x1 transparent png.
chrome.notifications.create({
type: 'basic',
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgAAIAAAUAAXpeqz8AAAAASUVORK5CYII=',
title: '󰅍 URL copied to clipboard',
message: ''
});
} catch (error) {
console.error('[copy-url] failed:', error);
}
}
// Keyboard shortcut (Alt+Shift+L).
chrome.commands.onCommand.addListener((command) => {
if (command !== 'copy-url') return;
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
copyUrl(tabs[0] && tabs[0].url);
});
});
// Clicking the extension's toolbar icon.
chrome.action.onClicked.addListener((tab) => {
copyUrl(tab && tab.url);
});
@@ -1,21 +0,0 @@
chrome.commands.onCommand.addListener((command) => {
if (command === 'copy-url') {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const currentTab = tabs[0];
chrome.scripting.executeScript({
target: { tabId: currentTab.id },
func: () => {
navigator.clipboard.writeText(window.location.href);
}
}).then(() => {
chrome.notifications.create({
type: 'basic',
iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGNgAAIAAAUAAXpeqz8AAAAASUVORK5CYII=',
title: ' URL copied to clipboard',
message: ''
});
});
});
}
});
@@ -1,10 +1,11 @@
{
"manifest_version": 3,
"name": "Copy URL",
"version": "1.0",
"version": "1.3",
"description": "Copy current URL to clipboard, this extension is installed by Omarchy",
"action": { "default_title": "Copy URL" },
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxbOMKgGcG20uelP6lx5OnuafGP9gNR1ajzURuQ45tv/Is5sc1tn9ii9PsKxpzDzPL9Z5lAqrTFgEcKHyTJnZXAGOMIruB0odJ0BW6eoQ3rxaQ/fBK2yGN7FZAfygPOWHj+Fdrh2eYQ7Ap92Dmlz8x0ceGyais415KmeevVcPwS7jjHFd1bGBp17cEMOBQcOx/3zCnko1hGtCzSdyZQl3cNMebW/FGvmxlPUMLVfWhysfFUuzxMRRf58+/j0mo/9iKQ0uTuxbQ8+W64OmYOVR87IXYNgbo+hEtORjiQhVg1KzaBx1bZ6clQMv96W/xYy1cIzUA5yI9y7lNr7n9zKijwIDAQAB",
"permissions": ["activeTab", "scripting", "notifications"],
"permissions": ["activeTab", "clipboardWrite", "notifications", "offscreen"],
"icons": {
"16": "icon.png",
"48": "icon.png",
@@ -16,5 +17,5 @@
"description": "Copy URL"
}
},
"background": {"service_worker": "background.js"}
"background": {"service_worker": "background-2.js"}
}
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<script src="offscreen.js"></script>
</head>
<body>
<textarea id="target"></textarea>
</body>
</html>
@@ -0,0 +1,17 @@
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.target !== 'offscreen' || message.type !== 'copy-url') return;
// Offscreen documents are never focused, so navigator.clipboard.writeText
// rejects with "Document is not focused". execCommand('copy') has no focus
// requirement and is authorized by the clipboardWrite permission.
try {
const target = document.getElementById('target');
target.value = message.url;
target.focus();
target.select();
sendResponse(document.execCommand('copy'));
} catch (error) {
console.error('[copy-url offscreen] error:', error);
sendResponse(false);
}
});
+17
View File
@@ -36,6 +36,23 @@ JS
fail "copy-url extension manifest has the stable id" "$copy_url_id"
pass "copy-url extension manifest has the stable id"
jq -e '
.manifest_version == 3 and
(.permissions | index("clipboardWrite")) and
(.permissions | index("offscreen")) and
(.background.service_worker | startswith("background-"))
' "$ROOT/default/chromium/extensions/copy-url/manifest.json" >/dev/null ||
fail "copy-url extension uses an offscreen clipboard document"
[[ -f $ROOT/default/chromium/extensions/copy-url/offscreen.html &&
-f $ROOT/default/chromium/extensions/copy-url/offscreen.js ]] ||
fail "copy-url extension ships its offscreen clipboard document"
pass "copy-url extension uses an offscreen clipboard document"
jq -e '.action != null' "$ROOT/default/chromium/extensions/copy-url/manifest.json" >/dev/null &&
grep -q 'action.onClicked' "$ROOT/default/chromium/extensions/copy-url/"background-*.js ||
fail "copy-url extension is clickable from the toolbar"
pass "copy-url extension is clickable from the toolbar"
TMPDIR=$(mktemp -d)
preferences="$TMPDIR/Preferences"
backup="$TMPDIR/Preferences.bak"