Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<html>
<head>
<title>var() name argument is an arbitrary substitution value</title>
<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().
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");
// 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
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.
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>