fix: retain only valid cascade winners

This commit is contained in:
Kinneyzhang 2026-08-28 22:06:17 +08:00
parent c79eb796b4
commit 13559a41f0
4 changed files with 80 additions and 5 deletions

View File

@ -54,6 +54,7 @@ Do not pass both `:stylesheet` and `:rules`. `:rules` accepts the rule list retu
- `(ecss-computed-style-custom-properties STYLE)` returns a deterministic custom-property plist.
- `(ecss-computed-style-active-properties STYLE)` returns property IDs supplied by valid declarations or active inheritance, preserving explicit nil versus absence. An invalid winner falling back to the initial value is not presented as an active declaration.
- `(ecss-computed-style-specified-properties STYLE)` returns longhand property IDs that have cascade winners, independent of whether provenance was requested.
- `(ecss-computed-style-precedence-winner STYLE PROPERTIES)` returns the property ID whose already-selected declaration winner has the highest cascade precedence. It never reruns cascade or requires public provenance.
- `(ecss-computed-style-copy-with-values STYLE VALUES &key active-properties specified-properties)` returns an immutable-style copy for a consumer that has already proved selector/cascade facts unchanged and only needs to replace computed values.
- `(ecss-computed-style-provenance STYLE)` returns winner, origin, importance, layer, specificity, scope/source/declaration order, validity, and fallback facts.
- `(ecss-computed-style-diagnostics STYLE)` returns deterministic diagnostics such as custom-property cycles.

View File

@ -54,6 +54,7 @@ ORIGIN 是 `ua`、`user`、`author`、`animation` 或 `transition`。Inline decl
- `(ecss-computed-style-custom-properties STYLE)` 返回 deterministic custom-property plist。
- `(ecss-computed-style-active-properties STYLE)` 返回由有效 declaration 或 active inheritance 实际提供的 property ids保留 explicit nil 与 absence 的区别invalid winner 回退到 initial 时不伪装成 active declaration。
- `(ecss-computed-style-specified-properties STYLE)` 返回存在 cascade winner 的长手 property ids独立于是否请求 provenance。
- `(ecss-computed-style-precedence-winner STYLE PROPERTIES)` 返回这些 canonical property 已选 winner 中 cascade 优先级最高的 property ID不会重跑 cascade也不要求公开 provenance。
- `(ecss-computed-style-copy-with-values STYLE VALUES &key active-properties specified-properties)` 在 selector/cascade 事实已证明不变、只需替换 computed values 时返回新的 immutable-style copy。
- `(ecss-computed-style-provenance STYLE)` 返回 winner、origin、important、layer、specificity、scope/source/declaration order、valid/fallback 等事实。
- `(ecss-computed-style-diagnostics STYLE)` 返回 custom-property cycle 等 deterministic diagnostics。

View File

