| <!DOCTYPE html> |
| <title>CSS Values: ident() as an arbitrary substitution function</title> |
| <link rel="help" href="https://drafts.csswg.org/css-values-5/#ident"> |
| <link rel="help" href="https://github.com/w3c/csswg-drafts/issues/14213"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <div id="target" data-name="myname" data-suffix="-suffix"></div> |
| <div id="siblings"><div></div><div></div><div id="third-child"></div></div> |
| <script> |
| "use strict"; |
| |
| // ident() is substituted at computed-value time, so its argument may contain other substitution |
| // functions and the identifier it produces may itself name a custom property. |
| |
| const target = document.getElementById("target"); |
| |
| function computed(declaration, property) { |
| target.setAttribute("style", declaration); |
| const value = getComputedStyle(target).getPropertyValue(property); |
| target.removeAttribute("style"); |
| return value; |
| } |
| |
| test(() => { |
| assert_equals(computed('--part: "name"; --result: ident("my" var(--part));', "--result"), "myname"); |
| }, "var() in the ident() argument"); |
| |
| test(() => { |
| assert_equals(computed('--result: ident("my" attr(data-name type(*)));', "--result"), "mymyname"); |
| }, "attr() in the ident() argument"); |
| |
| test(() => { |
| assert_equals(computed('--result: ident(ident("my") ident("name"));', "--result"), "myname"); |
| }, "ident() in the ident() argument"); |
| |
| test(() => { |
| assert_equals(computed('--myname: PASS; --result: var(ident("--" attr(data-name type(*))));', "--result"), "PASS"); |
| }, "ident() building the var() name argument from an attribute"); |
| |
| // <integer> parts include math functions, and the tree-counting ones resolve against the element |
| // the value is being computed for. |
| test(() => { |
| const third = document.getElementById("third-child"); |
| third.style.viewTransitionName = 'ident("vtl-" sibling-index())'; |
| assert_equals(getComputedStyle(third).viewTransitionName, "vtl-3"); |
| third.removeAttribute("style"); |
| }, "sibling-index() in the ident() argument"); |
| |
| test(() => { |
| const third = document.getElementById("third-child"); |
| third.style.viewTransitionName = 'ident("vtl-" calc(sibling-index() * 2))'; |
| assert_equals(getComputedStyle(third).viewTransitionName, "vtl-6"); |
| third.removeAttribute("style"); |
| }, "a math function over sibling-index() in the ident() argument"); |
| |
| test(() => { |
| assert_equals(computed('--result: ident(var(--missing));', "--result"), ""); |
| }, "an unresolvable argument makes ident() invalid at computed-value time"); |
| |
| test(() => { |
| assert_equals(computed('--result: ident(5px);', "--result"), ""); |
| }, "an argument that is not <ident-arg>+ makes ident() invalid at computed-value time"); |
| |
| // A cycle through the argument leaves the property guaranteed-invalid, it does not hang. |
| test(() => { |
| assert_equals(computed('--result: ident("x" var(--result));', "--result"), ""); |
| }, "a cycle through the ident() argument"); |
| |
| // https://drafts.csswg.org/css-values-5/#attr-security |
| // An identifier built from an attribute taints the value it substitutes into, so picking which |
| // property to substitute cannot smuggle out a URL the attribute never contained. |
| const url = "https://does-not-exist.test/404.png"; |
| |
| test(() => { |
| assert_equals(computed(`--myname: url("${url}"); background-image: var(ident("--" attr(data-name type(*))));`, |
| "background-image"), "none"); |
| }, "an ident() built from an attribute taints the property it names"); |
| |
| test(() => { |
| assert_equals(computed(`--myname: url("${url}"); --n: ident("--" attr(data-name type(*))); background-image: var(var(--n));`, |
| "background-image"), "none"); |
| }, "the taint survives the ident() being held in a custom property"); |
| |
| test(() => { |
| assert_equals(computed(`--myname: url("${url}"); background-image: var(ident("--" "myname"));`, |
| "background-image"), `url("${url}")`); |
| }, "an ident() with no attr() involved does not taint the property it names"); |
| |
| // A value the property cannot accept is invalid at computed-value time, which behaves as unset |
| // rather than falling back to the earlier declaration. |
| test(() => { |
| assert_equals(computed('width: 10px; width: ident("wid" "th");', "width"), |
| computed('width: 10px; width: unset;', "width")); |
| }, "ident() substituting a value the property does not accept"); |
| |
| </script> |