fix(playback): keep playback shortcuts after a mouse click on a bar button (#1516)

Chromium focuses a clicked <button>, and a focused control captures the
keyboard: Space and Enter activate it again, and ControlsShortcuts yields
to any interactive element in the key's path. After a click on the
fullscreen button, Space left fullscreen instead of pausing and the seek,
volume and mute keys did nothing until a click on the video took focus
away. Follow-up to #1512, which stopped that focus from pinning the bar
but left it on the button.

A completed pointer click now releases the focus it left on the control
(onBarClick -> ControlsSurface.releasePointerFocus). The click is
attributed by its pointerType (empty for Enter/Space activation and
element.click()), with the legacy MouseEvent fallback answered once per
recorded press, so keyboard activation keeps focus where Tab put it.
Only buttons and range sliders are released. Chromium keeps its
sequential-focus starting point at the blurred control, so a later Tab
continues from it. The release dispatches a focusout while the pointer
still rests on the control, so the volume anchor ignores it instead of
closing the popover under the hovering mouse.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5.1 authored and GitHub committed 2026-09-03 21:42:08 +02:00
1 parent 90d26d499f
commit 308ed9cb41
12 files changed
+548 -26

No files matched your search

+13
View File
@@ -1093,6 +1093,19 @@ test.describe('@xtream inline series fullscreen', () => {
await expect.poll(fullscreenOwner).not.toBeNull();
await expect(overlayTitle).toContainText('S01E01');
// Chromium leaves the clicked button focused; the shared controls
// release that focus so Space reaches the playback shortcut instead
// of activating the button again (which left fullscreen while the
// video kept playing).
const paused = () =>
video.evaluate((el) => (el as HTMLVideoElement).paused);
await expect.poll(paused).toBe(false);
await page.keyboard.press('Space');
await expect.poll(paused).toBe(true);
expect(await fullscreenOwner()).not.toBeNull();
await page.keyboard.press('Space');
await expect.poll(paused).toBe(false);
// Manual switch from the shared controls' own next-episode button.
await playerView.hover();
await playerView