Repository navigation
Conversation
- Sorted selector candidates are cached per style scope, keyed by type, id and classes, so views that share them stop re-collecting and re-sorting the same rules. - A view whose matched rules are the ones it last applied (no var() or calc() values, no animations, no local writes since) skips the property pass. - A className change re-matches only the descendants whose selectors read a changed class outside their subject. Scoped CSS variables and class attribute selectors still restyle the whole subtree. - Views styled with the same color string share one Color instance. - Each property's `css:` key string is built once.
|
View your CI Pipeline Execution ↗ for commit 1315833
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
commit: |
|
Thanks @triniwiz. We're targeting 9.2 for this one rather than the 9.1.3 patch: it changes when views restyle, the review found a regression, and the benchmark gain (1–3%) is within run-to-run noise. Regression (fails on this PR, passes on After a
A theme class on a page with variables defined on cards inside it is a realistic trigger. One fix that passed all of these plus the full suite: walk the children manually, and when a restyled child has (or had) scoped variables, restyle everything below it. Behavior change: descendants that are no longer restyled on a class change also no longer restart their CSS keyframe animations. Arguably more correct, but visible. What checked out: skipping an identical rule re-apply, the sorted candidate cache (selectors version + media epoch), shared Note this branch includes #11505's commit, so the two land together. |
A className change re-matched only class-dependent descendants and fell
back to the whole subtree only when the changed view itself held scoped
CSS variables. A restyled descendant that defined variables was not
followed down, so views below it reading them through var() or calc()
kept stale values, e.g. `.dark .card { --bg: blue }` with
`.card Label { color: var(--bg, red) }`.
The walk now restyles the whole subtree below any restyled descendant
that has, or had, scoped variables.
PR Checklist
What is the current behavior?
classNamechange re-matches the view and every descendant.color: #1F2330parses a newColor(and on iOS creates its ownUIColor).What is the new behavior?
var()orcalc()values, no keyframe animations and no local writes since, the property pass is skipped.classNameinvalidation. Each selector scope records which classes selectors read outside their subject (ancestor and sibling compounds, functional pseudo-class arguments), and which subjects they gate. AclassNamechange restyles the view, then only descendants that are subjects of a selector reading a changed class. Scoped CSS variables reach descendants throughvar(), so the whole subtree is restyled below the changed view, and below any restyled descendant, that has or had scoped variables. It also falls back to the whole subtree when a[class...]attribute selector reads ancestors.Colorper string;Coloris immutable.css:key string for each property is built once.Behavior change
Descendants that are no longer restyled on a class change no longer restart their CSS keyframe animations. Before, every
classNamechange stopped and replayed the animations of the whole subtree; now only restyled views do.Numbers
On its own the gain on these benchmarks is 1 to 4%, within run-to-run noise: their class toggle styles most of the views it affects, so scoped invalidation rarely skips work here. The gains are larger on top of #11507 and #11510 (all of the series together at size M: attach 0.62 to 0.76x, restyle 0.90x).
Resize only lays out and runs no CSS; its 2% difference is within run-to-run noise.
How this was measured
xplat-benchmarks
ns-coreapp (plain core, no plugins), iPhone 17 Pro Max simulator, iOS 26.4. Each scenario mounts about 1,000 views at size M. Base and change were installed alternately for 3 rounds of 10 measured iterations, median ms, lower is better. "Attach" is assigning the built tree to a loaded parent (native view creation, CSS, native setters); "painted" ends one frame after layout. A GC is forced before each mutation, as it already is after each unmount, so a collection pause lands in the same phase for both builds.Stacked on #11505. Part of a series: #11505, #11506 (this), #11507, #11510.