| <!DOCTYPE html> | |
| <link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <script src="./resources/utils.js"></script> | |
| <div id="outer"> | |
| <div id="target"></div> | |
| </div> | |
| <script> | |
| // Parsing: | |
| let uppercase_first = (x) => x.charAt(0).toUpperCase() + x.slice(1); | |
| let to_camel_case = (x) => x.split('-')[0] + x.split('-').slice(1).map(uppercase_first).join(''); | |
| function get_cssom_descriptor_value(rule, descriptor) { | |
| switch (descriptor) { | |
| case 'syntax': | |
| return rule.syntax; | |
| case 'inherits': | |
| return rule.inherits; | |
| case 'initial-value': | |
| return rule.initialValue; | |
| default: | |
| assert_true(false, 'Should not reach here'); | |
| return null; | |
| } | |
| } | |
| // Test that for the given descriptor (e.g. 'syntax'), the specified value | |
| // will yield the expected_value when observed using CSSOM. If the expected_value | |
| // is omitted, it is the same as the specified value. | |
| function test_descriptor(descriptor, specified_value, expected_value) { | |
| let camel = to_camel_case(descriptor); | |
| if (typeof(expected_value) === 'undefined') | |
| expected_value = specified_value; | |
| test_with_at_property({ [camel]: specified_value }, (name, rule) => { | |
| assert_equals(get_cssom_descriptor_value(rule, descriptor), expected_value); | |
| }, `Attribute '${descriptor}' returns expected value for [${specified_value}]`); | |
| } | |
| // syntax | |
| test_descriptor('syntax', '"<color>"', '<color>'); | |
| test_descriptor('syntax', '"<color> | none"', '<color> | none'); | |
| test_descriptor('syntax', '"<color># | <image> | none"', '<color># | <image> | none'); | |
| test_descriptor('syntax', '"foo | bar | baz"', 'foo | bar | baz'); | |
| test_descriptor('syntax', '"*"', '*'); | |
| test_descriptor('syntax', '"notasyntax"', 'notasyntax'); | |
| test_descriptor('syntax', 'red', ''); | |
| test_descriptor('syntax', 'rgb(255, 0, 0)', ''); | |
| test_descriptor('syntax', '<color>', ''); | |
| test_descriptor('syntax', 'foo | bar', ''); | |
| // initial-value | |
| test_descriptor('initial-value', '10px'); | |
| test_descriptor('initial-value', 'rgb(1, 2, 3)'); | |
| test_descriptor('initial-value', 'red'); | |
| test_descriptor('initial-value', 'foo'); | |
| test_descriptor('initial-value', 'if(){}'); | |
| test_descriptor('initial-value', 'var(--x)'); | |
| // inherits | |
| test_descriptor('inherits', 'true', true); | |
| test_descriptor('inherits', 'false', false); | |
| test_descriptor('inherits', 'none', false); | |
| test_descriptor('inherits', '0', false); | |
| test_descriptor('inherits', '1', false); | |
| test_descriptor('inherits', '"true"', false); | |
| test_descriptor('inherits', '"false"', false); | |
| test_descriptor('inherits', 'calc(0)', false); | |
| test_with_style_node('@property foo { }', (node) => { | |
| assert_equals(node.sheet.rules.length, 0); | |
| }, 'Invalid property name does not parse [foo]'); | |
| test_with_style_node('@property -foo { }', (node) => { | |
| assert_equals(node.sheet.rules.length, 0); | |
| }, 'Invalid property name does not parse [-foo]'); | |
| // Applying @property rules | |
| function test_applied(syntax, initial, inherits, expected) { | |
| test_with_at_property({ | |
| syntax: `"${syntax}"`, | |
| initialValue: initial, | |
| inherits: inherits | |
| }, (name, rule) => { | |
| let actual = getComputedStyle(target).getPropertyValue(name); | |
| assert_equals(actual, expected); | |
| }, `Rule applied [${syntax}, ${initial}, ${inherits}]`); | |
| } | |
| function test_not_applied(syntax, initial, inherits) { | |
| test_with_at_property({ | |
| syntax: `"${syntax}"`, | |
| initialValue: initial, | |
| inherits: inherits | |
| }, (name, rule) => { | |
| let actual = getComputedStyle(target).getPropertyValue(name); | |
| assert_equals(actual, ''); | |
| }, `Rule not applied [${syntax}, ${initial}, ${inherits}]`); | |
| } | |
| // syntax, initialValue, inherits, expected | |
| test_applied('*', 'if(){}', false, 'if(){}'); | |
| test_applied('<angle>', '42deg', false, '42deg'); | |
| test_applied('<angle>', '1turn', false, '360deg'); | |
| test_applied('<color>', 'green', false, 'rgb(0, 128, 0)'); | |
| test_applied('<color>', 'rgb(1, 2, 3)', false, 'rgb(1, 2, 3)'); | |
| test_applied('<image>', 'url("http://a/")', false, 'url("http://a/")'); | |
| test_applied('<integer>', '5', false, '5'); | |
| test_applied('<length-percentage>', '10px', false, '10px'); | |
| test_applied('<length-percentage>', '10%', false, '10%'); | |
| test_applied('<length-percentage>', 'calc(10% + 10px)', false, 'calc(10% + 10px)'); | |
| test_applied('<length>', '10px', false, '10px'); | |
| test_applied('<number>', '2.5', false, '2.5'); | |
| test_applied('<percentage>', '10%', false, '10%'); | |
| test_applied('<resolution>', '50dppx', false, '50dppx'); | |
| test_applied('<resolution>', '96dpi', false, '1dppx'); | |
| test_applied('<time>', '10s', false, '10s'); | |
| test_applied('<time>', '1000ms', false, '1s'); | |
| test_applied('<transform-function>', 'rotateX(0deg)', false, 'rotateX(0deg)'); | |
| test_applied('<transform-list>', 'rotateX(0deg)', false, 'rotateX(0deg)'); | |
| test_applied('<transform-list>', 'rotateX(0deg) translateX(10px)', false, 'rotateX(0deg) translateX(10px)'); | |
| test_applied('<url>', 'url("http://a/")', false, 'url("http://a/")'); | |
| // inherits: true/false | |
| test_applied('<color>', 'tomato', false, 'rgb(255, 99, 71)'); | |
| test_applied('<color>', 'tomato', true, 'rgb(255, 99, 71)'); | |
| test_with_at_property({ syntax: '"*"', inherits: true }, (name, rule) => { | |
| try { | |
| outer.style.setProperty(name, 'foo'); | |
| let actual = getComputedStyle(target).getPropertyValue(name); | |
| assert_equals(actual, 'foo'); | |
| } finally { | |
| outer.style = ''; | |
| } | |
| }, 'Rule applied for "*", even with no initial value'); | |
| test_not_applied(undefined, 'green', false); | |
| test_not_applied('<color>', undefined, false); | |
| test_not_applied('<color>', 'green', undefined); | |
| test_not_applied('<gandalf>', 'grey', false); | |
| test_not_applied('gandalf', 'grey', false); | |
| test_not_applied('<color>', 'notacolor', false); | |
| test_not_applied('<length>', '10em', false); | |
| // Inheritance | |
| test_with_at_property({ | |
| syntax: '"<length>"', | |
| inherits: false, | |
| initialValue: '0px' | |
| }, (name, rule) => { | |
| try { | |
| outer.style = `${name}: 40px`; | |
| assert_equals(getComputedStyle(outer).getPropertyValue(name), '40px'); | |
| assert_equals(getComputedStyle(target).getPropertyValue(name), '0px'); | |
| } finally { | |
| outer.style = ''; | |
| } | |
| }, 'Non-inherited properties do not inherit'); | |
| test_with_at_property({ | |
| syntax: '"<length>"', | |
| inherits: true, | |
| initialValue: '0px' | |
| }, (name, rule) => { | |
| try { | |
| outer.style = `${name}: 40px`; | |
| assert_equals(getComputedStyle(outer).getPropertyValue(name), '40px'); | |
| assert_equals(getComputedStyle(target).getPropertyValue(name), '40px'); | |
| } finally { | |
| outer.style = ''; | |
| } | |
| }, 'Inherited properties inherit'); | |
| // Initial values | |
| test_with_at_property({ | |
| syntax: '"<color>"', | |
| inherits: true, | |
| initialValue: 'green' | |
| }, (name, rule) => { | |
| try { | |
| target.style = `--x:var(${name})`; | |
| assert_equals(getComputedStyle(target).getPropertyValue(name), 'rgb(0, 128, 0)'); | |
| } finally { | |
| target.style = ''; | |
| } | |
| }, 'Initial values substituted as computed value'); | |
| test_with_at_property({ | |
| syntax: '"<length>"', | |
| inherits: false, | |
| initialValue: undefined | |
| }, (name, rule) => { | |
| try { | |
| target.style = `${name}: calc(1px + 1px);`; | |
| assert_equals(getComputedStyle(target).getPropertyValue(name), 'calc(1px + 1px)'); | |
| } finally { | |
| target.style = ''; | |
| } | |
| }, 'Non-universal registration are invalid without an initial value'); | |
| test_with_at_property({ | |
| syntax: '"*"', | |
| inherits: false, | |
| initialValue: undefined | |
| }, (name, rule) => { | |
| try { | |
| // If the registration suceeded, ${name} does *not* inherit, and hence | |
| // the computed value on 'target' should be empty. | |
| outer.style = `${name}: calc(1px + 1px);`; | |
| assert_equals(getComputedStyle(target).getPropertyValue(name), ''); | |
| } finally { | |
| outer.style = ''; | |
| } | |
| }, 'Initial value may be omitted for universal registration'); | |
| </script> |