S5 browser live pixels: Chromium pages resize with their pane every frame (motion.frameOwner.pages, off)

Merged
LA
1d ago
+840-84

Summary

P0 (cmux-ad m14, Lawrence 2026-11-05): during key resizes and divider drags, in every frame every Chromium page must show live pixels rendered at that frame's pane size. Not frozen, not stretched, not the old frame anchored at top-left. Today a key resize holds the page at its start size (the old frame, clipped), and a drag at the default live mode shows the page some frames behind its divider.

This is FRAME OWNER stage S5, app half, behind one Debug switch motion.frameOwner.pages (BrowserResizeTunables.framesOwnedByCommit, default OFF). The fork half is on feat already: cef-156.0.15-cmux.44 (manaflow-ai/cef#27) carries API 26 (layer hosting) and API 27 (resize sync).

With the switch ON:

  • Every page window is hosted in the window's own layer tree (API 26), so the page's pixels commit in the same Core Animation transaction as its pane.
  • A key resize no longer holds the page. The chrome follows its pane (S3) and the page lays out at its page area in every frame.
  • A divider drag lays the page out live (#26595's resizeSync path), whatever browserResize.dragResize says.
  • The main thread never waits for the page. Chromium's resize wait (CATransactionCoordinator's pre-commit handler) runs on Chromium's UI thread, and in cmux that is the app's main thread (CEFMessagePump). So with S5 on, every pane deadline the fork gets (syncDeadline, dragResizeDeadline) is replaced by motion.frameOwner.pagesWait, which is 0 by default (CEFLayerHosting.waitMs). For API 27, 0 means no pre-commit wait, no GPU CA transaction and no post-commit wait. The main thread commits each resize frame at once. The page window's resize reaches Chromium in that turn. Chromium's frame at the new size shows through the hosted context when the GPU draws it, with no app commit. Until then the page shows its last frame inside its pane, unscaled, and the next frame catches up. pagesWait above 0 is only a measurement knob (it shows the cost of a same-frame page), capped at half the shortest display frame.
  • There is no timer and no held size.

With the switch OFF nothing changes.

Base: Austin Wang's #26595 (merged as cb362c9eb54), carried by Leo's #29679. #26595's drag bookkeeping moves from CEFHostView.swift to CEFTabContentView+PageSync.swift with a reason set (drag, key resize), so a drag and a key resize can overlap. split-resize-bench.py takes the resizeSync variant (#29679). Co-authored-by: Austin Wang, Leo.

Design: plans/cmux-next/cef-layer-hosting-DESIGN.md, section "FRAME OWNER S5".

Dates: this PR (OFF) opened by 2026-11-05 14:00Z. ON (default on, after the OFF/ON films pass) by 2026-11-06 20:00Z. The switch-deletion PR follows within 24 h of ON.

Known limit: with no main-thread wait, a page frame drawn after its pane's commit shows one or more refreshes later. The page is then at its pane's size from Chromium's next frame, not always in the pane's own frame. Chromium gets the same frame without a UI-thread wait through viz surface synchronization in its GPU process. The cmux equivalent is a fork call that hands the wait to the render server (the page's frame as a fence that the embedder's commit waits on there). That is the next fork API, and I own it; the films with S5 ON measure the lag.

Testing

Red/green command (fleet): cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref SHA --arg=suite --arg=Packages/macOS/CmuxNext --arg=CEFPageFrameOwnerTests --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app

With the switch ON, CEFPageFrameOwnerTests checks:

  • In every key-resize and drag step, the page is at its pane's size.
  • The page is hosted.
  • The fork gets no positive deadline (main-thread wait budget 0).
  • The measurement knob is capped.
  • A tab moved to another pane drops that pane's resize.
  • A switch flip reaches a shown page at once.
  • A page with docked DevTools follows its pane with geometry only: one layout call per step in its subtree, and both hosts at their part of the pane in every step. There is no hold.
  • A hosted page clips to its pane (m18: never protrudes).

With the switch OFF, the key-resize hold does not change.

Red runs (each fails on the base or before its fix):

CommitWhat it showsFleet step
04decc19b91Base version of the suitec7a120925aba4e2762cd9429
df0b121271dTab moveac90adc19f57ab9b5df9dafe
273f05b16eeSwitch flipd3a420895a27cc63eac26163
60a32b6a463Final flip test on base99a68cfeeac58cc19e4c21b1: 6 of 7 fail
3b3221e565aDevTools geometry and clip1097eb2114ac58fda98caa4e

Results at head 3d7033aa328b0c8d6158d54fe06b7e69ffa52894:

  • Green: step e8d1565acad0c0a9b31595c6, all 9 suites passed (CEFPageFrameOwnerTests, CEFDeferredTabShowTests, CEFLayerHostingTests, CEFLayerHostingPaneDragTests, BrowserChromePaneMotionParkTests, CEFDevToolsBackgroundTests, CEFDevToolsCloseTests, CEFDevToolsDockSideTests, CEFTrackerStatsReportTests).
  • Compile: job f1ad03e14f8c4e7a5d1815b6 passed.
  • Display fleet run (gui-session-v1, --display) at the exact head: step 68785989a437d8df39f60512 (cmuxs-Mac-mini-4), all 9 suites passed.

Observed / Not run

What earlier films showed:

  • STATUS 13652 and 13849, m14 judge, earlier heads: with the switch ON, key-resize page failures drop by about 4 to 10 times. Drags are not better.
  • STATUS 13849: at 8ce675c, a page with docked DevTools missed 35 to 40 frames ON against 4 to 6 OFF. The cause was a subtree relayout per frame. This head fixes that with a geometry-only path.

Not run yet: the m18 film of this code. pixel-perfect's round ab-s5m18 uses build 3b8c9bde at ca9b397. ca9b397 differs from this head only in how a rounded find or prompt bar hole is posted. The m18 judge counts page pixels outside the pane (must be 0) and lag (at most 2 refreshes). The switch turns ON by default in the next PR, after those films pass.

Known limits:

  • If two panes move at the same time, an old chrome's attach can end another pane's key-resize hold.
  • One drag listener per container cannot tell a late drag end apart from a new drag.

The fence (fork API 32, manaflow-ai/cef#29) is off today's path (m18): no page wait anywhere.

Changelog

none

Proof

No capture: the switch is off by default, so this PR changes no pixels. The ON PR carries the OFF/ON films.