Skip to content

Commit 986933e

Browse files
committed
Add initial support for computed/used/resolved property values
This commit adds a first pass of the implementation of the processings to get a resolved value from a CSSStyleDeclaration returned by getComputedStyle(): - collect and filter values declared for the property and element - sort declared values to output a cascaded value - default the cascaded value to output a specified value - resolve the specified value into a computed value by absolutizing component values when there is enough data, following the requirements of the "computed value" line in the property definition table - resolve the computed value into a used value for some properties It lacks support for several features: - collecting transition and animation declarations - collecting declarations from a higher (encapsulation/tree) context - sort declarations based on their context (and importance) - calculate the specificity of pseudos defined with an evaluation context - resolve the computed value of an arbitrary substitution containing value - resolve the computed value of a <whole-value> - resolve the computed/used value of other relative value types than <color> - resolve the computed/used value of other properties than color-like ones, whose "computed value" line is not "as specified" - resolve the computed/used value of a shorthand Less importantly, <system-color> and <deprecated-color> are resolved without considering the user preferred color scheme, its overriding preference, or the UA default color scheme. Several problems also need to be fixed: - at least one selector of the style rule must match the element in order to collect a declaration for the property, while calculating the specificity requires matching all selectors: a caching system would avoid matching the same selector against the same element (and context) multiple times - selector matching algorithms must not enter in a shadow tree unless it is explicitly specified for a selector, which should be defined in the specs but is not - top-level shadow tree elements must inherit values from the shadow host - "text-decoration-line" and "page" inheritance rules must be considered - evaluating @container's condition only considers <container-name> - component values are not resolved when nested in a function or simple block Most of these features and problems require a more in-depth reflection on the model, more particularly on: - how to collect and sort declared values - how to represent the context, associate it to a declaration, and pass it to the selector matching algorithms - how to resolve a computed and used value Adding more tests will help to identify all requirements.
1 parent 5ae623a commit 986933e

14 files changed

Lines changed: 2292 additions & 1030 deletions

File tree

