Ignore a keydown that carries no key
Picking a saved login from Chrome's password autofill dispatches a plain Event named "keydown", with no key on it. The shortcut listener passed it to comboOf, which read the key's length and threw -- an uncaught TypeError in the console on every sign-in. Harmless, since nothing was bound to it, but it was noise that looks like a real fault. comboOf now returns null for an event with no key, as it already does for a bare modifier, so the listener stops there.
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { comboOf, keyboard } from "@/lib/keyboard";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A "keydown" that carries no key. Chrome's password autofill dispatches one
|
||||||
|
* as a plain Event when a saved login is picked, and comboOf read `key.length`
|
||||||
|
* off it -- an uncaught TypeError in the console on every sign-in.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let pop: (() => void) | null = null;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
pop?.();
|
||||||
|
pop = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("a keydown with no key", () => {
|
||||||
|
it("has no combo", () => {
|
||||||
|
expect(comboOf(new Event("keydown") as KeyboardEvent)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reaches no binding and throws nothing", () => {
|
||||||
|
const handler = vi.fn();
|
||||||
|
pop = keyboard.pushScope("test", [{ keys: "e", description: "Archive", group: "Mail", handler }]);
|
||||||
|
const errors: unknown[] = [];
|
||||||
|
const onError = (ev: ErrorEvent) => errors.push(ev.error);
|
||||||
|
window.addEventListener("error", onError);
|
||||||
|
try {
|
||||||
|
window.dispatchEvent(new Event("keydown", { bubbles: true }));
|
||||||
|
} finally {
|
||||||
|
window.removeEventListener("error", onError);
|
||||||
|
}
|
||||||
|
expect(errors).toEqual([]);
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves real keys alone", () => {
|
||||||
|
expect(comboOf(new KeyboardEvent("keydown", { key: "e" }))).toBe("e");
|
||||||
|
expect(comboOf(new KeyboardEvent("keydown", { key: "E", shiftKey: true }))).toBe("E");
|
||||||
|
expect(comboOf(new KeyboardEvent("keydown", { key: "Enter", ctrlKey: true }))).toMatch(/enter$/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -136,6 +136,10 @@ const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigat
|
|||||||
|
|
||||||
export function comboOf(e: KeyboardEvent): string | null {
|
export function comboOf(e: KeyboardEvent): string | null {
|
||||||
const key = e.key;
|
const key = e.key;
|
||||||
|
// Chrome's password autofill dispatches a plain Event named "keydown" when a
|
||||||
|
// saved login is picked: no key, nothing to match, and reading its length
|
||||||
|
// threw on every sign-in.
|
||||||
|
if (!key) return null;
|
||||||
if (key === "Shift" || key === "Control" || key === "Alt" || key === "Meta") return null;
|
if (key === "Shift" || key === "Control" || key === "Alt" || key === "Meta") return null;
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
const mod = isMac ? e.metaKey : e.ctrlKey;
|
const mod = isMac ? e.metaKey : e.ctrlKey;
|
||||||
|
|||||||
Reference in New Issue
Block a user