This commit is contained in:
Kinneyzhang 2026-08-13 14:43:24 +08:00
parent 51feda4685
commit 0e6d76801a
6 changed files with 94 additions and 44 deletions

View File

@ -48,9 +48,11 @@ Do not pass both `:stylesheet` and `:rules`. `:rules` accepts the rule list retu
- `(ecss-computed-style-values STYLE)` returns a defensive values plist.
- `(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-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.
- `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` reads one value.
- `(ecss-computed-style-present-p STYLE PROPERTY)` detects an active property even when its value is nil.
- `(ecss-computed-style-specified-p STYLE PROPERTY)` detects whether a longhand property has a cascade winner.
Every aggregate getter returns a defensive copy. Mutating a returned value cannot change a computed style, schema set, or stylesheet.

View File

@ -48,9 +48,11 @@ ORIGIN 是 `ua`、`user`、`author`、`animation` 或 `transition`。Inline decl
- `(ecss-computed-style-values STYLE)` 返回 defensive values plist。
- `(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-provenance STYLE)` 返回 winner、origin、important、layer、specificity、scope/source/declaration order、valid/fallback 等事实。
- `(ecss-computed-style-diagnostics STYLE)` 返回 custom-property cycle 等 deterministic diagnostics。
- `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` 读取单值。
- `(ecss-computed-style-present-p STYLE PROPERTY)` 判断 property 是否 active即使值为 nil。
- `(ecss-computed-style-specified-p STYLE PROPERTY)` 判断长手 property 是否存在 cascade winner。
所有 aggregate getter 都返回 defensive copy。调用者修改返回值不会改变 computed style、schema set 或 stylesheet。

View File

@ -4,7 +4,7 @@
## Position
`ecss` is the generic pure-computation layer that answers which rule wins and what each final property value is. It transforms consumer-provided property schemas, subjects, stylesheets, inline declarations, and a parent computed style into computed values, custom properties, active properties, provenance, and diagnostics.
`ecss` is the generic pure-computation layer that answers which rule wins and what each final property value is. It transforms consumer-provided property schemas, subjects, stylesheets, inline declarations, and a parent computed style into computed values, custom properties, active properties, specified properties, provenance, and diagnostics.
```text
consumer schemas + subject adapter + stylesheet + parent style
@ -16,7 +16,7 @@ consumer schemas + subject adapter + stylesheet + parent style
CSS-wide/custom-property resolution → normalize/validate
computed style + provenance + diagnostics
computed style + specified properties + provenance + diagnostics
```
`ecss` does not know how computed values are displayed. TP may convert final text-property contributions into retained surface patches. Ebox may interpret final Box/Flex/Grid properties as measurement, layout, and painting consequences. Ordinary callers may use `ecss` without either package.

View File

@ -4,7 +4,7 @@
## 定位
`ecss` 是“规则如何赢、最终属性值是什么”的通用纯计算层。它把 consumer 提供的 property schemas、subjects、stylesheets、inline declarations 与 parent computed style 转换成 computed values、custom properties、active properties、provenance 和 diagnostics。
`ecss` 是“规则如何赢、最终属性值是什么”的通用纯计算层。它把 consumer 提供的 property schemas、subjects、stylesheets、inline declarations 与 parent computed style 转换成 computed values、custom properties、active properties、specified properties、provenance 和 diagnostics。
```text
consumer schemas + subject adapter + stylesheet + parent style
@ -16,7 +16,7 @@ consumer schemas + subject adapter + stylesheet + parent style
CSS-wide/custom-property resolution → normalize/validate
computed style + provenance + diagnostics
computed style + specified properties + provenance + diagnostics
```
`ecss` 不知道 computed values 最终如何显示。TP 可以把最终文本属性贡献转换成 retained surface patchEbox 可以把最终 Box/Flex/Grid 属性解释成 measurement、layout 与 painting 后果;普通调用者也可以完全不用 TP/Ebox。

View File

@ -46,8 +46,9 @@
(cl-defstruct (ecss-computed-style
(:constructor ecss--make-computed-style)
(:conc-name ecss--computed-style-))
"Computed declarations, custom properties, provenance, and diagnostics."
values custom-properties active-properties provenance diagnostics)
"Computed declarations, custom properties, metadata, and diagnostics."
values custom-properties active-properties specified-properties
provenance diagnostics)
(cl-defstruct (ecss--wide (:constructor ecss--make-wide)) kind)
(cl-defstruct (ecss--important (:constructor ecss--make-important)) value)
@ -429,13 +430,13 @@ optional selector limiting the rule to a matching subject or ancestor."
SPECIFICITY, DISTANCE, and DECLARATION-ORDER are the match precedence facts."
(pcase-let ((`(,raw . ,important) (ecss--unwrap-important value)))
(ecss--make-candidate
:property property :value (ecss--copy-boundary-data raw)
:property property :value raw
:origin (ecss-rule-origin rule)
:important important :inline nil :layer (ecss-rule-layer rule)
:layer-rank (ecss-rule-layer-rank rule) :specificity specificity
:scope-distance distance :source-order (ecss-rule-source-order rule)
:declaration-order declaration-order
:selector (ecss--copy-boundary-data (ecss-rule-selector rule)))))
:selector (ecss-rule-selector rule))))
(defun ecss--rule-candidates (rule subject adapter)
"Return candidates from matching RULE for SUBJECT through ADAPTER."
@ -460,7 +461,7 @@ SPECIFICITY, DISTANCE, and DECLARATION-ORDER are the match precedence facts."
collect
(pcase-let ((`(,raw . ,important) (ecss--unwrap-important value)))
(ecss--make-candidate
:property property :value (ecss--copy-boundary-data raw)
:property property :value raw
:origin 'author
:important important :inline t :layer nil :layer-rank nil
:specificity '(0 0 0)
@ -601,10 +602,11 @@ SCHEMAS validates inline declarations and ADAPTER exposes SUBJECT."
(values (ecss--parent-values parent-style)))
(if (and (ecss-property-schema-inherits schema)
(plist-member values property))
(cons (ecss--copy-boundary-data (plist-get values property)) 'inherit)
(cons (ecss--copy-boundary-data
(ecss-property-schema-initial schema))
'initial))))
(cons (if (ecss-computed-style-p parent-style)
(plist-get values property)
(ecss--copy-boundary-data (plist-get values property)))
'inherit)
(cons (ecss-property-schema-initial schema) 'initial))))
(defun ecss--wide-default (wide schema parent-style)
"Resolve non-revert WIDE value for SCHEMA using PARENT-STYLE."
@ -614,17 +616,15 @@ SCHEMAS validates inline declarations and ADAPTER exposes SUBJECT."
(let ((values (ecss--parent-values parent-style))
(property (ecss-property-schema-id schema)))
(if (plist-member values property)
(cons (ecss--copy-boundary-data (plist-get values property))
(cons (if (ecss-computed-style-p parent-style)
(plist-get values property)
(ecss--copy-boundary-data (plist-get values property)))
'inherit)
(cons (ecss--copy-boundary-data
(ecss-property-schema-initial schema))
'initial))))
(cons (ecss-property-schema-initial schema) 'initial))))
('unset (if (ecss-property-schema-inherits schema)
(ecss--wide-default (ecss-wide-value 'inherit)
schema parent-style)
(cons (ecss--copy-boundary-data
(ecss-property-schema-initial schema))
'initial)))))
(cons (ecss-property-schema-initial schema) 'initial)))))
(defun ecss--candidate-provenance (candidate &rest extra)
"Return public provenance for CANDIDATE followed by EXTRA facts."
@ -800,14 +800,14 @@ STACK detects cycles and DIAGNOSTICS records them."
(cons raw resolved)))
(defun ecss--resolve-property-value
(value schema parent-style custom diagnostics)
(value schema parent-style custom-tables diagnostics)
"Resolve VALUE for SCHEMA using PARENT-STYLE, CUSTOM, and DIAGNOSTICS."
(cond ((and (ecss--wide-p value)
(memq (ecss--wide-kind value) '(initial inherit unset)))
(ecss--wide-default value schema parent-style))
(t
(pcase-let ((`(,raw . ,resolved)
(ecss--custom-value-tables custom)))
custom-tables))
(cons (ecss--resolve-variable-value
value raw resolved nil diagnostics)
'declaration)))))
@ -824,16 +824,13 @@ STACK detects cycles and DIAGNOSTICS records them."
ecss--invalid))))
(defun ecss--compute-candidate-value
(candidate schema parent-style custom subject resolver diagnostics)
(candidate schema source parent-style custom-tables diagnostics)
"Resolve CANDIDATE for SCHEMA and computation context.
PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle
explicit sources, and DIAGNOSTICS records variable failures."
SOURCE is the already resolved candidate value. PARENT-STYLE and CUSTOM
supply inherited values, and DIAGNOSTICS records variable failures."
(let* ((property (ecss-property-schema-id schema))
(source (ecss--resolve-source
(ecss--candidate-value candidate)
property subject resolver))
(resolved (ecss--resolve-property-value
source schema parent-style custom diagnostics)))
source schema parent-style custom-tables diagnostics)))
(cons (if (eq (cdr resolved) 'declaration)
(ecss--normalize-property-value schema (car resolved))
(ecss--copy-boundary-data (car resolved)))
@ -846,7 +843,7 @@ explicit sources, and DIAGNOSTICS records variable failures."
(cons value source)))
(defun ecss--resolve-property-candidates
(schema candidates parent-style custom subject resolver diagnostics)
(schema candidates parent-style custom-tables subject resolver diagnostics)
"Resolve SCHEMA from ordered CANDIDATES.
PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle
explicit sources, and DIAGNOSTICS records variable failures."
@ -867,8 +864,8 @@ explicit sources, and DIAGNOSTICS records variable failures."
(setq winner candidate
resolved
(ecss--compute-candidate-value
candidate schema parent-style custom subject
(lambda (_value _property _subject) raw) diagnostics))))))
candidate schema raw parent-style custom-tables
diagnostics))))))
(let* ((invalid (and winner (eq (car resolved) ecss--invalid)))
(final (if (or (null winner) invalid)
(ecss--fallback-value schema parent-style)
@ -885,29 +882,33 @@ explicit sources, and DIAGNOSTICS records variable failures."
(list :source source :origin (if (eq source 'initial) 'ua 'inherit))))
(defun ecss--compute-property-values
(schemas table parent-style custom subject resolver diagnostics provenance-p)
(schemas table parent-style custom-tables subject resolver diagnostics
provenance-p)
"Compute SCHEMAS property values from TABLE and context.
PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle
PARENT-STYLE and CUSTOM-TABLES supply inherited values. SUBJECT and RESOLVER handle
explicit sources. DIAGNOSTICS records failures; PROVENANCE-P retains facts."
(let (values active provenance)
(let (values active specified provenance)
(dolist (property (ecss--schema-set-order schemas))
(let ((schema (ecss--schema-for schemas property)))
(unless (ecss-property-schema-shorthand schema)
(pcase-let ((`(,value ,winner ,invalid ,source)
(ecss--resolve-property-candidates
schema (gethash property table) parent-style custom
schema (gethash property table) parent-style
custom-tables
subject resolver diagnostics)))
(setq values (plist-put values property value))
(when (or (and winner (not invalid))
(and (ecss-property-schema-inherits schema)
(ecss--parent-property-active-p parent-style property)))
(push property active))
(when winner
(push property specified))
(when provenance-p
(setq provenance
(plist-put provenance property
(ecss--property-provenance
winner invalid source))))))))
(list values (nreverse active) provenance)))
(list values (nreverse active) (nreverse specified) provenance)))
(defun ecss--validate-rule-declarations (schemas rule)
"Validate canonical declarations in RULE against SCHEMAS."
@ -967,15 +968,17 @@ non-nil, is the only function allowed to evaluate caller-owned value sources;
table parent-style subject value-resolver provenance))
(custom (ecss--resolved-custom-properties
(car raw-facts) diagnostics)))
(pcase-let ((`(,values ,active ,property-facts)
(let ((custom-tables (ecss--custom-value-tables custom)))
(pcase-let ((`(,values ,active ,specified ,property-facts)
(ecss--compute-property-values
schemas table parent-style custom subject value-resolver
schemas table parent-style custom-tables subject value-resolver
diagnostics provenance)))
(ecss--make-computed-style
:values values :custom-properties custom :active-properties active
:provenance (and provenance
(append (cadr raw-facts) property-facts))
:diagnostics (nreverse (car diagnostics)))))))
(ecss--make-computed-style
:values values :custom-properties custom :active-properties active
:specified-properties specified
:provenance (and provenance
(append (cadr raw-facts) property-facts))
:diagnostics (nreverse (car diagnostics))))))))
(defun ecss--computed-style-check (style)
"Return STYLE or signal when it is not a computed style."
@ -1000,6 +1003,12 @@ non-nil, is the only function allowed to evaluate caller-owned value sources;
(ecss--copy-boundary-data
(ecss--computed-style-active-properties style)))
(defun ecss-computed-style-specified-properties (style)
"Return property identifiers with cascade winners from computed STYLE."
(ecss--computed-style-check style)
(ecss--copy-boundary-data
(ecss--computed-style-specified-properties style)))
(defun ecss-computed-style-provenance (style)
"Return defensive winner provenance from computed STYLE."
(ecss--computed-style-check style)
@ -1022,5 +1031,11 @@ non-nil, is the only function allowed to evaluate caller-owned value sources;
(ecss--computed-style-active-properties
(ecss--computed-style-check style))))
(defun ecss-computed-style-specified-p (style property)
"Return non-nil when PROPERTY has a cascade winner in computed STYLE."
(memq property
(ecss--computed-style-specified-properties
(ecss--computed-style-check style))))
(provide 'ecss-cascade)
;;; ecss-cascade.el ends here

View File

@ -691,6 +691,37 @@
(should (equal (ecss-computed-style-custom-properties style)
'(--theme ["blue"])))))
(ert-deftest ecss-cascade-test-specified-properties-track-declaration-winners ()
(let* ((schemas (ecss-cascade-test--schemas))
(stylesheet (ecss-stylesheet-create))
(target (ecss-cascade-test--subject :classes '("target")))
(initial (ecss-compute-style schemas target))
parent child declared returned)
(ecss-stylesheet-add-rule
stylesheet schemas ".target" '(app/color "blue"))
(setq parent
(ecss-compute-style
schemas target :declarations '(app/payload ("from-parent")))
child
(ecss-compute-style schemas target :parent-style parent)
declared
(ecss-compute-style
schemas target :stylesheet stylesheet
:declarations '(app/width 12 app/payload ("inline"))))
(should (equal (ecss-computed-style-specified-properties declared)
'(app/color app/width app/payload)))
(should (ecss-computed-style-specified-p declared 'app/color))
(should (ecss-computed-style-specified-p declared 'app/width))
(should (ecss-computed-style-specified-p declared 'app/payload))
(should-not (ecss-computed-style-specified-p initial 'app/color))
(should-not (ecss-computed-style-specified-p child 'app/payload))
(should (ecss-computed-style-present-p child 'app/payload))
(should (null (ecss-computed-style-provenance declared)))
(setq returned (ecss-computed-style-specified-properties declared))
(setcar returned :damaged)
(should (equal (ecss-computed-style-specified-properties declared)
'(app/color app/width app/payload)))))
(ert-deftest ecss-cascade-test-provenance-getter-copies-selector-data ()
(let ((schemas (ecss-cascade-test--schemas))
(stylesheet (ecss-stylesheet-create))