code wiki / (root) / nx_css_apply.nx

nx_css_apply.nx source

↩ module page · 181 lines · 7523 B

1// nx_css_apply.nx -- CSS cascade for the Nishi browser. Phase 2 2// fourth primitive after nx_css_tokenize, nx_css_parse, 3// nx_css_selector_match. Walks a caller-supplied element array, 4// matches every parsed CssRule against every element, and emits a 5// flat `CssComputedDecl[]` of (element_idx, declaration) pairs in 6// SOURCE ORDER. Cascade-by-source-order means downstream queries 7// pick the LAST matching entry for "winner" semantics. 8// 9// What it does: 10// - O(R × E) match loop -- fine at andelinwest.com scale 11// (~50 rules × ~100 elements = 5k pairs). Bigger sites get a 12// reverse selector index in Phase 2c (queued). 13// - Per matched rule, the rule's decl_count declarations are 14// copied into the output array, each tagged with element_idx. 15// - Within an element_idx group, declarations appear in source 16// order (stylesheet position, then rule position, then decl 17// position). 18// - Lookup helper `nx_css_lookup_property` scans the flat array 19// and returns the LAST entry whose property name byte-equals 20// the query for the given element. This is the value the 21// layout / render layers consume. 22// 23// What it does NOT handle yet (named Phase 2b/2c improvements): 24// - specificity (per CSS spec: id > class > tag; ties broken by 25// source order). Phase 2 ships SOURCE-ORDER-ONLY cascade -- 26// adequate for hand-written stylesheets where specificity is 27// implicit. 28// - !important 29// - inherited properties (color, font-* propagate to children; 30// needs DOM tree walk + parent computed_style lookup) 31// - initial / inherit / unset / revert keyword values 32// - reverse selector index for large sheets 33// 34// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 35// gap list is EXACT and tested. 36// 37// genealogy_id: w3c_css_cascade_module_level_4 + 38// substrate_browser_phase_2_apply 39// lineage_id: nishi_browser_css_apply_v1 40// 41// nx_safety_envelope: 42// intended_use: "CSS cascade -- browser front-end style apply" 43// sil_target: SIL1 44// evidence: [W3C_CSS_cascade_canonical_basis, 45// bounded_O_R_E_match_loop, 46// source_order_explicit, 47// pure_function_caller_owned_memory] 48// verdict: NOT_YET_EVALUATED 49 50import "nx_syscalls.nx" 51import "nx_css_tokenize.nx" 52import "nx_css_parse.nx" 53import "nx_css_selector_match.nx" 54 55// One entry in the flat cascade output array. 56struct CssComputedDecl { 57 element_idx: i64, 58 prop_off: i64, 59 prop_len: i64, 60 val_kind: i64, 61 val_off: i64, 62 val_len: i64, 63 unit_off: i64, 64 unit_len: i64 65} 66 67const NX_CSS_COMPUTED_DECL_BYTES: i64 = 64 68 69// ---- helpers ---- 70 71// Byte-equal compare two regions of the same source buffer. Same as 72// the matcher's helper, duplicated here to avoid cross-module 73// private exposure -- nxc2 doesn't have a shared-private convention 74// yet and the helper is 8 lines. 75func _css_apply_bytes_equal(src: *u8, a_off: i64, a_len: i64, 76 b_off: i64, b_len: i64) -> i64 { 77 if a_len != b_len { return 0 } 78 var i: i64 = 0 79 while i < a_len { 80 let ca: i64 = (src[a_off + i] as i64) & 255 81 let cb: i64 = (src[b_off + i] as i64) & 255 82 if ca != cb { return 0 } 83 i = i + 1 84 } 85 return 1 86} 87 88// ---- public API ---- 89 90// Apply all rules to all elements. Output goes into `computed` array, 91// up to `max_computed` entries. Returns 1 on success, 0 if the output 92// array is too small for the matches found (caller can retry with a 93// larger array; partial fill is preserved up to overflow). 94// 95// `src` is the shared byte buffer that backs both the parsed rules 96// (selector / decl property / decl value offsets all point here) and 97// the element views (tag / id / class offsets all point here). 98// 99// `out_count` is written with the number of entries actually filled. 100func nx_css_apply(src: *u8, 101 rules: *CssRule, n_rules: i64, 102 decls: *CssDeclaration, n_decls: i64, 103 elements: *CssElement, n_elements: i64, 104 computed: *CssComputedDecl, max_computed: i64, 105 out_count: *i64) -> i64 { 106 var written: i64 = 0 107 var e: i64 = 0 108 while e < n_elements { 109 let el: *CssElement = (elements as *u8 + (e as nx_size) * (NX_CSS_ELEMENT_BYTES as nx_size)) as *CssElement 110 var r: i64 = 0 111 while r < n_rules { 112 let rule: *CssRule = (rules as *u8 + (r as nx_size) * (NX_CSS_RULE_BYTES as nx_size)) as *CssRule 113 let m: i64 = nx_css_selector_match(rule, el) 114 if m == 1 { 115 // Copy each of the rule's declarations into the 116 // output, tagged with this element's index. 117 var d: i64 = 0 118 while d < rule.decl_count { 119 let src_decl_idx: i64 = rule.decl_first + d 120 if src_decl_idx >= n_decls { 121 // Defensive: malformed input. Stop emitting 122 // for this rule. 123 d = rule.decl_count 124 } else { 125 if written >= max_computed { 126 out_count[0] = written 127 return 0 128 } 129 let sd: *CssDeclaration = (decls as *u8 + (src_decl_idx as nx_size) * (NX_CSS_DECLARATION_BYTES as nx_size)) as *CssDeclaration 130 let cd: *CssComputedDecl = (computed as *u8 + (written as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 131 cd.element_idx = e 132 cd.prop_off = sd.prop_off 133 cd.prop_len = sd.prop_len 134 cd.val_kind = sd.val_kind 135 cd.val_off = sd.val_off 136 cd.val_len = sd.val_len 137 cd.unit_off = sd.unit_off 138 cd.unit_len = sd.unit_len 139 written = written + 1 140 d = d + 1 141 } 142 } 143 } 144 r = r + 1 145 } 146 e = e + 1 147 } 148 out_count[0] = written 149 return 1 150} 151 152// Query the cascade output for the LAST entry that: 153// - has element_idx == query_element 154// - has property name byte-equal to (prop_off, prop_len) in `src` 155// 156// Returns a pointer to the matching CssComputedDecl, or 0 if no 157// match. Caller reads val_kind / val_off / val_len / unit_off / 158// unit_len from the returned struct. 159// 160// "Last" implements source-order cascade winner-take-all per CSS 161// spec section 6.4.4 (Phase 2 source-order-only; specificity in 162// Phase 2b). 163func nx_css_lookup_property(src: *u8, 164 computed: *CssComputedDecl, count: i64, 165 query_element: i64, 166 prop_off: i64, prop_len: i64) -> *CssComputedDecl { 167 // Walk in reverse so the first byte-equal hit IS the last 168 // appearance in source order. 169 var i: i64 = count - 1 170 while i >= 0 { 171 let cd: *CssComputedDecl = (computed as *u8 + (i as nx_size) * (NX_CSS_COMPUTED_DECL_BYTES as nx_size)) as *CssComputedDecl 172 if cd.element_idx == query_element { 173 if _css_apply_bytes_equal(src, cd.prop_off, cd.prop_len, 174 prop_off, prop_len) == 1 { 175 return cd 176 } 177 } 178 i = i - 1 179 } 180 return 0 as *CssComputedDecl 181}