Concepts — Cross-Layer Vertical Traces

The join point. Spec areas §25 (archaeology), §43 (cross-layer traces). Prerequisites: accumulate across Phases 2–5; each trace has its own gate.


1. Why this is the centre of the track

Every other module studies one layer. This module is where the layers become one system. The capability being built is specific and testable:

Given any operation a web developer can perform, trace it downward until you run out of mechanism — and know where you ran out.

The last clause matters more than the first. A trace that stops at "and then Blink handles it" is not a failure; a trace that stops there without noticing is. The gap is the next thing to study, which is why every trace records its deepest resolved layer.


2. The trace template

Application code
   -> Framework (scheduler, reactivity, reconciliation)
   -> DOM / Web API surface
   -> Binding layer (IDL, V8 wrapper, conversion, checks)
   -> Blink (which subsystem, which data structure)
   -> Chromium (which process, which thread, any Mojo hop)
   -> Compositor (does it get here? what changes?)
   -> GPU (raster? draw only?)
   -> Pixels

For every trace, answer:

  1. Which layers are involved, and which are skipped?
  2. Which thread and process is each step on?
  3. What is deferred vs immediate?
  4. What would make this operation dramatically more expensive?
  5. What evidence did you use at each layer — source, trace, or debugger?

Question 1's second half is the valuable one. Knowing that transform skips layout and paint is worth more than knowing what it does do.


3. Method

Use all three instruments, in bi-01's ladder order:

  • Tracing first to find out what actually happened and on which thread.
  • Source to find the mechanism.
  • Debugger to confirm ordering and exact values where it matters.

Record each trace in its own docs/ file. Update the ledger in PROGRESS.md §4 with the deepest layer you genuinely resolved — not the deepest layer you can name.


4. The eight traces

Trace 1 — classList.add('active') · after bi-07

Focus: invalidation scope. The interesting result is how much work is not done.

  • Framework: did anything schedule, or was this a direct DOM call?
  • Binding: DOMTokenList mutation; what conversion happens?
  • Blink: attribute change → does any rule mention .active? If not, nothing is invalidated.
  • If yes: which invalidation sets, and what scope do they mark?
  • When does recalc actually run? What forces it earlier?
  • Then: construct a variant where the same visual change invalidates 100× more elements.

Trace 2 — getBoundingClientRect() · after bi-08

Focus: forced synchronous layout — the highest-value trace in the set.

  • Binding: which getter, and is it marked as forcing layout?
  • Blink: what does "update style and layout" actually do when called mid-task?
  • What is the cost when nothing is dirty? When everything is?
  • Build the read/write loop, measure at n = 100/1000/5000, and state the complexity class.
  • Where in the trace does the layout appear, and how is it attributed?

Trace 3 — DOM insertion (appendChild) · after bi-04

Focus: how much happens before the pointer update (bi-04 §3 question 1).

  • Hierarchy checks, adoption, removal from previous parent.
  • Custom element reactions and mutation observers: queued when, delivered when?
  • Style/layout/paint invalidation consequences.
  • Compare inserting into a detached tree vs the live document. Explain the difference.

Trace 4 — click · after bi-10, bi-11

Focus: the input path across processes and threads.

  • OS event → browser process → compositor thread: can the compositor handle it alone?
  • Hit testing: compositor-side vs main-thread. What decides?
  • Dispatch to the renderer main thread; event queued at input priority.
  • DOM event dispatch: capture, target, bubble; retargeting across shadow boundaries.
  • Framework handler; state update; scheduling.
  • Then measure: with a 300 ms task running, what is the input delay and where is it spent?
  • Finally: dispatch a synthetic click and diff the trace against a real one (fw-12).

Trace 5 — scroll · after bi-10

Focus: why this usually never reaches your JS.

  • Compositor-driven path end to end. Where is the scroll offset stored?
  • Add a non-passive wheel listener. Re-trace. What changed and where?
  • scroll event delivery: when, at what priority, and why it is always one frame behind.
  • Tiles, raster priority, checkerboarding.

Trace 6 — setCount(count + 1) · after fw-04

Focus: the full stack, and which stages get skipped. This is the capstone trace.

  • Framework: state slot write, schedule, batching, priority.
  • Render phase (interruptible), commit phase (atomic).
  • The actual DOM operations — count them.
  • Style invalidation scope; layout needed or not; paint needed or not; compositor-only or not.
  • Frame presentation.
  • Then produce the two variants: one where the update is compositor-only, and one where it forces layout. Explain the difference at every layer.

