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
6 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 −0
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.132",5+  "version": "3.6.134",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.132",4+  "version": "3.6.134",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.132",3+  "version": "3.6.134",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

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       }

view file

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   }

view file

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 

view file