fix(lock): harden lock screen input paths
CI / typecheck + test + build (windows) (push) Canceled after 0s

- remove the application menu while locked (lockMenu.ts): Alt reveals the
  default menu and its mouse-clickable Reload/DevTools/Zoom items bypass
  before-input-event entirely; menu is rebuilt from the default template on
  unlock, startup-restored locks covered from initLockController
- extract the keyboard classification into pure lockShortcuts.ts
  (isLockBlockedShortcut/isPanicLockChord) with a table-driven test
  (lock-shortcuts.mjs, 29 cases) — the v1.0.17 lockout escaped CI because
  this decision lived inline in createWindow()
- reserve Ctrl+L in the global show/hide shortcut recorder: a global
  registration intercepts the chord at OS level and silently disables the
  panic lock
- skip auto-repeat keydowns in the panic-lock branch (held Ctrl+L on an
  unconfigured app re-read lock.json + settings.json per repeat)
- LockScreen: re-sync the cooldown clock on visibilitychange/focus/pageshow
  so a suspended renderer timer cannot leave the password input disabled
  past the real deadline
This commit is contained in:
Bill committed 2026-09-30 00:38:57 +08:00
1 parent dd08f8054a
commit 86f51df2e6
11 files changed
+229 -44

No files matched your search

+17
View File
@@ -68,6 +68,23 @@ export function LockScreen({ state, onStateChange }: LockScreenProps): React.JSX
return () => window.clearInterval(id)
}, [cooldownUntil])
// The countdown is renderer-driven, and Chromium may suspend or coalesce
// timers while the window is hidden, minimized or occluded — a suspended
// interval would leave the input disabled (and the "retry in N s" text
// frozen) long past the real deadline. Re-sync the clock the moment the
// page becomes visible or focused again, so recovery is immediate.
useEffect(() => {
const sync = (): void => setNow(Date.now())
document.addEventListener('visibilitychange', sync)
window.addEventListener('focus', sync)
window.addEventListener('pageshow', sync)
return () => {
document.removeEventListener('visibilitychange', sync)
window.removeEventListener('focus', sync)
window.removeEventListener('pageshow', sync)
}
}, [])
const remainingMs = Math.max(0, cooldownUntil - now)
const cooling = remainingMs > 0
@@ -500,8 +500,17 @@ function acceleratorFromEvent(e: React.KeyboardEvent<HTMLInputElement>): string
*/
const RESERVED_CONTROL_KEYS = new Set(['=', '-', '0', 'PageUp', 'PageDown'])
/**
* Whole chords the app owns outright, matched exactly (modifier set included).
* Ctrl+L is the panic lock, captured in main's before-input-event: a global
* registration intercepts the key at the OS level even while this window is
* focused, so binding it here would silently disable the lock shortcut.
*/
const RESERVED_EXACT_ACCELERATORS = new Set(['Control+L'])
/** true when `accel` (e.g. "Control+Shift+=") collides with an in-app shortcut */
function isReservedAccelerator(accel: string): boolean {
if (RESERVED_EXACT_ACCELERATORS.has(accel)) return true
const parts = accel.split('+')
return parts.includes('Control') && RESERVED_CONTROL_KEYS.has(parts[parts.length - 1])
}