feat(desktop): window.open popout Detach for Firefox #87

Merged
Claude-Run merged 1 commit from feat/VAPP-75-firefox-window-open-detach into main 2026-07-06 16:05:14 +02:00
Member

Browsers without Document Picture-in-Picture (Firefox today) had a disabled Detach button with a "Firefox does not support it yet" tooltip, so Detach was dead there. This adds the non-PiP popout path VAPP-50 promised: when pip_supported() is false the toolbar now renders a single enabled Detach/Re-dock toggle that opens a same-origin window.open popup sized to the remote framebuffer and reparents the live canvas node into it. The popup shares this page's JS/wasm realm, so the WebSocket transport, 2D context, and input listeners travel with the node and the session keeps streaming with no reconnect. Chromium keeps the existing Document PiP path unchanged.

The PiP reparent/re-dock body is factored into move_canvas_into_window, shared by both paths via a poll_closed flag. Popups do not reliably deliver pagehide to the opener, so the popout path also polls window.closed (a 250ms interval that re-docks and self-cancels once closed); a shared one-shot latch runs the re-dock exactly once whichever trigger fires first. PipSession gains an optional PopoutPoll and a close method so teardown removes the pagehide listener, cancels the poll interval, and closes the window, leaking no window, WebSocket, listener, or timer. The chosen live-canvas-reparent strategy (over a fresh popup session) is recorded in the detach_window doc comment.

The VAPP-70 source guard is extended to fail the build if the disabled-Detach tooltip returns or the window.open popout call disappears.

#VAPP-75

Browsers without Document Picture-in-Picture (Firefox today) had a disabled Detach button with a "Firefox does not support it yet" tooltip, so Detach was dead there. This adds the non-PiP popout path VAPP-50 promised: when `pip_supported()` is false the toolbar now renders a single enabled Detach/Re-dock toggle that opens a same-origin `window.open` popup sized to the remote framebuffer and reparents the live canvas node into it. The popup shares this page's JS/wasm realm, so the WebSocket transport, 2D context, and input listeners travel with the node and the session keeps streaming with no reconnect. Chromium keeps the existing Document PiP path unchanged. The PiP reparent/re-dock body is factored into `move_canvas_into_window`, shared by both paths via a `poll_closed` flag. Popups do not reliably deliver `pagehide` to the opener, so the popout path also polls `window.closed` (a 250ms interval that re-docks and self-cancels once closed); a shared one-shot latch runs the re-dock exactly once whichever trigger fires first. `PipSession` gains an optional `PopoutPoll` and a `close` method so teardown removes the pagehide listener, cancels the poll interval, and closes the window, leaking no window, WebSocket, listener, or timer. The chosen live-canvas-reparent strategy (over a fresh popup session) is recorded in the `detach_window` doc comment. The VAPP-70 source guard is extended to fail the build if the disabled-Detach tooltip returns or the `window.open` popout call disappears. #VAPP-75
feat(desktop): window.open popout Detach for Firefox
All checks were successful
Check / fmt + clippy + build + tests (pull_request) Successful in 36s
Create release / Create release from merged PR (pull_request) Has been skipped
5c5aa3159d
Browsers without Document Picture-in-Picture (Firefox today) had a disabled Detach button with a "Firefox does not support it yet" tooltip, so Detach was dead there. This adds the non-PiP popout path VAPP-50 promised: when `pip_supported()` is false the toolbar now renders a single enabled Detach/Re-dock toggle that opens a same-origin `window.open` popup sized to the remote framebuffer and reparents the live canvas node into it. The popup shares this page's JS/wasm realm, so the WebSocket transport, 2D context, and input listeners travel with the node and the session keeps streaming with no reconnect. Chromium keeps the existing Document PiP path unchanged.

The PiP reparent/re-dock body is factored into `move_canvas_into_window`, shared by both paths via a `poll_closed` flag. Popups do not reliably deliver `pagehide` to the opener, so the popout path also polls `window.closed` (a 250ms interval that re-docks and self-cancels once closed); a shared one-shot latch runs the re-dock exactly once whichever trigger fires first. `PipSession` gains an optional `PopoutPoll` and a `close` method so teardown removes the pagehide listener, cancels the poll interval, and closes the window, leaking no window, WebSocket, listener, or timer. The chosen live-canvas-reparent strategy (over a fresh popup session) is recorded in the `detach_window` doc comment.

The VAPP-70 source guard is extended to fail the build if the disabled-Detach tooltip returns or the `window.open` popout call disappears.

#VAPP-75
Claude-Run deleted branch feat/VAPP-75-firefox-window-open-detach 2026-07-06 16:05:14 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-apps!87
No description provided.