91ca0c3fee4e

append popup inside modal ancestor so focus traps don't intercept Peach's internal inputs (v3.6.131)

time
2026-05-29 16:56 UTC · 5 months ago
machine
koh2
parent
b1774d59b9e2
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+15 −6
manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22   "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33   "manifest_version": 3,44   "name": "Peach Password Manager",5-  "version": "3.6.130",5+  "version": "3.6.131",66   "description": "Zero-knowledge password manager",77   "browser_specific_settings": {88     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "3.6.130",4+  "version": "3.6.131",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": "3.6.130",3+  "version": "3.6.131",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/content/inject.ts+15 −6
@@ -1253,7 +1253,15 @@12531253   options: SelectorOptions = {}12541254 ): () => void {12551255   const host = document.createElement("div");1256-  host.style.cssText = "position: absolute; z-index: 2147483647;";1256+  // When anchorEl is inside a focus-trapping modal (aria-modal, role=dialog, <dialog>),1257+  // the modal's JS focus trap fires when Peach's internal inputs gain focus (they are1258+  // outside the modal in the DOM), immediately blurring them and closing the identity1259+  // suggest list. Fix: detect the modal ancestor and append the host inside it so focus1260+  // traps see Peach as within the modal. Use position:fixed so the popup positions1261+  // correctly relative to the viewport — overflow and transforms on the modal cannot1262+  // clip or misplace a fixed child.1263+  const modalAncestor = anchorEl.closest<HTMLElement>('dialog, [aria-modal="true"], [role="dialog"]');1264+  host.style.cssText = `position: ${modalAncestor ? "fixed" : "absolute"}; z-index: 2147483647;`;12571265   host.style.setProperty("--peach-accent", accentHex);12581266   host.style.setProperty("--peach-accent-dim", withAlpha(accentHex, "28"));12591267   host.style.setProperty("--peach-glow", withAlpha(accentHex, "18"));@@ -1264,8 +1272,9 @@12641272   style.textContent = SELECTOR_STYLES;12651273 12661274   const rect = anchorEl.getBoundingClientRect();1267-  host.style.top = `${rect.bottom + window.scrollY + 5}px`;1268-  host.style.left = `${rect.left + window.scrollX}px`;1275+  // Fixed positioning uses viewport-relative coords; absolute uses document-relative.1276+  host.style.top = `${rect.bottom + (modalAncestor ? 0 : window.scrollY) + 5}px`;1277+  host.style.left = `${rect.left + (modalAncestor ? 0 : window.scrollX)}px`;12691278   // Spec §1.1: 320px width. The Math.max() floor preserves usability on12701279   // wide single-input fields (e.g. centered email input on signup pages).12711280   // Frozen at open time — updatePosition re-asserts this so page CSS cannot@@ -2633,12 +2642,12 @@26332642   document.addEventListener("keydown", silverPlatterTypeHandler, true);26342643   cleanupHooks.push(() => document.removeEventListener("keydown", silverPlatterTypeHandler, true));26352644 2636-  document.body.appendChild(host);2645+  (modalAncestor ?? document.body).appendChild(host);26372646 26382647   function updatePosition() {26392648     const r = anchorEl.getBoundingClientRect();2640-    host.style.top = `${r.bottom + window.scrollY + 5}px`;2641-    host.style.left = `${r.left + window.scrollX}px`;2649+    host.style.top = `${r.bottom + (modalAncestor ? 0 : window.scrollY) + 5}px`;2650+    host.style.left = `${r.left + (modalAncestor ? 0 : window.scrollX)}px`;26422651     host.style.width = `${popupWidth}px`;26432652   }26442653   const stopObserving = observeAnchorPosition(anchorEl, updatePosition);

view file