Trace 7 — fetch() · after bi-02

Focus: crossing the process boundary.

  • Binding, promise creation, which microtask resolves it.
  • Renderer → network service process via Mojo. The renderer never holds the socket.
  • Where are CORS, CSP, and cross-origin read blocking enforced, and why there?
  • Response streaming back; which thread does the body arrive on?
  • Promise resolution → microtask → your .then. Does this yield to rendering? (bi-11)

Trace 8 — requestAnimationFrame · after bi-11

Focus: the rendering opportunity.

  • Where is the callback stored, and who runs it?
  • Its position relative to style/layout/paint in the update-the-rendering steps.
  • Relationship to the compositor's BeginFrame and to vsync.
  • What happens when a frame is skipped, and how do you observe that?

4.5 The layer-skipping table — the thing you are really learning

Every trace is ultimately teaching one table. Fill it in from your own traces; the version below is the shape, not the answer.

OperationFrameworkDOMStyleLayoutPaintCompositeGPU
classList.add (no matching rule)skipped
classList.add (colour only)
classList.add (changes width)
transform animation
top animation
compositor scroll
scroll with non-passive listener???
getBoundingClientRect (clean)cached
getBoundingClientRect (dirty)forcedforced
setState → text changemaybe

The column that matters is "skipped." Anyone can learn that layout is expensive. The Principal-level skill is knowing precisely which changes avoid it, and being able to say why from mechanism rather than from a memorised list — because the list changes and the mechanism does not.

Cross-check every row against bi-07's css_properties.json5 invalidate: data. If your trace disagrees with the declared invalidation, one of the two is telling you something interesting.


4.6 How to run a trace when you cannot debug

The local build is blocked on this machine (bi-00-roadmap/docs/chromium-build-debug-trace.md), which removes the debugger but not the other two instruments. All eight traces are largely doable with:

  • Perfetto / DevTools Performance on stock Chrome — rung 3,
  • git grep and git log -S on the local checkout — rung 2, and faster than Code Search,
  • the declarative filescss_properties.json5, .idl, .mojom, runtime_enabled_features.json5.

For each trace, record the evidence type per layer. A trace resolved entirely from source reading is weaker than one where a trace event confirmed the code actually ran — and noticing that difference is itself part of the exercise. Source tells you what can happen; a trace tells you what did.


4.7 Trace-writing standards

A finished trace should let a reader who has not done it reach the same conclusion. Concretely:

  • Name the process and thread at every step. "Blink handles it" is not a resolved layer.
  • State deferred vs immediate. Most of the pipeline is deferred; saying so is most of the insight.
  • Quote your evidence. A trace event name, a source symbol, a spec sentence, a measured number.
  • Record where resolution was lost, explicitly. PROGRESS.md's ledger has a column for it.
  • Build the variant. Every trace requires one constructed case that changes which layers are involved — that is what proves you understood the mechanism rather than memorised a path.

A trace with an honest "I could not resolve below the compositor commit" is worth more than one that hand-waves through to the GPU. The gap is your next study target; a papered-over gap is a hole you will fall into later.


5. Archaeology missions (§25)

Same template, applied to subsystems rather than operations. For each: public API → binding → Blink → Chromium subsystem → process/thread → downstream effect.

document.createElement · classList.add · getBoundingClientRect · requestAnimationFrame · fetch · CSS Grid · click/input handling · accessibility tree

Overlap with the traces is intentional — the traces follow one invocation, the missions map one subsystem. Doing both on the same API from two directions is how the picture closes.


6. Verification

A trace is complete when:

  • Every layer is either resolved with evidence, or explicitly recorded as unresolved
  • Process and thread are named at every resolved step
  • Deferred vs immediate is stated at every step
  • At least one variant is constructed that changes which layers are involved
  • The evidence type (source / trace / debugger) is recorded per layer
  • PROGRESS.md §4 ledger updated with the deepest genuinely-resolved layer

7. Principal Engineer Review

  1. Pick any two traces. Name the layer where they diverge most, and what that says about the cost model a developer should carry.

  2. Trace 6 in its compositor-only variant skips layout, paint and raster. Give the rule a developer could apply without knowing any of this, and say where the rule fails.

  3. Which of the eight operations has the largest gap between "what developers think happens" and what happens? Justify.

  4. You are teaching this to a team with two hours. Which single trace do you use, and why that one?

  5. For each trace, name the one measurement you would put on a dashboard to detect regression.

  6. Where did you run out of resolution most often? What would it take to close that gap, and is it worth it?