Fix circular custom properties during computed style resolution - #242
Conversation
Resolve per-element custom-property dependency components iteratively before inheritance, including unused fallback edges. Preserve computed token values, substitute complete consumer and shorthand values, and apply computed-value defaults without recursive variable evaluation. Add regression coverage for cycles, inheritance, shared rules, CSSOM mutation, token boundaries, long chains, nested fallbacks, and bounded expansion. Document the substitution limit. Fixes AngleSharp#241 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
@FlorianRappl this is obviously AI generated (Astra) and I won't be offended if you prefer not to deal with this PR. At this point I don't even know if you think the issue needs to be solved yet. At least if that helps you validate a solution that's good enough for me. Otherwise don't hesitate to shut it down. |
|
Note: behavioral changes on noncyclic inputs:
|
|
Yes the behavioral changes should not be there. For instance, only the style computations should resolve CSS variables. This is also necessary and the real browser behavior. |
Keep variable resolution inside computed-style preparation; leave raw cascades and render-tree specified styles unchanged. Restore nested CssVarValue fallback trees and direct References-based computation, retaining iterative parsing, serialization, and fallback traversal. Honor caller-modified references in custom-property and shorthand resolution. Add compatibility regressions for raw declarations, public parser cursor and value-tree behavior, mutable references, and deep public fallbacks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Addressed in 99c535a. Variable resolution now stays in computed-style preparation; GetDeclarations, ComputeCascadedStyle, and render-tree SpecifiedStyle retain the raw expressions. Nested fallbacks again expose CssVarValue objects, and direct CssReferenceValue.Compute honors supplied/modified References and its previous first-success behavior. The fallback traversal remains iterative. Added compatibility regressions, including caller-modified custom-property and shorthand references. The full suite passes on .NET 8 and .NET 10 (2,167 tests each), including the original cycle and deep-fallback cases. |
FlorianRappl
left a comment
There was a problem hiding this comment.
Imho the PR is larger than it needs / should be, but as style computation is complex and most likely not yet fully there it should be a good basis.
I think we can merge this when the comments have been addressed and performance does not show regression (missing comparison: beforehand vs now - how much are the changes costing in terms of perf).
4f469d6
into
AngleSharp:hotfix/custom-property-cycles
|
There is a slight perf. penalty here, but it's acceptable: Baseline was:
With this PR we get:
|
Types of Changes
Prerequisites
Please make sure you can check the following two boxes:
Contribution Type
What types of changes does your code introduce? Put an
xin all the boxes that apply:Description
Circular CSS custom properties currently cause unbounded recursion during computed style resolution, terminating the host process with an uncatchable stack overflow. This change makes every member of a cyclic component guaranteed-invalid, allowing consumers to use fallbacks or normal computed-value defaulting instead.
Fixes: #241
Variable resolution is confined to computed-style preparation.
GetDeclarations,ComputeExplicitStyle,ComputeCascadedStyle, and render-treeSpecifiedStyleretain the original variable expressions. Computed results are separate declarations; stylesheet and inline values are not rewritten.Within computation, dependencies are discovered using the CSS tokenizer, including references in unused fallbacks but excluding literal strings, comments, and URLs. An iterative strongly connected component traversal resolves local custom properties before inheritance. Inherited aliases retain their parent's resolved token values, while declarations explicitly matching both parent and child resolve locally. Selector matching is reused rather than repeated to identify local properties.
Consumer and shorthand substitution preserves the complete token stream and token boundaries, deferring grammar conversion until the consuming property is known. Invalid-at-computed-value declarations inherit or use their initial value rather than exposing an earlier cascaded declaration. Custom-property names remain case-sensitive through CSSOM lookup and mutation.
Compatibility revision
The follow-up restores the three public behaviors identified in review: raw cascades do not resolve variables, nested
var()fallbacks remainCssVarValueobjects, and directCssReferenceValue.Computeuses the suppliedReferencesarray, including modified entries and its first-success behavior. Public parser source-position behavior is preserved. Caller-modified references are also honored in computed custom properties and shorthands. Nested fallback parsing, serialization, and computation remain iterative.Bounds and limitations
To bound exponential expansion during style computation, substituted values have a documented limit of 1,048,576 UTF-16 code units, including inserted token separators. Existing property grammar and layout limitations remain unchanged.
Validation
-p:TargetFrameworks=net10.0 -p:RestoreEnablePackagePruning=falseto retain the existing test dependency references without editing project manifests.netstandard2.0,net8.0, andnet10.0passed with no warnings or errors. Windows-onlynet462andnet472targets were not exercised on macOS.