| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>var() name argument is an arbitrary substitution value</title> |
| <link rel="help" href="https://drafts.csswg.org/css-variables-2/#replace-a-var-function"> |
| <link rel="help" href="https://drafts.csswg.org/css-values-5/#component-function-commas"> |
| <link rel="author" title="Apple Inc." href="https://apple.com"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <style> |
| #target { --other: 10px; } |
| @property --registered-length { |
| syntax: "<length>"; |
| inherits: false; |
| initial-value: 7px; |
| } |
| @property --registered-universal { |
| syntax: "*"; |
| inherits: false; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="target" data-name="--other" data-not-a-name="10px"></div> |
| <script> |
| "use strict"; |
| |
| const target = document.getElementById("target"); |
| |
| function computed(declaration, property) { |
| target.setAttribute("style", declaration); |
| const value = getComputedStyle(target).getPropertyValue(property || "width"); |
| target.removeAttribute("style"); |
| return value; |
| } |
| |
| const initialWidth = computed("width: initial;"); |
| |
| // The inner var() resolves to `--other`, which becomes the name looked up by the outer var(). |
| // https://drafts.csswg.org/css-variables-2/#replace-a-var-function example. |
| test(() => { |
| assert_equals(computed("--myvar: --other; width: var(var(--myvar));"), "10px"); |
| }, "var() name comes from another var()"); |
| |
| test(() => { |
| assert_equals(computed("--myvar: --other; --indirect: --myvar; width: var(var(var(--indirect)));"), "10px"); |
| }, "var() name comes from a chain of var()s"); |
| |
| // A substituted name that does not parse as a <custom-property-name> makes the reference |
| // guaranteed-invalid, so the fallback is used. |
| test(() => { |
| assert_equals(computed("--myvar: not-a-custom-prop; width: var(var(--myvar), 20px);"), "20px"); |
| }, "invalid substituted name falls back"); |
| |
| // The name-providing var() is itself unset, so the name is guaranteed-invalid: fallback is used. |
| test(() => { |
| assert_equals(computed("width: var(var(--unset), 30px);"), "30px"); |
| }, "unset name-providing var() falls back"); |
| |
| // Whitespace around the substituted name is allowed. |
| test(() => { |
| assert_equals(computed("--myvar: --other; width: var( var(--myvar) );"), "10px"); |
| }, "whitespace around substituted name"); |
| |
| // A substituted name that is not itself a custom-property name (bad prefix) and no fallback: |
| // the property is invalid at computed-value time and falls back to initial (auto). |
| test(() => { |
| assert_equals(computed("--myvar: other; width: var(var(--myvar));"), initialWidth); |
| }, "invalid substituted name without fallback is invalid at computed-value time"); |
| |
| // A name argument that substitutes to nothing does not parse as a <custom-property-name>. |
| test(() => { |
| assert_equals(computed("--empty: ; width: var(var(--empty), 40px);"), "40px"); |
| }, "name argument substituting to nothing falls back"); |
| |
| // A <custom-property-name> is a single ident, so a multi-token name never parses. |
| test(() => { |
| assert_equals(computed("--two: --other --other; width: var(var(--two), 50px);"), "50px"); |
| }, "multi-token substituted name falls back"); |
| |
| test(() => { |
| assert_equals(computed("--dimension: 10px; width: var(var(--dimension), 60px);"), "60px"); |
| }, "dimension-token substituted name falls back"); |
| |
| test(() => { |
| assert_equals(computed('--string: "--other"; width: var(var(--string), 70px);'), "70px"); |
| }, "string substituted name falls back"); |
| |
| // "--" is not a valid <custom-property-name>. |
| test(() => { |
| assert_equals(computed("--dashes: --; width: var(var(--dashes), 80px);"), "80px"); |
| }, "-- as substituted name falls back"); |
| |
| // https://drafts.csswg.org/css-values-5/#component-function-commas |
| // A {}-wrapped free-form production represents the contents of the block, so the braces are |
| // stripped before the name is parsed. They are allowed even when not required. |
| test(() => { |
| assert_equals(computed("width: var({--other});"), "10px"); |
| }, "{}-wrapped literal name argument"); |
| |
| test(() => { |
| assert_equals(computed("--myvar: --other; width: var({var(--myvar)});"), "10px"); |
| }, "{}-wrapped substituted name argument"); |
| |
| test(() => { |
| assert_equals(computed("width: var( { --other } );"), "10px"); |
| }, "{}-wrapped name argument with whitespace"); |
| |
| test(() => { |
| assert_equals(computed("width: var({--unset}, 90px);"), "90px"); |
| }, "{}-wrapped name argument with fallback"); |
| |
| // Other arbitrary substitution functions can produce the name. |
| test(() => { |
| assert_equals(computed("width: var(attr(data-name type(*)));"), "10px"); |
| }, "var() name comes from attr()"); |
| |
| test(() => { |
| assert_equals(computed("width: var(attr(data-not-a-name type(*)), 100px);"), "100px"); |
| }, "var() name from attr() that is not a name falls back"); |
| |
| test(() => { |
| assert_equals(computed("width: var(if(style(--other: 10px): --other; else: --unset));"), "10px"); |
| }, "var() name comes from if()"); |
| |
| test(() => { |
| assert_equals(computed("width: var(random-item(--key, --other, --other));"), "10px"); |
| }, "var() name comes from random-item()"); |
| |
| // Cycles are detected through the name argument, since looking up the referenced property |
| // implies property replacement. https://drafts.csswg.org/css-variables-2/#replace-a-var-function |
| test(() => { |
| assert_equals(computed("--self: var(var(--self)); width: var(--self, 110px);"), "110px"); |
| }, "direct cycle through the name argument"); |
| |
| test(() => { |
| assert_equals(computed("--name: --cyclic; --cyclic: var(var(--name)); width: var(--cyclic, 120px);"), "120px"); |
| }, "indirect cycle through the name argument"); |
| |
| // Registered properties: a substituted name resolves them like a literal name does. |
| test(() => { |
| assert_equals(computed("--name: --registered-length; width: var(var(--name));"), "7px"); |
| }, "substituted name resolves a registered property"); |
| |
| // A universal registration without an initial value is guaranteed-invalid, so the fallback is used. |
| test(() => { |
| assert_equals(computed("--name: --registered-universal; width: var(var(--name), 130px);"), "130px"); |
| }, "substituted name of a guaranteed-invalid registered property uses the fallback"); |
| |
| // A name that does not parse has no referenced property, so there is no registered syntax to |
| // check the fallback against. |
| // https://drafts.css-houdini.org/css-properties-values-api/#fallbacks-in-var-references |
| test(() => { |
| assert_equals(computed("--name: not-a-name; width: var(var(--name), 140px);"), "140px"); |
| }, "fallback of an unparsed name is not syntax checked"); |
| </script> |
| </body> |
| </html> |