@ -55,7 +55,7 @@
(:conc-name ecss--computed-style-))
"Computed declarations, custom properties, metadata, and diagnostics."
schemas values custom-properties active-properties specified-properties
provenance diagnostics)
winners provenance diagnostics)
(cl-defstruct (ecss-computed-property-fact
(:constructor ecss--make-computed-property-fact))
@ -1061,7 +1061,7 @@ handle explicit sources, and DIAGNOSTICS records variable failures."
PARENT-STYLE and CUSTOM-TABLES supply inherited values. SUBJECT and RESOLVER
handle explicit sources. DIAGNOSTICS records failures; PROVENANCE-P retains
facts."
(let (values active specified provenance)
(let (values active specified winners provenance)
(dolist (property (ecss--schema-set-order schemas))
(let ((schema (gethash property (ecss--schema-set-table schemas))))
(unless (ecss-property-schema-shorthand schema)
@ -1076,13 +1076,15 @@ facts."
(ecss--parent-property-active-p parent-style property)))
(push property active))
(when winner
(push property specified))
(push property specified)
(unless invalid
(setq winners (plist-put winners property winner))))
(when provenance-p
(setq provenance
(plist-put provenance property
(ecss--property-provenance
winner invalid source))))))))
(list values (nreverse active) (nreverse specified) provenance)))
(list values (nreverse active) (nreverse specified) winners provenance)))
(defun ecss--validate-rule-declarations (schemas rule)
"Validate canonical declarations in RULE against SCHEMAS."
@ -1143,7 +1145,7 @@ non-nil, is the only function allowed to evaluate caller-owned value sources;
(custom (ecss--resolved-custom-properties
(car raw-facts) diagnostics)))
(let ((custom-tables (ecss--custom-value-tables custom)))
(pcase-let ((`(,values ,active ,specified ,property-facts)
(pcase-let ((`(,values ,active ,specified ,winners ,property-facts)
(ecss--compute-property-values
schemas table parent-style custom-tables subject value-resolver
diagnostics provenance)))
@ -1151,6 +1153,7 @@ non-nil, is the only function allowed to evaluate caller-owned value sources;
:schemas schemas :values values :custom-properties custom
:active-properties active
:specified-properties specified
:winners winners
:provenance (and provenance
(append (cadr raw-facts) property-facts))
:diagnostics (nreverse (car diagnostics))))))))
@ -1207,6 +1210,27 @@ the public boundary."
(ecss--copy-boundary-data
(ecss--computed-style-specified-properties style)))
(defun ecss-computed-style-precedence-winner (style properties)
"Return the highest-precedence declared property in PROPERTIES for STYLE.
PROPERTIES may use canonical IDs or aliases from STYLE's schema set. Nil is
returned when none has a cascade winner. The comparison reuses ECSS's exact
cascade ordering and does not require public provenance materialization."
(setq style (ecss--computed-style-check style))
(let ((schemas (ecss--computed-style-schemas style))
(winners (ecss--computed-style-winners style))
winner-property winner)
(dolist (property properties winner-property)
(let* ((canonical
(or (ecss-schema-set-canonical-id schemas property)
(signal 'ecss-invalid-declaration
(list :unknown-property property))))
(candidate (plist-get winners canonical)))
(when (and candidate
(or (null winner)
(ecss--candidate-higher-p candidate winner)))
(setq winner-property canonical
winner candidate))))))
(defun ecss-computed-style-provenance (style)
"Return defensive winner provenance from computed STYLE."
(ecss--computed-style-check style)

View File

@ -751,6 +751,55 @@
(should (equal (ecss-computed-style-specified-properties declared)
'(app/color app/width app/payload)))))
(ert-deftest ecss-cascade-test-cross-property-projection-reuses-cascade-order ()
"A consumer projection should choose among property winners without recascade."
(let* ((schemas (ecss-cascade-test--schemas))
(stylesheet (ecss-stylesheet-create))
(subject (ecss-cascade-test--subject
:id "hero" :classes '("target"))))
(ecss-stylesheet-add-rule
stylesheet schemas ".target" '(app/padding-left 1))
(ecss-stylesheet-add-rule
stylesheet schemas "#hero" '(app/width 2))
(let* ((ruled (ecss-compute-style
schemas subject :stylesheet stylesheet))
(inline (ecss-compute-style
schemas subject :stylesheet stylesheet
:declarations '(app/padding-left 3)))
(copy (ecss-computed-style-copy-with-values
inline (ecss-computed-style-values inline))))
(should (eq (ecss-computed-style-precedence-winner
ruled '(app/padding-left app/width))
'app/width))
(should (eq (ecss-computed-style-precedence-winner
inline '(app/padding-left app/width))
'app/padding-left))
(should (eq (ecss-computed-style-precedence-winner
copy '(app/padding-left app/width))
'app/padding-left))
(should-not (ecss-computed-style-provenance inline)))))
(ert-deftest ecss-cascade-test-cross-property-projection-skips-invalid-winner ()
"An invalid declaration must not override a valid cross-property fact."
(let* ((schemas (ecss-cascade-test--schemas))
(stylesheet (ecss-stylesheet-create))
(subject (ecss-cascade-test--subject
:id "hero" :classes '("target"))))
(ecss-stylesheet-add-rule
stylesheet schemas ".target" '(app/padding-left 5))
(ecss-stylesheet-add-rule
stylesheet schemas "#hero" '(app/width "invalid"))
(let ((style (ecss-compute-style
schemas subject :stylesheet stylesheet)))
(should (eq (ecss-computed-style-precedence-winner
style '(app/padding-left app/width))
'app/padding-left))
(should (memq 'app/width
(ecss-computed-style-specified-properties style)))
(should-not (memq 'app/width
(ecss-computed-style-active-properties style)))
(should (= (ecss-computed-style-value style 'app/width) 0)))))
(ert-deftest ecss-cascade-test-provenance-getter-copies-selector-data ()
(let ((schemas (ecss-cascade-test--schemas))
(stylesheet (ecss-stylesheet-create))