| <!DOCTYPE html> |
| <title>CSS Custom Functions: CSSOM</title> |
| <link rel="help" href="https://drafts.csswg.org/css-mixins-1/#cssom"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| |
| <script> |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() {} |
| `); |
| assert_equals(sheet.cssRules.length, 1); |
| let functionRule = sheet.cssRules[0]; |
| assert_true(functionRule instanceof CSSFunctionRule); |
| assert_equals(functionRule.cssRules.length, 0); |
| }, 'Empty CSSFunctionRule'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| result: 100px; |
| } |
| `); |
| assert_equals(sheet.cssRules.length, 1); |
| let functionRule = sheet.cssRules[0]; |
| assert_true(functionRule instanceof CSSFunctionRule); |
| assert_equals(functionRule.cssRules.length, 1); |
| let declarationsRule = functionRule.cssRules[0]; |
| assert_true(declarationsRule instanceof CSSFunctionDeclarations); |
| let descriptors = declarationsRule.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| }, 'Single CSSFunctionDeclarations'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| result: 100px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.result, '100px'); |
| }, 'CSSFunctionDescriptors (result)'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| result: 100px; |
| result: 101px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.result, '101px'); |
| }, 'CSSFunctionDescriptors (result, repeated)'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| --y: 2px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.getPropertyValue('--x'), '1px'); |
| assert_equals(descriptors.getPropertyValue('--y'), '2px'); |
| assert_equals(descriptors.getPropertyValue('--unknown'), ''); |
| assert_equals(descriptors.result, ''); |
| }, 'CSSFunctionDescriptors (local variables)'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| --y: 2px; |
| --x: 3px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.getPropertyValue('--x'), '3px'); |
| assert_equals(descriptors.getPropertyValue('--y'), '2px'); |
| assert_equals(descriptors.getPropertyValue('--unknown'), ''); |
| assert_equals(descriptors.result, ''); |
| }, 'CSSFunctionDescriptors (local variables, repeated)'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| --y: 2px; |
| result: 3px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.getPropertyValue('--x'), '1px'); |
| assert_equals(descriptors.getPropertyValue('--y'), '2px'); |
| assert_equals(descriptors.getPropertyValue('--unknown'), ''); |
| assert_equals(descriptors.result, '3px'); |
| }, 'CSSFunctionDescriptors (local variables and result)'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| --y: 2px; |
| result: 3px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.cssText, '--x: 1px; --y: 2px; result: 3px;'); |
| }, 'CSSFunctionDescriptors serialization'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| syntax: "<length>"; |
| --y: 2px; |
| color: red; |
| unknown: unknown; |
| result: 3px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.length, 3); |
| assert_equals(descriptors.cssText, '--x: 1px; --y: 2px; result: 3px;'); |
| }, 'Unknown descriptors'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.length, 1); |
| descriptors.cssText = '--x: 1px; color: red; result: 3px; --y: 2px;'; |
| assert_equals(descriptors.length, 3); |
| assert_equals(descriptors.cssText, '--x: 1px; result: 3px; --y: 2px;'); |
| }, 'Unknown descriptors (mutation)'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| result: 2px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.length, 2); |
| assert_equals(descriptors.item(0), '--x'); |
| assert_equals(descriptors.item(1), 'result'); |
| }, 'item()'); |
| |
| // https://webidl.spec.whatwg.org/#dfn-indexed-property-getter |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| --x: 1px; |
| result: 2px; |
| } |
| `); |
| let descriptors = sheet.cssRules[0]?.cssRules[0]?.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_array_equals(Array.from(descriptors), ['--x', 'result']);; |
| }, 'Indexed property getter'); |
| |
| // Conditional rules |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() { |
| @supports (width: 100px) { |
| result: 100px; |
| } |
| result: 200px; |
| } |
| `); |
| assert_equals(sheet.cssRules.length, 1); |
| let functionRule = sheet.cssRules[0]; |
| assert_true(functionRule instanceof CSSFunctionRule); |
| assert_equals(functionRule.cssRules.length, 2); |
| let supportsRule = functionRule.cssRules[0]; |
| assert_true(supportsRule instanceof CSSSupportsRule); |
| assert_true(supportsRule.cssRules[0] instanceof CSSFunctionDeclarations); |
| assert_equals(supportsRule.cssRules[0].style?.result, '100px'); |
| |
| let declarationsRule = functionRule.cssRules[1]; |
| assert_true(declarationsRule instanceof CSSFunctionDeclarations); |
| let descriptors = declarationsRule.style; |
| assert_true(descriptors instanceof CSSFunctionDescriptors); |
| assert_equals(descriptors.result, '200px'); |
| }, '@supports in body'); |
| |
| // CSSFunctionRule |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --foo() {} |
| `); |
| assert_equals(sheet.cssRules.length, 1); |
| let functionRule = sheet.cssRules[0]; |
| assert_true(functionRule instanceof CSSFunctionRule); |
| assert_equals(functionRule.name, '--foo'); |
| }, 'CSSFunctionRule.name'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --f0() {} |
| @function --f1(--x) {} |
| @function --f2(--x <length>) {} |
| @function --f3(--x: 10px) {} |
| @function --f4(--x <length>: 10px) {} |
| @function --f5(--x type(<length> | auto): 10px, --y, --z: red) {} |
| `); |
| assert_equals(sheet.cssRules.length, 6); |
| assert_object_equals(sheet.cssRules[0].getParameters(), []); |
| assert_object_equals(sheet.cssRules[1].getParameters(), [ |
| {name: '--x', type: '*'}, |
| ]); |
| assert_object_equals(sheet.cssRules[2].getParameters(), [ |
| {name: '--x', type: '<length>'}, |
| ]); |
| assert_object_equals(sheet.cssRules[3].getParameters(), [ |
| {name: '--x', type: '*', defaultValue: '10px'}, |
| ]); |
| assert_object_equals(sheet.cssRules[4].getParameters(), [ |
| {name: '--x', type: '<length>', defaultValue: '10px'}, |
| ]); |
| assert_object_equals(sheet.cssRules[5].getParameters(), [ |
| {name: '--x', type: '<length> | auto', defaultValue: '10px'}, |
| {name: '--y', type: '*'}, |
| {name: '--z', type: '*', defaultValue: 'red'}, |
| ], '--f5'); |
| }, 'CSSFunctionRule.getParameters()'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(` |
| @function --f0() {} |
| @function --f1() returns <length> {} |
| @function --f2() returns <length>+ {} |
| @function --f3() returns type(*) {} |
| @function --f4() returns type(<length> | auto) {} |
| `); |
| assert_equals(sheet.cssRules.length, 5); |
| assert_equals(sheet.cssRules[0]?.returnType, '*'); |
| assert_equals(sheet.cssRules[1]?.returnType, '<length>'); |
| assert_equals(sheet.cssRules[2]?.returnType, '<length>+'); |
| assert_equals(sheet.cssRules[3]?.returnType, '*'); |
| assert_equals(sheet.cssRules[4]?.returnType, '<length> | auto'); |
| }, 'CSSFunctionRule.returnType'); |
| |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| // U+0009 CHARACTER TABULATION |
| sheet.replaceSync(`@function --escaped-\\9 -tab(--param-\\9 -tab) { --local-\\9 -tab: 1px; }`); |
| assert_equals(sheet.cssRules.length, 1); |
| let rule = sheet.cssRules[0]; |
| assert_equals(rule.name, '--escaped-\t-tab'); |
| assert_equals(rule.getParameters()[0]?.name, '--param-\t-tab'); |
| assert_equals(rule.cssRules[0].style.getPropertyValue('--local-\t-tab'), '1px'); |
| }, 'CSSFunctionRule escapes'); |
| |
| function test_cssText(actual, expected) { |
| expected ??= actual; |
| let name = actual.match(/@function (--[-\w]+)/)[1]; |
| test(t => { |
| let sheet = new CSSStyleSheet(); |
| sheet.replaceSync(actual); |
| assert_equals(sheet.cssRules[0]?.cssText, expected); |
| }, `CSSFunctionRule.cssText (${name})`); |
| } |
| |
| // The function name is used by the test description; please make sure it's |
| // unique among all test_cssText cases. |
| test_cssText(`@function --empty() { }`); |
| |
| test_cssText(`@function --ret-length() returns <length> { }`); |
| test_cssText(`@function --ret-length-auto() returns type(<length> | auto) { }`); |
| |
| test_cssText(`@function --param-single(--x) { }`); |
| test_cssText(`@function --param-typed(--x <length>) { }`); |
| test_cssText(`@function --param-typed-default(--x <length>: 10px) { }`); |
| test_cssText(`@function --param-default(--x: 10px) { }`); |
| test_cssText(`@function --param-multi(--x, --y) { }`); |
| test_cssText(`@function --param-multi-mixed(--x: 10px, --y, --z <length>) { }`); |
| |
| test_cssText(`@function --body-result() { result: 10px; }`); |
| test_cssText(`@function --body-locals() { --x: 1px; --y: 2px; result: 10px; }`); |
| |
| // Cases that don't round-trip as-is: |
| test_cssText(`@function --param-type-fn(--x type(<length>)) { }`, |
| `@function --param-type-fn(--x <length>) { }`); |
| test_cssText(`@function --param-type-fn-uni(--x type(*)) { }`, |
| `@function --param-type-fn-uni(--x) { }`); |
| test_cssText(`@function --ret-type-fn() returns type(*) { }`, |
| `@function --ret-type-fn() { }`); |
| test_cssText(`@function --ret-type-fn-uni() returns type(*) { }`, |
| `@function --ret-type-fn-uni() { }`); |
| test_cssText(`@function --body-result-multi() { result: 10px; result: 20px; }`, |
| `@function --body-result-multi() { result: 20px; }`); |
| |
| // Escapes (U+0009 CHARACTER TABULATION): |
| test_cssText(`@function --escaped-\\9 -tab(--param-\\9 -tab) { --local-\\9 -tab: 1px; }`); |
| </script> |