062f87c4a7e5
resist focus-trap blur in identity suggest list + popup auto-close after inline unlock (v3.6.134)
- time
- 2026-05-29 22:30 UTC · 5 months ago
- machine
- koh2
- parent
- 42176e8b1f29
↓ peach-browser-062f87c4.tar.gzatlas at this save
files changed6 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/messages.ts+7 −0src/content/inject.ts+10 −0src/popup/App.tsx+22 −0manifest.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.132",5+ "version": "3.6.134",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.132",4+ "version": "3.6.134",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.132",3+ "version": "3.6.134",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/background/messages.ts+7 −0
@@ -835,6 +835,13 @@835835 // never in page-hosted DOM (audit HIGH). Best-effort: openPopup needs a836836 // user gesture + Chrome 127+/Firefox; on failure the user clicks the837837 // toolbar icon. The badge already signals Peach needs attention.838+ //839+ // Set a session flag so the popup knows it was opened for inline unlock840+ // and should close itself once the vault is unlocked (returning the user841+ // to the inline autofill selector on the page).842+ if (chrome.storage.session) {843+ await chrome.storage.session.set({ peach_inline_unlock: true });844+ }838845 try { await chrome.action.openPopup(); } catch { /* user opens via toolbar */ }839846 return { ok: true };840847 }src/content/inject.ts+10 −0
@@ -1789,6 +1789,11 @@17891789 list.style.display = "none";17901790 suppressNextFocus = true;17911791 inp.focus();1792+ // If a page focus trap intercepts the focus() call above and redirects1793+ // to anchorEl before inp ever fires a focus event, suppressNextFocus1794+ // would strand the user (the list won't show on their next click).1795+ // Clear it after a short window so subsequent clicks still work.1796+ setTimeout(() => { suppressNextFocus = false; }, 120);17921797 }17931798 17941799 function render() {@@ -1850,6 +1855,11 @@18501855 inp.addEventListener("blur", (e) => {18511856 const rt = (e as FocusEvent).relatedTarget as Node | null;18521857 if (rt && list.contains(rt)) return;1858+ // A focus trap on the page (e.g. a modal's aria-modal enforcer) fires when1859+ // inp gains focus and immediately moves focus back to anchorEl. Don't1860+ // close the list for this case — the user never intentionally left the field.1861+ // The list will close naturally when the user picks a row or clicks outside.1862+ if (rt === anchorEl) return;18531863 setTimeout(() => { list.style.display = "none"; }, 150);18541864 });18551865 }src/popup/App.tsx+22 −0
@@ -175,6 +175,9 @@175175 const [forceRecoveryWords, setForceRecoveryWords] = createSignal(false);176176 177177 let contentPhaseTimer: number | undefined;178+ // Set when the popup was opened from the inline "Open Peach to unlock" button.179+ // handleUnlock() closes the popup after unlock so the user returns to the page.180+ let openedForInlineUnlock = false;178181 179182 function applyAccent(name: AccentColorName) {180183 const color = ACCENT_COLORS.find(c => c.name === name) ?? ACCENT_COLORS[0]!;@@ -345,6 +348,19 @@345348 recoveryPending = (out as { peach_recovery_pending?: string })?.peach_recovery_pending === "1";346349 } catch { /* best-effort */ }347350 351+ // If this popup was opened from the inline "Open Peach to unlock" button,352+ // the background set a session flag. Read and clear it so handleUnlock()353+ // can close the popup after unlock and return the user to the page.354+ if (chrome.storage.session) {355+ try {356+ const s = await chrome.storage.session.get("peach_inline_unlock");357+ if ((s as { peach_inline_unlock?: boolean }).peach_inline_unlock) {358+ openedForInlineUnlock = true;359+ await chrome.storage.session.remove("peach_inline_unlock");360+ }361+ } catch { /* best-effort */ }362+ }363+348364 // Native: closing the app before recovery-words confirmation means the349365 // user didn't finish vault creation. Throw the partial vault away and350366 // restart onboarding rather than asking them to unlock a vault they@@ -978,6 +994,12 @@978994 if (celebrating) {979995 setView("pro-celebration");980996 window.setTimeout(() => setView("entry"), 2500);997+ }998+ // Popup was opened from the inline "Open Peach to unlock" button — close it999+ // now so the user lands back on the page with the autofill selector ready.1000+ if (openedForInlineUnlock) {1001+ openedForInlineUnlock = false;1002+ window.close();9811003 }9821004 }9831005