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
↓ peach-browser-91ca0c3f.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+15 −6manifest.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": {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",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": {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);