fix(shortcuts): ignore editable control events

This commit is contained in:
2026-07-31 22:28:18 -04:00
parent e12375f2d2
commit 3ee970a54b
3 changed files with 52 additions and 8 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ Constraints: local commits only; no push; no browser testing; one commit per fea
- [x] Keep the controller visible for its own video in element and ancestor fullscreen.
- [x] Target popup actions at the frame represented by the displayed speed; keep “all videos” intentional.
- [ ] Ignore shortcuts originating from editable controls, including shadow-DOM inputs.
- [x] Ignore shortcuts originating from editable controls, including shadow-DOM inputs.
## Accessibility and usability
+27 -7
View File
@@ -4373,6 +4373,32 @@ function inIframe() {
}
}
function isEditableShortcutTarget(event) {
var path =
event && typeof event.composedPath === "function"
? event.composedPath()
: [event && event.target];
return path.some(function(target) {
if (!target || target.nodeType !== 1) return false;
var nodeName = target.nodeName;
var role = target.getAttribute && target.getAttribute("role");
var contentEditable =
target.getAttribute && target.getAttribute("contenteditable");
return (
nodeName === "INPUT" ||
nodeName === "TEXTAREA" ||
nodeName === "SELECT" ||
target.isContentEditable ||
(contentEditable !== null && contentEditable !== "false") ||
role === "textbox" ||
role === "searchbox" ||
role === "combobox" ||
role === "spinbutton"
);
});
}
function attachKeydownListeners(doc) {
// Content scripts already run in every frame. Keeping each listener scoped
// to its own frame avoids duplicate shortcuts and stale iframe ownership.
@@ -4396,13 +4422,7 @@ function attachKeydownListeners(doc) {
return;
}
if (
event.target.nodeName === "INPUT" ||
event.target.nodeName === "TEXTAREA" ||
event.target.isContentEditable
) {
return;
}
if (isEditableShortcutTarget(event)) return;
if (
!siteRuleUtils.isSpeederActiveForSite(
+24
View File
@@ -414,6 +414,30 @@ describe("inject.js media/controller lifecycle regressions", () => {
expect(video.currentTime).toBe(63);
});
it("ignores shortcuts from selects and shadow-DOM edit fields", async () => {
bootInject();
await settleLifecycle();
const { video } = createControlledVideo();
const select = document.createElement("select");
const host = document.createElement("site-editor");
const input = document.createElement("input");
host.attachShadow({ mode: "open" }).appendChild(input);
document.body.append(select, host);
[select, input].forEach((target) => {
target.dispatchEvent(
new KeyboardEvent("keydown", {
bubbles: true,
composed: true,
code: "KeyD",
key: "d"
})
);
});
expect(video.playbackRate).toBe(1);
});
it("finishes a forced SPA initialization after settings hydration", async () => {
vi.useFakeTimers();
bootInject({