Repository navigation
Add an opt-in cascade that orders rules by specificity and source order #535
Description
Part of #520 (step 15). Behind an option; with it off, output stays byte-identical.
Today's cascade is a fixed order of selector groups (see #520). So div p beats #id, p.c beats #i, body p beats .c, and among descendant rules the one lifted from the nearest ancestor wins whatever its weight. Classes apply in alphabetical order, and a selector written twice is merged into one key: .a{red} .b{green} .a{blue} on class="a b" gives green where a browser gives blue.
Design. Use the compiled rules from #538 for every selector, and replace the key strings and nested trees. Sort the matches by specificity, then position. Apply, in order:
- inherited values
- built-in defaults
- presentational attributes, as author rules with zero specificity (Built-in defaults override HTML attributes, so
<hr color>and<hr width>are ignored #531) - normal declarations
- inline style
- stylesheet important declarations, sorted by specificity and then position (Honour
!importantin stylesheets and inline styles #532) - inline important declarations
This removes blk[]['cascadeCSS'], CssManager::$tablecascadeCSS and fixedPosBlockCascadeCSS; the class combinations and their memory cost; and most of mergeFullCssRules(), mergeDescendantCss() and mergeTableDescendantSelectors(). Positions are kept per declaration, not per key, which a selector written twice needs.
It must keep:
- inherited values and the built-in defaults, applied before the stylesheet.
mergeBlockCascadingCss()lifts cascade subtrees, which goes, and inherits block properties, which Inherit from the parent element's computed values under the opt-in cascade #547 replaces with the parent's computed values under this option. Split it first. - the border dominance level for table cells (
setDominanceFromProperties(…, 9)) for every rule that reaches a cell previewBlockCss(), which must stay free of side effects- the other readers and writers of
CssManager::$CSS:@page(src/Mpdf.php:16214onwards), SVG's class lookup (src/Image/Svg.php:3843), and the writers that setBODY(src/Mpdf.php:12200-12244). Keep the array, or give them something that answers the same way. - the behaviours from Match descendant selectors inside an absolutely positioned block (#474) #493 and Match descendant rules naming a table or cell against the content of the cell (#223) #507. Run
PositionedBlockSelectorTestandTableCellDescendantSelectorTestunder both cascades.
Option. A ConfigVariables key (for example 'cssCascade' => 'legacy'|'standard'), legacy by default, with the legacy merger left in place. GravityPDF templates are likely written against today's order. Decide separately, and later, whether it becomes the default.
Case. Class and id names match case-insensitively today. That is what a browser does in quirks mode, which it uses for HTML with no doctype. Either keep case-insensitive matching here, or make it depend on the document having a doctype. The same decision applies to how tables inherit in #543. Case-sensitive matching needs the original values, which the tokenizer uppercases (src/Mpdf.php:15077).
Performance. Compare with a 300-rule, 300-element document and the 2,000-cell table from #526, cold and hot, averaged.
Tests. The precedence table in #520 as a data provider, run under both cascades. Legacy expectations stay as they are; standard ones follow the browser column. Run the snapshot suite under both.
Activity
jakejackson1 commentedon Sep 30, 2026
Done in #631.