460535802d51
stop keyboard event leakage and re-assert focus against page focus traps in locked inline popup (v1.9.9)
- time
- 2026-04-25 04:50 UTC · 6 months ago
- machine
- koh2
- parent
- c99a298af8fb
↓ peach-browser-46053580.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+27 −2manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "1.9.8",4+ "version": "1.9.9",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "1.9.8",4+ "version": "1.9.9",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "1.9.8",3+ "version": "1.9.9",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/content/inject.ts+27 −2
@@ -892,12 +892,37 @@892892 }893893 }894894 895- pwInp.addEventListener("keydown", (e) => { if (e.key === "Enter") void tryUnlock(); });895+ // Stop all keyboard events from bubbling to the page. Without this,896+ // events propagate via the composed Shadow DOM path and page focus-trap897+ // handlers (e.g. Etsy's modal) can intercept keystrokes typed into the898+ // Peach unlock input and route them to the page's own password field.899+ pwInp.addEventListener("keydown", (e) => {900+ e.stopPropagation();901+ if (e.key === "Enter") void tryUnlock();902+ });903+ pwInp.addEventListener("keyup", (e) => e.stopPropagation());904+ pwInp.addEventListener("keypress", (e) => e.stopPropagation());896905 unlockBtn.addEventListener("click", () => void tryUnlock());897906 898907 wrap.append(head, pwInp, errEl, unlockBtn);899908 container.appendChild(wrap);900- requestAnimationFrame(() => pwInp.focus());909+910+ // Focus synchronously first, then again after layout — page focus traps911+ // (e.g. Etsy's modal) often run in the next frame and steal focus back.912+ // Re-assert focus whenever the input loses it to something outside our913+ // shadow root, as long as the overlay is still in the DOM.914+ const refocus = (e?: FocusEvent) => {915+ if (!host.isConnected) return;916+ if (e) {917+ const related = e.relatedTarget;918+ // Focus moving within the shadow root (e.g. to the unlock button) is fine.919+ if (related instanceof Node && shadow.contains(related)) return;920+ }921+ pwInp.focus({ preventScroll: true });922+ };923+ pwInp.focus({ preventScroll: true });924+ requestAnimationFrame(() => refocus());925+ pwInp.addEventListener("focusout", refocus);901926 }902927 903928 function handleKeyDown(e: KeyboardEvent) {