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-values STYLE)` returns a defensive values plist.
- `(ecss-computed-style-custom-properties STYLE)` returns a deterministic custom-property 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-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-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-diagnostics STYLE)` returns deterministic diagnostics such as custom-property cycles.
- `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` reads one value. - `(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-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. 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-values STYLE)` 返回 defensive values plist。
- `(ecss-computed-style-custom-properties STYLE)` 返回 deterministic custom-property 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-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-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-diagnostics STYLE)` 返回 custom-property cycle 等 deterministic diagnostics。
- `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` 读取单值。 - `(ecss-computed-style-value STYLE PROPERTY &optional FALLBACK)` 读取单值。
- `(ecss-computed-style-present-p STYLE PROPERTY)` 判断 property 是否 active即使值为 nil。 - `(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。 所有 aggregate getter 都返回 defensive copy。调用者修改返回值不会改变 computed style、schema set 或 stylesheet。

View File

@ -4,7 +4,7 @@
## Position ## 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 ```text
consumer schemas + subject adapter + stylesheet + parent style 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 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. `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 ```text
consumer schemas + subject adapter + stylesheet + parent style 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 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。 `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 (cl-defstruct (ecss-computed-style
(:constructor ecss--make-computed-style) (:constructor ecss--make-computed-style)
(:conc-name ecss--computed-style-)) (:conc-name ecss--computed-style-))
"Computed declarations, custom properties, provenance, and diagnostics." "Computed declarations, custom properties, metadata, and diagnostics."
values custom-properties active-properties provenance diagnostics) values custom-properties active-properties specified-properties
provenance diagnostics)
(cl-defstruct (ecss--wide (:constructor ecss--make-wide)) kind) (cl-defstruct (ecss--wide (:constructor ecss--make-wide)) kind)
(cl-defstruct (ecss--important (:constructor ecss--make-important)) value) (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." SPECIFICITY, DISTANCE, and DECLARATION-ORDER are the match precedence facts."
(pcase-let ((`(,raw . ,important) (ecss--unwrap-important value))) (pcase-let ((`(,raw . ,important) (ecss--unwrap-important value)))
(ecss--make-candidate (ecss--make-candidate
:property property :value (ecss--copy-boundary-data raw) :property property :value raw
:origin (ecss-rule-origin rule) :origin (ecss-rule-origin rule)
:important important :inline nil :layer (ecss-rule-layer rule) :important important :inline nil :layer (ecss-rule-layer rule)
:layer-rank (ecss-rule-layer-rank rule) :specificity specificity :layer-rank (ecss-rule-layer-rank rule) :specificity specificity
:scope-distance distance :source-order (ecss-rule-source-order rule) :scope-distance distance :source-order (ecss-rule-source-order rule)
:declaration-order declaration-order :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) (defun ecss--rule-candidates (rule subject adapter)
"Return candidates from matching RULE for SUBJECT through 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 collect
(pcase-let ((`(,raw . ,important) (ecss--unwrap-important value))) (pcase-let ((`(,raw . ,important) (ecss--unwrap-important value)))
(ecss--make-candidate (ecss--make-candidate
:property property :value (ecss--copy-boundary-data raw) :property property :value raw
:origin 'author :origin 'author
:important important :inline t :layer nil :layer-rank nil :important important :inline t :layer nil :layer-rank nil
:specificity '(0 0 0) :specificity '(0 0 0)
@ -601,10 +602,11 @@ SCHEMAS validates inline declarations and ADAPTER exposes SUBJECT."
(values (ecss--parent-values parent-style))) (values (ecss--parent-values parent-style)))
(if (and (ecss-property-schema-inherits schema) (if (and (ecss-property-schema-inherits schema)
(plist-member values property)) (plist-member values property))
(cons (ecss--copy-boundary-data (plist-get values property)) 'inherit) (cons (if (ecss-computed-style-p parent-style)
(cons (ecss--copy-boundary-data (plist-get values property)
(ecss-property-schema-initial schema)) (ecss--copy-boundary-data (plist-get values property)))
'initial)))) 'inherit)
(cons (ecss-property-schema-initial schema) 'initial))))
(defun ecss--wide-default (wide schema parent-style) (defun ecss--wide-default (wide schema parent-style)
"Resolve non-revert WIDE value for SCHEMA using 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)) (let ((values (ecss--parent-values parent-style))
(property (ecss-property-schema-id schema))) (property (ecss-property-schema-id schema)))
(if (plist-member values property) (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) 'inherit)
(cons (ecss--copy-boundary-data (cons (ecss-property-schema-initial schema) 'initial))))
(ecss-property-schema-initial schema))
'initial))))
('unset (if (ecss-property-schema-inherits schema) ('unset (if (ecss-property-schema-inherits schema)
(ecss--wide-default (ecss-wide-value 'inherit) (ecss--wide-default (ecss-wide-value 'inherit)
schema parent-style) schema parent-style)
(cons (ecss--copy-boundary-data (cons (ecss-property-schema-initial schema) 'initial)))))
(ecss-property-schema-initial schema))
'initial)))))
(defun ecss--candidate-provenance (candidate &rest extra) (defun ecss--candidate-provenance (candidate &rest extra)
"Return public provenance for CANDIDATE followed by EXTRA facts." "Return public provenance for CANDIDATE followed by EXTRA facts."
@ -800,14 +800,14 @@ STACK detects cycles and DIAGNOSTICS records them."
(cons raw resolved))) (cons raw resolved)))
(defun ecss--resolve-property-value (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." "Resolve VALUE for SCHEMA using PARENT-STYLE, CUSTOM, and DIAGNOSTICS."
(cond ((and (ecss--wide-p value) (cond ((and (ecss--wide-p value)
(memq (ecss--wide-kind value) '(initial inherit unset))) (memq (ecss--wide-kind value) '(initial inherit unset)))
(ecss--wide-default value schema parent-style)) (ecss--wide-default value schema parent-style))
(t (t
(pcase-let ((`(,raw . ,resolved) (pcase-let ((`(,raw . ,resolved)
(ecss--custom-value-tables custom))) custom-tables))
(cons (ecss--resolve-variable-value (cons (ecss--resolve-variable-value
value raw resolved nil diagnostics) value raw resolved nil diagnostics)
'declaration))))) 'declaration)))))
@ -824,16 +824,13 @@ STACK detects cycles and DIAGNOSTICS records them."
ecss--invalid)))) ecss--invalid))))
(defun ecss--compute-candidate-value (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. "Resolve CANDIDATE for SCHEMA and computation context.
PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle SOURCE is the already resolved candidate value. PARENT-STYLE and CUSTOM
explicit sources, and DIAGNOSTICS records variable failures." supply inherited values, and DIAGNOSTICS records variable failures."
(let* ((property (ecss-property-schema-id schema)) (let* ((property (ecss-property-schema-id schema))
(source (ecss--resolve-source
(ecss--candidate-value candidate)
property subject resolver))
(resolved (ecss--resolve-property-value (resolved (ecss--resolve-property-value
source schema parent-style custom diagnostics))) source schema parent-style custom-tables diagnostics)))
(cons (if (eq (cdr resolved) 'declaration) (cons (if (eq (cdr resolved) 'declaration)
(ecss--normalize-property-value schema (car resolved)) (ecss--normalize-property-value schema (car resolved))
(ecss--copy-boundary-data (car resolved))) (ecss--copy-boundary-data (car resolved)))
@ -846,7 +843,7 @@ explicit sources, and DIAGNOSTICS records variable failures."
(cons value source))) (cons value source)))
(defun ecss--resolve-property-candidates (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. "Resolve SCHEMA from ordered CANDIDATES.
PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle PARENT-STYLE and CUSTOM supply inherited values. SUBJECT and RESOLVER handle
explicit sources, and DIAGNOSTICS records variable failures." explicit sources, and DIAGNOSTICS records variable failures."
@ -867,8 +864,8 @@ explicit sources, and DIAGNOSTICS records variable failures."
(setq winner candidate (setq winner candidate
resolved resolved
(ecss--compute-candidate-value (ecss--compute-candidate-value
candidate schema parent-style custom subject candidate schema raw parent-style custom-tables
(lambda (_value _property _subject) raw) diagnostics)))))) diagnostics))))))
(let* ((invalid (and winner (eq (car resolved) ecss--invalid))) (let* ((invalid (and winner (eq (car resolved) ecss--invalid)))
(final (if (or (null winner) invalid) (final (if (or (null winner) invalid)
(ecss--fallback-value schema parent-style) (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)))) (list :source source :origin (if (eq source 'initial) 'ua 'inherit))))
(defun ecss--compute-property-values (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. "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." 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)) (dolist (property (ecss--schema-set-order schemas))
(let ((schema (ecss--schema-for schemas property))) (let ((schema (ecss--schema-for schemas property)))
(unless (ecss-property-schema-shorthand schema) (unless (ecss-property-schema-shorthand schema)
(pcase-let ((`(,value ,winner ,invalid ,source) (pcase-let ((`(,value ,winner ,invalid ,source)
(ecss--resolve-property-candidates (ecss--resolve-property-candidates
schema (gethash property table) parent-style custom schema (gethash property table) parent-style
custom-tables
subject resolver diagnostics))) subject resolver diagnostics)))
(setq values (plist-put values property value)) (setq values (plist-put values property value))
(when (or (and winner (not invalid)) (when (or (and winner (not invalid))
(and (ecss-property-schema-inherits schema) (and (ecss-property-schema-inherits schema)
(ecss--parent-property-active-p parent-style property))) (ecss--parent-property-active-p parent-style property)))
(push property active)) (push property active))
(when winner
(push property specified))
(when provenance-p (when provenance-p
(setq provenance (setq provenance
(plist-put provenance property (plist-put provenance property
(ecss--property-provenance (ecss--property-provenance
winner invalid source)))))))) winner invalid source))))))))
(list values (nreverse active) provenance))) (list values (nreverse active) (nreverse specified) provenance)))
(defun ecss--validate-rule-declarations (schemas rule) (defun ecss--validate-rule-declarations (schemas rule)
"Validate canonical declarations in RULE against SCHEMAS." "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)) table parent-style subject value-resolver provenance))
(custom (ecss--resolved-custom-properties (custom (ecss--resolved-custom-properties
(car raw-facts) diagnostics))) (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 (ecss--compute-property-values
schemas table parent-style custom subject value-resolver schemas table parent-style custom-tables subject value-resolver
diagnostics provenance))) diagnostics provenance)))
(ecss--make-computed-style (ecss--make-computed-style
:values values :custom-properties custom :active-properties active :values values :custom-properties custom :active-properties active
:provenance (and provenance :specified-properties specified
(append (cadr raw-facts) property-facts)) :provenance (and provenance
:diagnostics (nreverse (car diagnostics))))))) (append (cadr raw-facts) property-facts))
:diagnostics (nreverse (car diagnostics))))))))
(defun ecss--computed-style-check (style) (defun ecss--computed-style-check (style)
"Return STYLE or signal when it is not a computed 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--copy-boundary-data
(ecss--computed-style-active-properties style))) (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) (defun ecss-computed-style-provenance (style)
"Return defensive winner provenance from computed STYLE." "Return defensive winner provenance from computed STYLE."
(ecss--computed-style-check 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-active-properties
(ecss--computed-style-check style)))) (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) (provide 'ecss-cascade)
;;; ecss-cascade.el ends here ;;; ecss-cascade.el ends here

View File

@ -691,6 +691,37 @@
(should (equal (ecss-computed-style-custom-properties style) (should (equal (ecss-computed-style-custom-properties style)
'(--theme ["blue"]))))) '(--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 () (ert-deftest ecss-cascade-test-provenance-getter-copies-selector-data ()
(let ((schemas (ecss-cascade-test--schemas)) (let ((schemas (ecss-cascade-test--schemas))
(stylesheet (ecss-stylesheet-create)) (stylesheet (ecss-stylesheet-create))