Skip to content

OSR OnPaint intermittently returns visually incomplete frame after JS-ready and Invalidate with external begin frame #4166

@L0stInFades

Description

@L0stInFades

Describe the bug

In windowless/off-screen rendering (OSR), OnPaint can intermittently deliver a full-size image buffer that is visually incomplete after the page has reported that its JavaScript state is ready and the native host has invalidated the view.

The buffer dimensions and byte size are correct, and this does not appear to be a dirty-rect interpretation issue. The bad frame looks like an intermediate compositor result: the background is present, but some DOM/canvas/composited layers are missing or only partially updated, sometimes with a tiled/stair-step boundary. A later Invalidate / OnPaint for the same page state produces the complete image.

This is visible when using CEF OSR as a deterministic frame capture backend for video export, where each animation frame is driven by JavaScript and then captured from OnPaint.

To Reproduce

I have not reduced this to cefclient or cefsimple yet. The current reproduction is in a custom OSR application using CEF APIs through Rust bindings, but the flow maps directly to CEF windowless rendering APIs:

  1. Create a windowless browser with:
    • external_begin_frame_enabled = true on CefWindowInfo
    • external_message_pump = true
    • windowless_rendering_enabled = true
    • windowless_frame_rate = 30
    • shared_texture_enabled = false / regular OnPaint buffer path
  2. Use command-line switches including:
    • disable-gpu
    • disable-gpu-compositing
    • disable-dev-shm-usage
  3. Load an HTML page with complex animated composited content:
    • gradient background
    • DOM overlays
    • canvas-drawn elements
    • CSS clip/reveal effects
    • filters and/or 3D transforms
  4. Expose a JS method similar to setTime(ms) that updates the page to a deterministic animation time and reports a "frame ready" signal after the JS/React/canvas state update has completed.
  5. Native frame loop:
    • call JS setTime(ms)
    • wait for the JS ready signal
    • drain previously queued paint callbacks
    • call CefBrowserHost::Invalidate(PET_VIEW)
    • pump the external message loop / external begin frames
    • capture the resulting OnPaint buffer
  6. Repeat this for a sequence of frames.

Intermittently, the first OnPaint after the JS ready signal and invalidation contains an incomplete visual composite. Capturing another candidate for the same JS state usually produces the correct image.

Expected behavior

After the page has reached a stable JS state and the view is invalidated, there should be a reliable way for an OSR client to know when CEF has delivered a fully composited frame for that state.

Alternatively, if the first OnPaint after invalidation is allowed to represent an intermediate compositor state, the expected synchronization mechanism for deterministic frame capture should be documented.

Actual behavior

OnPaint sometimes returns a valid full-size BGRA buffer that is not visually complete for the current page state. Some composited layers appear missing or partially updated. A subsequent invalidation/capture for the same state produces the expected complete frame.

Workarounds tried

  • Waiting for JavaScript / React / canvas completion before invalidating the view.
  • Waiting for requestAnimationFrame on the page side before signaling readiness.
  • Draining stale paint callbacks before each capture.
  • Pumping external begin frames while waiting for OnPaint.

The reliable workaround so far is to capture multiple OnPaint candidates for the same JS state and choose the most complete candidate. This avoids the visual corruption, but it adds extra capture cost and suggests the first paint can be an intermediate compositor result.

Versions (please complete the following information):

  • OS: macOS 15.7.4 (Darwin 24.6.0, x86_64)
  • CEF Version: CEF 146.0.12 via cef-dll-sys 146.7.0+146.0.12 / Rust cef crate 146.7.0

Additional context

  • The page renders correctly in an interactive browser context; the issue is specific to OSR frame capture timing.
  • I have not yet reproduced this in cefclient or cefsimple.
  • The observed OnPaint buffer is the full image buffer, not only the dirty rect data.
  • The main question is whether this behavior is expected for OSR with external_begin_frame_enabled, and what synchronization point CEF recommends for deterministic capture of a fully composited frame.

Metadata

Metadata

Assignees

No one assigned

    Labels

    osrRelated to off-screen rendering

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions