Skip to content

Commit cc34d5c

Browse files
committed
fix: prevent fullscreen stack buildup during arrow-key grid navigation
The browser Fullscreen API is a stack — every requestFullscreen() call pushes a new entry and exitFullscreen() only pops the top. The previous arrow-key navigation called nextCell.requestFullscreen() directly, so visiting streams A→B→C→D built up a hidden stack [A,B,C,D]. Exiting fullscreen then required unwinding every visited stream one-by-one before returning to the grid (double-click / toggle button / Escape each only removed the topmost entry). Fix: always call document.exitFullscreen() first to drain the stack to empty, wait for the fullscreenchange event to confirm the transition, then call requestFullscreen() on the next cell. A module-level _fsNavBusy guard prevents overlapping transitions from rapid key presses. Closes #317
1 parent 969c3e0 commit cc34d5c

1 file changed

Lines changed: 44 additions & 6 deletions

File tree

web/js/components/preact/FullscreenManager.jsx

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,26 @@ import { tinykeys } from 'tinykeys';
1111
* Finds the currently fullscreen .video-cell, locates its grid position, then
1212
* steps in the requested direction (with wrap-around), skipping empty cells.
1313
*
14+
* IMPORTANT: The browser Fullscreen API is a *stack* — every requestFullscreen()
15+
* call pushes a new entry, and exitFullscreen() only pops the top. If we simply
16+
* called nextCell.requestFullscreen() we would build up a stack [A, B, C, D],
17+
* forcing the user to "unwind" every visited stream before returning to the grid.
18+
* To avoid that, we always exitFullscreen() first (draining the stack to empty),
19+
* wait for the fullscreenchange event, then requestFullscreen() on the next cell.
20+
* A guard flag prevents overlapping transitions from rapid key presses.
21+
*
1422
* @param {'ArrowLeft'|'ArrowRight'|'ArrowUp'|'ArrowDown'} direction
1523
* @param {Array} streamsToShow - streams visible in the current page
1624
* @param {number} cols - grid column count
1725
* @param {number} rows - grid row count
1826
*/
27+
28+
// Guard: true while a fullscreen transition is in progress.
29+
let _fsNavBusy = false;
30+
1931
function navigateFullscreenGrid(direction, streamsToShow, cols, rows) {
32+
if (_fsNavBusy) return; // drop key if transition already underway
33+
2034
const fullscreenEl = document.fullscreenElement;
2135
if (!fullscreenEl) return;
2236

@@ -33,6 +47,7 @@ function navigateFullscreenGrid(direction, streamsToShow, cols, rows) {
3347
// Walk one step at a time in the requested direction, wrapping around, until
3448
// we land on a populated cell (or exhaust all possibilities).
3549
const maxAttempts = cols * rows;
50+
let nextCell = null;
3651
for (let i = 0; i < maxAttempts; i++) {
3752
if (direction === 'ArrowRight') {
3853
nextCol = (nextCol + 1) % cols;
@@ -49,17 +64,40 @@ function navigateFullscreenGrid(direction, streamsToShow, cols, rows) {
4964
const nextStream = streamsToShow[nextIndex];
5065
// Query the DOM for the cell; it remains in the DOM even while another
5166
// element is in fullscreen mode.
52-
const nextCell = document.querySelector(
67+
const candidate = document.querySelector(
5368
`[data-stream-name="${CSS.escape(nextStream.name)}"].video-cell`
5469
);
55-
if (nextCell && nextCell !== fullscreenEl) {
56-
nextCell.requestFullscreen().catch(err => {
57-
console.warn(`Grid nav fullscreen switch failed: ${err.message}`);
58-
});
70+
if (candidate && candidate !== fullscreenEl) {
71+
nextCell = candidate;
5972
}
60-
return;
73+
break;
6174
}
6275
}
76+
77+
if (!nextCell) return;
78+
79+
// Exit fullscreen first to flush the stack, then re-enter for the next cell.
80+
_fsNavBusy = true;
81+
82+
const onChanged = () => {
83+
document.removeEventListener('fullscreenchange', onChanged);
84+
if (!document.fullscreenElement) {
85+
// Stack is now empty — enter fullscreen for the next cell.
86+
nextCell.requestFullscreen()
87+
.catch(err => console.warn(`Grid nav fullscreen switch failed: ${err.message}`))
88+
.finally(() => { _fsNavBusy = false; });
89+
} else {
90+
// Something else grabbed fullscreen unexpectedly; just release the guard.
91+
_fsNavBusy = false;
92+
}
93+
};
94+
95+
document.addEventListener('fullscreenchange', onChanged);
96+
document.exitFullscreen().catch(err => {
97+
document.removeEventListener('fullscreenchange', onChanged);
98+
console.warn(`Grid nav fullscreen exit failed: ${err.message}`);
99+
_fsNavBusy = false;
100+
});
63101
}
64102

65103
/**

0 commit comments

Comments
 (0)