fix(juggler): record video headful and under a virtual display (#93)

After the screencastFrameAck/timestamp fix, recording worked headless but still
produced nothing usable anywhere else: `headless="virtual"` and plain headful
both emitted a valid .webm containing 24 pure-white frames -- Playwright's
filler for a screencast that never delivered a frame.

nsScreencastService only has a working source when the browser is headless
(HeadlessWindowCapturer). Outside headless, CreateWindowCapturer falls through
to libwebrtc's X11 window capturer, which fails three different ways:

  * no XComposite -> startVideoRecording() succeeds and then never delivers a
    frame. This is Camoufox's own Xvfb configuration, which passes
    `-extension COMPOSITE`;
  * XComposite enabled -> the browser segfaults during capture (reproduced on
    the shipped 152.0.4-beta.28 as well, so it is not specific to this branch);
  * Wayland -> nsWindow::GetNativeData(NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID) is
    documented as unhandled and returns null, so the service throws
    NS_ERROR_FAILURE ("Failed to get native window id") and no capture starts.

Capture from the compositor instead when not headless, via
WindowGlobalParent.drawSnapshot() -- the same call Page.screenshot already
uses, which is why screenshots have always worked in every mode. It renders
page content directly and does not care about the windowing system.

The tick is ack-driven, mirroring nsScreencastService's kMaxFramesInFlight = 1,
so a slow consumer throttles capture rather than queueing JPEGs. Headless keeps
the native C++ capturer, which is cheaper and already correct.

Measured on the packaged Linux build, 3s recording of an animated page, frames
decoded to PNG and inspected rather than trusting file existence:

                        before                  after
  headless              100 frames, real        unchanged, real
  headless="virtual"    24 frames, all white    100 frames, real
  headful (Xvfb, X11)   24 frames, all white    99 frames, real
  headful (Wayland env) no capture at all       99 frames, real

tests/async/test_video.py passes 5/5 both headless and headful. Enabling
Composite no longer crashes either, since X11 window capture is now unused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jake Writer
2026-07-28 11:58:09 -06:00
parent 775fbe0552
commit fa8a93577e
2 changed files with 139 additions and 15 deletions
+8 -8
View File
@@ -32,19 +32,19 @@ SCREEN_ENV_VAR = "CAMOUFOX_VIRTUAL_DISPLAY_SIZE"
# The Composite extension, disabled by default (Xvfb's `-extension COMPOSITE`).
#
# This was briefly enabled by default on the theory that #93 (no video under
# headless="virtual") was caused by disabling it. Measurement says otherwise:
# headless="virtual") was caused by disabling it. It was not: #93 was a juggler
# bug, fixed by capturing the screencast from the compositor instead of from
# libwebrtc's X11 window capturer. Both states were measured before that fix:
#
# composite off, record_video_dir -> a valid .webm of 24 pure-white frames
# composite ON, record_video_dir -> browser dies with SIGSEGV, no video
# composite ON, no recording -> fine
#
# So compositing does not fix #93, and turning it on converts a blank recording
# into a crash whenever someone records under a virtual display. Reproduced on
# both this build and the shipped 152.0.4-beta.28, so the segfault is in the
# screencast capture path, not something this branch introduced.
#
# Left as an opt-in for hosts with real GL where it may behave differently:
# set CAMOUFOX_VIRTUAL_DISPLAY_COMPOSITE=1 to enable it.
# The segfault was inside the X11 capturer, which the browser no longer uses, so
# enabling Composite is no longer dangerous -- but it is also no longer good for
# anything, since recording never touches X11 window capture now. Leave it off
# (Camoufox's long-standing default) and keep the escape hatch:
# CAMOUFOX_VIRTUAL_DISPLAY_COMPOSITE=1 enables it.
COMPOSITE_ENV_VAR = "CAMOUFOX_VIRTUAL_DISPLAY_COMPOSITE"