0295c6a8be14

suppress autofill popup reopen on recently filled fields for 5s — fixes popup re-summoning when login button click refocuses an input (v2.0.3)

time
2026-04-26 12:39 UTC · 6 months ago
machine
koh2
parent
332398c99b82
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/index.ts+27 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.0.2",4+  "version": "2.0.3",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": "2.0.2",4+  "version": "2.0.3",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": "2.0.2",3+  "version": "2.0.3",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/content/index.ts+27 −1
@@ -37,6 +37,21 @@3737 // Suppresses spurious reopens until focus moves to a different field.3838 let lastDismissedField: HTMLInputElement | null = null;3939 40+// Fields that were just autofilled — suppress popup reopens on them briefly.41+// Some sites refocus an input on form submit (validation flows, animation42+// blur/focus, framework handlers), which would otherwise re-trigger the43+// selector right as the user clicks the login button. Cleared by explicit44+// user-summon (icon click, keyboard shortcut) or after AUTOFILL_SUPPRESS_MS.45+const recentlyAutofilledFields = new WeakSet<HTMLInputElement>();46+const AUTOFILL_SUPPRESS_MS = 5000;47+function markAutofilled(...fields: (HTMLInputElement | null | undefined)[]): void {48+  for (const f of fields) {49+    if (!f) continue;50+    recentlyAutofilledFields.add(f);51+    setTimeout(() => recentlyAutofilledFields.delete(f), AUTOFILL_SUPPRESS_MS);52+  }53+}54+4055 function debounce<T extends (...args: unknown[]) => void>(4156   fn: T,4257   ms: number@@ -346,6 +361,7 @@346361       const entry = (entryResponse as { ok: boolean; data?: VaultEntry }).data;347362       if (entry) {348363         await handleAutofill(anchorField, usernameEl, passwordEl, entry);364+        markAutofilled(anchorField, usernameEl, passwordEl);349365         // Offer to remember the current site for this entry only if none of350366         // its known domains (primary + additional) match — otherwise the351367         // user already mapped it and the prompt would be noise.@@ -410,6 +426,11 @@410426   // focus has genuinely moved away and come back.411427   if (field === lastDismissedField) return;412428 429+  // Field was just autofilled — sites often refocus an input on submit click,430+  // which would otherwise re-trigger the selector right when the user is431+  // trying to log in. Bail unless the user explicitly summoned us.432+  if (recentlyAutofilledFields.has(field)) return;433+413434   // Prevent two concurrent calls for the same field (e.g. focusin + icon click414435   // firing simultaneously — the second call would dismiss the first's selector).415436   if (pendingInteractions.has(field)) return;@@ -506,6 +527,7 @@506527     currentDismiss?.();507528     currentDismiss = null;508529     lastDismissedField = null; // icon click is always an explicit user open — clear the dismiss lock530+    recentlyAutofilledFields.delete(el); // explicit summon also clears autofill suppression509531     el.focus({ preventScroll: true });510532     void handleFieldInteraction(el);511533   });@@ -561,6 +583,7 @@561583           currentDismiss?.();562584           currentDismiss = null;563585           lastDismissedField = null;586+          recentlyAutofilledFields.delete(el);564587           el.focus({ preventScroll: true });565588           void handleFieldInteraction(el);566589         });@@ -620,7 +643,10 @@620643         ? activeInput621644         : (fields.find(f => f.type === "password")?.el622645             ?? fields.find(f => f.type === "username" || f.type === "email")?.el);623-      if (target) void handleFieldInteraction(target);646+      if (target) {647+        recentlyAutofilledFields.delete(target); // explicit summon clears suppression648+        void handleFieldInteraction(target);649+      }624650     } else if (isShortcut) {625651       // Shortcut on a page with no login form — show clipboard-copy search mode.626652       showSearchPalette();

view file