22
33const DOMException = require ( "../../../generated/idl/DOMException.js" ) ;
44const idlUtils = require ( "../../../generated/idl/utils.js" ) ;
5+ const propertyDefinitions = require ( "../../../generated/css-property-definitions" ) ;
56const propertyDescriptors = require ( "../../../generated/css-property-descriptors" ) ;
7+ const propertyMetadata = require ( "../../../generated/css-property-metadata" ) ;
8+ const { asciiLowercase } = require ( "../helpers/strings" ) ;
9+ const computedStyle = require ( "./helpers/computed-style" ) ;
10+ const cssValues = require ( "./helpers/css-values" ) ;
11+ const csstree = require ( "./helpers/patched-csstree" ) ;
612const {
713 borderProperties,
814 getPositionValue,
@@ -11,27 +17,26 @@ const {
1117 prepareProperties,
1218 shorthandProperties
1319} = require ( "./helpers/shorthand-properties" ) ;
14- const {
15- hasVarFunc, isGlobalKeyword, parsePropertyValue
16- } = require ( "./helpers/css-values" ) ;
17- const csstree = require ( "./helpers/patched-csstree" ) ;
18- const { asciiLowercase } = require ( "../helpers/strings" ) ;
20+ const { systemColors } = require ( "./helpers/system-colors" ) ;
1921
2022class CSSStyleDeclarationImpl {
2123 // https://drafts.csswg.org/cssom/#css-declaration-blocks
2224 // `_priorities` and `#values` together represent the spec's "declarations".
23- #computed ;
25+ _computed ;
2426 _readonly = false ;
2527 _priorities = new Map ( ) ;
2628 #values = new Map ( ) ;
2729 parentRule ;
2830 #ownerNode;
2931 #updating = false ;
3032
33+ // Internal private fields.
34+ #computedValueOpts = new Map ( ) ;
35+ #cachedPropertyValues = new Map ( ) ;
36+
3137 constructor ( globalObject , args , { computed, ownerNode, parentRule } = { } ) {
3238 this . _globalObject = globalObject ;
33-
34- this . #computed = Boolean ( computed ) ;
39+ this . _computed = Boolean ( computed ) ;
3540 this . parentRule = parentRule || null ;
3641 this . #ownerNode = ownerNode || null ;
3742 }
@@ -42,7 +47,7 @@ class CSSStyleDeclarationImpl {
4247 * @returns {string } The serialized CSS text.
4348 */
4449 get cssText ( ) {
45- if ( this . #computed ) {
50+ if ( this . _computed ) {
4651 return "" ;
4752 }
4853 const properties = new Map ( ) ;
@@ -111,7 +116,7 @@ class CSSStyleDeclarationImpl {
111116 if ( typeof property === "string" && typeof value === "string" ) {
112117 const priority = important ? "important" : "" ;
113118 const isCustomProperty = property . startsWith ( "--" ) ;
114- if ( isCustomProperty || hasVarFunc ( value ) ) {
119+ if ( isCustomProperty || cssValues . hasVarFunc ( value ) ) {
115120 if ( properties . has ( property ) ) {
116121 const { priority : itemPriority } = properties . get ( property ) ;
117122 if ( ! itemPriority ) {
@@ -121,7 +126,7 @@ class CSSStyleDeclarationImpl {
121126 properties . set ( property , { property, value, priority } ) ;
122127 }
123128 } else {
124- const parsedValue = parsePropertyValue ( property , value ) ;
129+ const parsedValue = cssValues . parsePropertyValue ( property , value ) ;
125130 if ( parsedValue ) {
126131 if ( properties . has ( property ) ) {
127132 const { priority : itemPriority } = properties . get ( property ) ;
@@ -174,10 +179,25 @@ class CSSStyleDeclarationImpl {
174179 * @returns {string } The property value, or empty string if not set.
175180 */
176181 getPropertyValue ( property ) {
177- if ( this . #values. has ( property ) ) {
178- return this . #values. get ( property ) . toString ( ) ;
182+ const value = this . #values. get ( property ) ?? "" ;
183+ if ( this . _computed ) {
184+ if ( this . #cachedPropertyValues. has ( property ) ) {
185+ const cachedValue = this . #cachedPropertyValues. get ( property ) ;
186+ // Return the cached resolved value if the specified value haven't changed.
187+ if ( value === cachedValue . value ) {
188+ return cachedValue . resolvedValue ;
189+ }
190+ }
191+ const resolvedValue = this . #getComputedValue( property , value ) ;
192+ if ( propertyDefinitions . has ( property ) ) {
193+ const { longhands } = propertyDefinitions . get ( property ) ;
194+ if ( ! longhands ) {
195+ this . #cachedPropertyValues. set ( property , { resolvedValue, value } ) ;
196+ }
197+ }
198+ return resolvedValue ;
179199 }
180- return "" ;
200+ return value ;
181201 }
182202
183203 /**
@@ -273,7 +293,7 @@ class CSSStyleDeclarationImpl {
273293
274294 // https://drafts.csswg.org/cssom/#update-style-attribute-for
275295 #updateStyleAttribute( ) {
276- if ( this . #computed || ! this . #ownerNode || this . #ownerNode. _settingCssText ) {
296+ if ( this . _computed || ! this . #ownerNode || this . #ownerNode. _settingCssText ) {
277297 return ;
278298 }
279299 this . #ownerNode. _settingCssText = true ;
@@ -308,6 +328,219 @@ class CSSStyleDeclarationImpl {
308328 }
309329 }
310330
331+ #getComputedValue( property , value ) {
332+ // Invalid or unsupported property.
333+ if ( ! propertyDefinitions . has ( property ) && ! property . startsWith ( "--" ) ) {
334+ return "" ;
335+ }
336+
337+ const { inherited, initial = "" , longhands } = cssValues . getPropertyDefinition ( property ) ;
338+ const { caseSensitive, functionTypes = { } } = this . #getPropertyMetadata( property ) ;
339+ const isColor = Boolean ( functionTypes . color || functionTypes . paint ) ;
340+
341+ if ( ! value || cssValues . isGlobalKeyword ( value ) ) {
342+ value = computedStyle . replaceEmptyValueAndKeywords (
343+ property ,
344+ value ,
345+ this . #ownerNode,
346+ { inherit : inherited === "yes" , initial, isColor, longhands }
347+ ) ;
348+ }
349+
350+ if ( property === "color" && / c u r r e n t c o l o r / i. test ( value ) ) {
351+ value = computedStyle . getInheritedPropertyValue (
352+ property ,
353+ this . #ownerNode,
354+ { inherit : true , initial, isColor }
355+ ) ;
356+ }
357+
358+ if ( cssValues . hasVarFunc ( value ) ) {
359+ // TODO: Resolve css var().
360+ }
361+
362+ if ( longhands ) {
363+ if ( isColor ) {
364+ value = asciiLowercase ( value ) ;
365+ if ( systemColors . has ( value ) ) {
366+ return value ;
367+ }
368+ }
369+ return this . #resolveShorthand( property , value ) ;
370+ }
371+
372+ return this . #resolveLonghand( property , value , { caseSensitive, isColor } ) ;
373+ }
374+
375+ #getPropertyMetadata( property ) {
376+ if ( propertyMetadata . has ( property ) ) {
377+ return propertyMetadata . get ( property ) ;
378+ }
379+
380+ const value = this . #values. get ( property ) ?? "" ;
381+ // TODO: Also check if all or part of the value is quoted.
382+ const caseSensitive = ( cssValues . hasVarFunc ( value ) || value . startsWith ( "--" ) ) ? true : undefined ;
383+
384+ return { caseSensitive } ;
385+ }
386+
387+ #resolveShorthand( property , value ) {
388+ // TODO: resolve other shorthands e.g. background, flex etc.
389+ switch ( property ) {
390+ case "margin" :
391+ case "padding" : {
392+ return this . #resolvePositionShorthand( property ) ;
393+ }
394+ default : {
395+ if ( property . startsWith ( "border" ) ) {
396+ return this . #resolveBorderShorthands( property ) ;
397+ }
398+ return value ;
399+ }
400+ }
401+ }
402+
403+ #resolvePositionShorthand( property ) {
404+ const shorthandItem = shorthandProperties . get ( property ) ;
405+ if ( ! shorthandItem || ! shorthandItem . shorthandFor ) {
406+ return "" ;
407+ }
408+ const longhandValues = [ ] ;
409+ for ( const [ longhandProperty ] of shorthandItem . shorthandFor ) {
410+ longhandValues . push ( this . getPropertyValue ( longhandProperty ) ) ;
411+ }
412+ return getPositionValue ( longhandValues ) ;
413+ }
414+
415+ #resolveLonghand( property , value , { caseSensitive, isColor } ) {
416+ const options = this . #prepareComputedValueOpts( ) ;
417+ const parsedValue = cssValues . parsePropertyValue ( property , value , {
418+ caseSensitive,
419+ ...options
420+ } ) ;
421+
422+ if ( isColor ) {
423+ const resolvedValue = cssValues . serializeColor ( parsedValue , options ) ;
424+ if ( resolvedValue ) {
425+ return resolvedValue ;
426+ }
427+ }
428+
429+ // TODO: Resolve special cases other than color.
430+
431+ return value ;
432+ }
433+
434+ #resolveBorderShorthands( property ) {
435+ switch ( property ) {
436+ case "border" : {
437+ const values = [ ] ;
438+ for ( const item of [ "top" , "right" , "bottom" , "left" ] ) {
439+ const value = this . getPropertyValue ( `border-${ item } ` ) ;
440+ if ( ! value ) {
441+ return "" ;
442+ }
443+ values . push ( value ) ;
444+ }
445+ const [ top , right , bottom , left ] = values ;
446+ if ( top === right && top === bottom && top === left ) {
447+ return top ;
448+ }
449+ return "" ;
450+ }
451+ case "border-top" :
452+ case "border-right" :
453+ case "border-bottom" :
454+ case "border-left" : {
455+ const values = [ ] ;
456+ for ( const item of [ "width" , "style" , "color" ] ) {
457+ const value = this . getPropertyValue ( `${ property } -${ item } ` ) ;
458+ if ( ! value ) {
459+ return "" ;
460+ }
461+ values . push ( value ) ;
462+ }
463+ return values . join ( " " ) ;
464+ }
465+ // border-width, border-style, border-color
466+ default : {
467+ return this . #resolvePositionShorthand( property ) ;
468+ }
469+ }
470+ }
471+
472+ // Options are used when resolving relative values or specified values.
473+ #prepareComputedValueOpts( ) {
474+ if ( ! this . #computedValueOpts. has ( "options" ) ) {
475+ this . #computedValueOpts. set ( "options" , { format : "computedValue" } ) ;
476+ }
477+ const options = this . #computedValueOpts. get ( "options" ) ;
478+
479+ // Return the cached options if the specified raw values haven't changed.
480+ const rawColorScheme = this . #values. get ( "color-scheme" ) ?? "" ;
481+ const rawColor = this . #values. get ( "color" ) ?? "" ;
482+ if (
483+ this . #computedValueOpts. get ( "rawColorScheme" ) === rawColorScheme &&
484+ this . #computedValueOpts. get ( "rawColor" ) === rawColor
485+ ) {
486+ return options ;
487+ }
488+ // Store current raw values for future cache validation.
489+ this . #computedValueOpts. set ( "rawColorScheme" , rawColorScheme ) ;
490+ this . #computedValueOpts. set ( "rawColor" , rawColor ) ;
491+
492+ // Prepare color-scheme.
493+ const colorScheme = computedStyle . replaceEmptyValueAndKeywords (
494+ "color-scheme" ,
495+ rawColorScheme ,
496+ this . #ownerNode,
497+ { inherit : true , initial : "normal" }
498+ ) ;
499+ this . #cachedPropertyValues. set ( "color-scheme" , {
500+ resolvedValue : colorScheme ,
501+ value : rawColorScheme
502+ } ) ;
503+ options . colorScheme = colorScheme ;
504+
505+ // Prepare current color.
506+ let currentColor = computedStyle . replaceEmptyValueAndKeywords (
507+ "color" ,
508+ rawColor ,
509+ this . #ownerNode,
510+ { inherit : true , initial : "canvastext" }
511+ ) ;
512+ currentColor = asciiLowercase ( currentColor ) ;
513+ // Replace currentcolor keyword.
514+ if ( currentColor === "currentcolor" ) {
515+ currentColor = computedStyle . getInheritedPropertyValue (
516+ "color" ,
517+ this . #ownerNode,
518+ { inherit : true , initial : "canvastext" , isColor : true }
519+ ) ;
520+ }
521+ // Resolve system colors.
522+ if ( systemColors . has ( currentColor ) ) {
523+ currentColor = cssValues . resolveSystemColorValue ( currentColor , colorScheme ) ;
524+ } else {
525+ // Resolve named colors.
526+ if ( / ^ [ a - z ] + $ / . test ( currentColor ) ) {
527+ currentColor = cssValues . resolveColor ( currentColor , { format : "computedValue" } ) ;
528+ }
529+ this . #cachedPropertyValues. set ( "color" , {
530+ resolvedValue : currentColor ,
531+ value : rawColor
532+ } ) ;
533+ }
534+ options . currentColor = currentColor ;
535+
536+ // TODO: Add customProperty, dimension etc.
537+
538+ // Store options.
539+ this . #computedValueOpts. set ( "options" , options ) ;
540+
541+ return options ;
542+ }
543+
311544 /**
312545 * Helper to handle border property expansion.
313546 *
@@ -322,7 +555,7 @@ class CSSStyleDeclarationImpl {
322555 priority = this . _priorities . get ( property ) ?? "" ;
323556 }
324557 if ( property === "border" ) {
325- properties . set ( property , { propery : property , value, priority } ) ;
558+ properties . set ( property , { property, value, priority } ) ;
326559 } else {
327560 for ( const itemProperty of this . #values. keys ( ) ) {
328561 if ( borderProperties . has ( itemProperty ) ) {
@@ -371,13 +604,13 @@ class CSSStyleDeclarationImpl {
371604 } else {
372605 this . _setProperty ( property , value , priority ) ;
373606 }
374- if ( value && ! hasVarFunc ( value ) ) {
607+ if ( value && ! cssValues . hasVarFunc ( value ) ) {
375608 const longhandValues = [ ] ;
376609 const shorthandItem = shorthandProperties . get ( shorthandProperty ) ;
377610 let hasGlobalKeyword = false ;
378611 for ( const [ longhandProperty ] of shorthandItem . shorthandFor ) {
379612 if ( longhandProperty === property ) {
380- if ( isGlobalKeyword ( value ) ) {
613+ if ( cssValues . isGlobalKeyword ( value ) ) {
381614 hasGlobalKeyword = true ;
382615 }
383616 longhandValues . push ( value ) ;
@@ -387,7 +620,7 @@ class CSSStyleDeclarationImpl {
387620 if ( ! longhandValue || longhandPriority !== priority ) {
388621 break ;
389622 }
390- if ( isGlobalKeyword ( longhandValue ) ) {
623+ if ( cssValues . isGlobalKeyword ( longhandValue ) ) {
391624 hasGlobalKeyword = true ;
392625 }
393626 longhandValues . push ( longhandValue ) ;
@@ -479,7 +712,7 @@ class CSSStyleDeclarationImpl {
479712 } else {
480713 this . _setProperty ( property , value , priority ) ;
481714 }
482- if ( value && ! hasVarFunc ( value ) ) {
715+ if ( value && ! cssValues . hasVarFunc ( value ) ) {
483716 const longhandValues = [ ] ;
484717 const { shorthandFor, position : shorthandPosition } = shorthandProperties . get ( shorthandProperty ) ;
485718 for ( const [ longhandProperty ] of shorthandFor ) {
0 commit comments