lib/cssom/CSSStyleDeclaration-impl.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getDeclarationName, parseDeclarations, parseDestructuredDeclaration } f
55
import { isFailure, isOmitted } from '../utils/value.js'
66
import { serializeDeclarationBlock, serializeValue } from '../serialize.js'
77
import expandShorthandDeclaration from '../parse/shorthand.js'
8+
import { getResolvedValue } from '../resolve.js'
89
import logical from '../properties/logical.js'
910
import properties from '../properties/definitions.js'
1011
import shorthands from '../properties/shorthands.js'
@@ -168,6 +169,9 @@ export default class CSSStyleDeclarationImpl {
168169
*/
169170
getPropertyValue(name) {
170171
name = getDeclarationName(this.parentRule ?? this._ownerNode, name)
172+
if (this._computed) {
173+
return serializeValue({ name, value: getResolvedValue(name, this._ownerNode) })
174+
}
171175
if (shorthands.has(name)) {
172176
const value = []
173177
let important = null

lib/cssom/CSSStyleRule-impl.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11

2-
import { delimiter, list, omitted } from '../values/value.js'
32
import { isFailure, isOmitted } from '../utils/value.js'
3+
import { list, omitted } from '../values/value.js'
44
import CSSGroupingRuleImpl from './CSSGroupingRule-impl.js'
55
import CSSNestedDeclarations from './CSSNestedDeclarations.js'
66
import CSSRuleList from './CSSRuleList.js'
77
import CSSScopeRule from './CSSScopeRule.js'
88
import CSSStyleProperties from './CSSStyleProperties.js'
99
import CSSStyleRule from './CSSStyleRule.js'
10+
import { ampersand } from '../values/defaults.js'
1011
import { parseGrammar } from '../parse/parser.js'
1112
import { serializeComponentValues } from '../serialize.js'
1213

13-
const nestingSelector = delimiter('&')
14-
const compound = list([nestingSelector, list([], '')], '', ['<compound-selector>'])
14+
const compound = list([ampersand, list([], '')], '', ['<compound-selector>'])
1515
const complexUnit = list([compound, list([], '')], '', ['<complex-selector-unit>'])
1616
const complexSelector = list([complexUnit, list()], ' ', ['<complex-selector>'])
1717
const relativeSelector = list([omitted, complexSelector], ' ', ['<relative-selector>'])

lib/match/container.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
import { getComputedValue } from '../resolve.js'
3+
import { isOmitted } from '../utils/value.js'
4+
import { serializeComponentValue } from '../serialize.js'
5+
6+
/**
7+
* @param {*} query
8+
* @param {Element} element
9+
* @returns {boolean}
10+
*/
11+
function matchQuery(query, element) {
12+
return isOmitted(query)
13+
}
14+
15+
/**
16+
* @param {object} name
17+
* @param {Element} element
18+
* @returns {boolean}
19+
* @see {@link https://drafts.csswg.org/css-conditional-5/#typedef-container-name}
20+
*/
21+
function matchName(name, element) {
22+
return isOmitted(name) || name.value === serializeComponentValue(getComputedValue('container-name', element))
23+
}
24+
25+
/**
26+
* @param {*[]} conditions
27+
* @param {Element} element
28+
* @returns {boolean}
29+
* @see {@link https://drafts.csswg.org/css-conditional-5/#typedef-container-condition}
30+
*/
31+
export default function match(conditions, element) {
32+
return conditions.some(([name, query]) => matchName(name, element) && matchQuery(query, element))
33+
}

lib/match/selector.js

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
import { findAncestor, getParent } from '../utils/dom.js'
23
import { isDelimiter, isOmitted } from '../utils/value.js'
34
import { serializeComponentValue, serializeIdentifier } from '../serialize.js'
45
import { toLowerCase } from '../utils/string.js'
@@ -985,14 +986,6 @@ function getNextSiblings({ nextElementSibling }) {
985986
return elements
986987
}
987988

988-
/**
989-
* @param {Element} element
990-
* @returns {Element|undefined}
991-
*/
992-
function getParent({ parentElement, parentNode }) {
993-
return parentElement ?? parentNode?.host
994-
}
995-
996989
/**
997990
* @param {Element} element
998991
* @returns {Element[]}
@@ -1026,24 +1019,6 @@ function getRightCombinedElements(element, combinator) {
10261019
}
10271020
}
10281021

1029-
/**
1030-
* @param {Element} element
1031-
* @param {function} accept
1032-
* @param {function} [reject]
1033-
* @returns {Element|undefined}
1034-
*/
1035-
function findAncestor({ parentElement }, accept, reject) {
1036-
while (parentElement) {
1037-
if (accept(parentElement)) {
1038-
return parentElement
1039-
}
1040-
if (reject?.(parentElement)) {
1041-
return
1042-
}
1043-
parentElement = getParent(parentElement)
1044-
}
1045-
}
1046-
10471022
/**
10481023
* @param {Element} element
10491024
* @param {function} predicate
@@ -1478,6 +1453,9 @@ function matchComplexSelector(element, selector, options) {
14781453
* @see {@link https://drafts.csswg.org/selectors-4/#match-a-selector-against-an-element}
14791454
*/
14801455
function matchElementAgainstSelectors(element, selector, options) {
1456+
if (options.all) {
1457+
return selector.filter(selector => matchComplexSelector(element, selector, options))
1458+
}
14811459
return selector.some(selector => matchComplexSelector(element, selector, options))
14821460
}
14831461

lib/parse/shorthand.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
import { isFunction, isList, isOmitted } from '../utils/value.js'
2424
import { keyword, list, omitted } from '../values/value.js'
2525
import { keywords as cssWideKeywords } from '../values/substitutions.js'
26+
import { getInitialValue } from '../resolve.js'
2627
import properties from '../properties/definitions.js'
2728
import shorthands from '../properties/shorthands.js'
2829

@@ -33,7 +34,7 @@ const borderTypes = ['<line-width>', '<line-style>', '<color>']
3334
* @returns {Map}
3435
*/
3536
function getInitialLonghandDeclarations(longhands) {
36-
return new Map(longhands.map(longhand => [longhand, properties[longhand].initial.parsed]))
37+
return new Map(longhands.map(longhand => [longhand, getInitialValue(longhand)]))
3738
}
3839

3940
/**
@@ -57,7 +58,7 @@ function parseLonghandsByIndex(values, longhands) {
5758
return new Map(longhands.map((longhand, index) => {
5859
let value = values[index]
5960
if (!value || isOmitted(value)) {
60-
value = properties[longhand].initial.parsed
61+
value = getInitialValue(longhand)
6162
}
6263
return [longhand, value]
6364
}))
@@ -75,7 +76,7 @@ function parseCoordinatedValues(lists, longhands) {
7576
const value = list[index]
7677
declarations
7778
.get(longhand)
78-
.push(isOmitted(value) ? properties[longhand].initial.parsed[0] : value)
79+
.push(isOmitted(value) ? getInitialValue(longhand)[0] : value)
7980
}))
8081
return declarations
8182
}
@@ -89,7 +90,7 @@ function parseCoordinatedValues(lists, longhands) {
8990
*/
9091
function parseAnimation(animations, longhands, resetOnly) {
9192
const declarations = parseCoordinatedValues(animations, longhands)
92-
resetOnly.forEach(longhand => declarations.set(longhand, properties[longhand].initial.parsed))
93+
resetOnly.forEach(longhand => declarations.set(longhand, getInitialValue(longhand)))
9394
return declarations
9495
}
9596

@@ -135,9 +136,9 @@ function parseBackground(backgrounds, longhands, resetOnly) {
135136
const color = final.pop()
136137
const declarations = new Map(longhands.map(longhand =>
137138
longhand === 'background-color'
138-
? [longhand, isOmitted(color) ? properties[longhand].initial.parsed : color]
139+
? [longhand, isOmitted(color) ? getInitialValue(longhand) : color]
139140
: [longhand, list([], ',')]))
140-
resetOnly.forEach(longhand => declarations.set(longhand, properties[longhand].initial.parsed))
141+
resetOnly.forEach(longhand => declarations.set(longhand, getInitialValue(longhand)))
141142
layers = isOmitted(layers) ? [final] : [...layers, final]
142143
layers.forEach(([image, positionSize, repeat, attachment, origin, clip]) => {
143144
let position
@@ -190,7 +191,7 @@ function parseBackground(backgrounds, longhands, resetOnly) {
190191
value = isOmitted(clip) ? origin : clip
191192
break
192193
}
193-
declarations.get(longhand).push(isOmitted(value) ? properties[longhand].initial.parsed[0] : value)
194+
declarations.get(longhand).push(isOmitted(value) ? getInitialValue(longhand)[0] : value)
194195
})
195196
})
196197
return declarations
@@ -235,9 +236,9 @@ function parseBorder(values, longhands, sides) {
235236
const typeIndex = Math.floor((index / sides) % 3)
236237
const type = borderTypes.at(typeIndex)
237238
const value = values.find(value => value.types.includes(type))
238-
return [longhand, value ?? properties[longhand].initial.parsed]
239+
return [longhand, value ?? getInitialValue(longhand)]
239240
}
240-
return [longhand, properties[longhand].initial.parsed]
241+
return [longhand, getInitialValue(longhand)]
241242
}))
242243
}
243244

@@ -681,7 +682,7 @@ function parseMask(masks, longhands, resetOnly) {
681682
const positionIndex = longhands.indexOf('mask-position')
682683
const declarations = new Map
683684
longhands.forEach(longhand => declarations.set(longhand, list([], ',')))
684-
resetOnly.forEach(longhand => declarations.set(longhand, properties[longhand].initial.parsed))
685+
resetOnly.forEach(longhand => declarations.set(longhand, getInitialValue(longhand)))
685686
masks.forEach(layer => {
686687
const [, positionSize,, origin, clip] = layer
687688
if (isOmitted(positionSize)) {
@@ -698,7 +699,7 @@ function parseMask(masks, longhands, resetOnly) {
698699
} else if (longhand === 'mask-clip' && !isOmitted(origin)) {
699700
value = origin
700701
} else {
701-
value = properties[longhand].initial.parsed[0]
702+
value = getInitialValue(longhand)[0]
702703
}
703704
}
704705
declarations.get(longhand).push(value)

0 commit comments

Comments
 (0)