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
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+27 −2
manifest.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": {

view file

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",

view file

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": {

view file

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) {

view file