new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css-houdini/properties-and-values.html
@@ -0,0 +1,988 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+
+<style type="text/css">
+body {
+ font-size: 16px;
+}
+
+@keyframes number {
+ from { --number: 0; }
+ to { --number: 1; }
+}
+
+@keyframes transform {
+ from { --transform: rotate(0deg); }
+ to { --transform: rotate(180deg); }
+}
+
+@keyframes list1 {
+ from { --list: 0 0; }
+ to { --list: 1 2; }
+}
+
+@keyframes lp {
+ from { --lp: calc(0 + 15%); }
+ to { --lp: calc(45% + 14); }
+}
+
+@keyframes length {
+ from { --length: 0px; }
+ to { --length: 10px; }
+}
+
+@keyframes percentage {
+ from { --percentage: 0%; }
+ to { --percentage: 100%; }
+}
+
+@keyframes integer {
+ from { --integer: 0; }
+ to { --integer: 10; }
+}
+
+@keyframes lps1 {
+ from { --lps: 0px 0% calc(0px + 0%); }
+ to { --lps: 100px 100% calc(100px + 100%); }
+}
+
+@keyframes lps2 {
+ from { --lps: calc(50px + 0%) calc(0px + 50%) calc(50px + 50%); }
+ to { --lps: calc(100px + 0%) calc(0px + 100%) calc(0px + 0%); }
+}
+
+@keyframes missing {
+ from { --number: 0; }
+}
+</style>
+
+<div id="grandparent">
+ <div id="parent">
+ <div id="child"></div>
+ </div>
+</div>
+<div id="sandbox"></div>
+
+<a id="link" href="/"></a>
+
+<script>
+let baseURL = document.getElementById("link").href;
+
+let animTests = [
+ { keyframes: "number",
+ property: "--number",
+ syntax: "<number>",
+ halfway: "0.5"
+ },
+ { keyframes: "transform",
+ property: "--transform",
+ syntax: "<transform-function>",
+ halfway: "rotate(90deg)"
+ },
+ { keyframes: "list1",
+ property: "--list",
+ syntax: "<number>+",
+ halfway: "0.5 1.0"
+ },
+ { keyframes: "lp",
+ property: "--lp",
+ syntax: "<length-percentage>",
+ halfway: "calc(7 + 30%)"
+ },
+ { keyframes: "length",
+ property: "--length",
+ syntax: "<length>",
+ halfway: "5px",
+ },
+ { keyframes: "percentage",
+ property: "--percentage",
+ syntax: "<percentage>",
+ halfway: "50%",
+ },
+ { keyframes: "integer",
+ property: "--integer",
+ syntax: "<integer>",
+ halfway: "5",
+ },
+ { keyframes: "lps1",
+ property: "--lps",
+ syntax: "<length-percentage>+",
+ halfway: "50px 50% calc(50px + 50%)",
+ },
+ { keyframes: "lps2",
+ property: "--lps",
+ syntax: "<length-percentage>+",
+ halfway: "calc(75px + 0%) calc(0px + 75%) calc(25px + 25%)",
+ },
+];
+
+let animTests2 = [
+ { syntax: "<number>",
+ keyframes: [
+ "0",
+ "1",
+ ],
+ checks: {
+ "0.4": "0.4",
+ "0.6": "0.6",
+ },
+ },
+
+ // These are uninterpolable values.
+ // They should be flip at 50%.
+
+ { syntax: "<angle>",
+ keyframes: [
+ "0deg",
+ "90deg",
+ ],
+ checks: {
+ "0.4": "0deg",
+ "0.6": "90deg",
+ },
+ },
+
+ { syntax: "<time>",
+ keyframes: [
+ "3s",
+ "9s",
+ ],
+ checks: {
+ "0.4": "3s",
+ "0.6": "9s",
+ },
+ },
+
+ { syntax: "<resolution>",
+ keyframes: [
+ "96dpi",
+ "144dpi",
+ ],
+ checks: {
+ "0.4": "96dpi",
+ "0.6": "144dpi",
+ },
+ },
+
+ { syntax: "<custom-ident>",
+ keyframes: [
+ "doing-good",
+ "is-in-our-code",
+ ],
+ checks: {
+ "0.4": "doing-good",
+ "0.6": "is-in-our-code",
+ },
+ },
+
+ { syntax: "Mozilla | Firefox",
+ keyframes: [
+ "Mozilla",
+ "Firefox",
+ ],
+ checks: {
+ "0.4": "Mozilla",
+ "0.6": "Firefox",
+ },
+ },
+
+ // <url>s need to carry around a principal too, so it's worth testing that we
+ // are able to pass them around correctly (the others are all just unparsed
+ // strings).
+ { syntax: "<url>",
+ keyframes: [
+ "url(\"http://mozilla.com/\")",
+ "url(\"http://firefox.com/\")",
+ ],
+ checks: {
+ "0.4": "url(\"http://mozilla.com/\")",
+ "0.6": "url(\"http://firefox.com/\")",
+ },
+ },
+
+ { syntax: "*",
+ keyframes: [
+ "youve never seen the real numbers",
+ "like this before",
+ ],
+ checks: {
+ "0.4": "youve never seen the real numbers",
+ "0.6": "like this before",
+ },
+ },
+];
+
+// { registrations: [
+// { name: <custom property name with '--' prefix>,
+// syntax: <custom property syntax>,
+// inherits: bool,
+// initialValue: <computationally idempotent initial value>
+// },
+// ...
+// ],
+// grandparent: <style for grandparent element>,
+// parent: <style for parent element>,
+// child: <style for child element>,
+// expected: {
+// <computed property>: <expected value string>,
+// ...
+// },
+// }
+let familyTests = [
+ // Basic uninherited property.
+ { registrations: [
+ { name: "--a",
+ syntax: "<color>",
+ inherits: false,
+ initialValue: "red",
+ },
+ ],
+ grandparent: "--a: green; --a: red;",
+ parent: "--a: inherit;",
+ expected: {
+ "--a": "red",
+ },
+ },
+ // Transform function arguments should be computed.
+ { registrations: [
+ { name: "--a",
+ syntax: "<transform-function>",
+ inherits: true,
+ },
+ ],
+ parent: "font-size: 10px;",
+ child: "--a: translateX(5em);",
+ expected: {
+ "--a": "translateX(50px)",
+ },
+ },
+ // If no initialValue is provided and the syntax is *, then a special
+ // initial value used. This initial value must be considered parseable by
+ // registerProperty() but invalid at computed value time.
+ { registrations: [
+ { name: "--a",
+ syntax: "*",
+ inherits: true,
+ },
+ ],
+ parent: "--b: happy birthday; --b: hi var(--a);",
+ expected: {
+ // 'happy birthday' is overwritten in the cascade, and the var(--a)
+ // substitution is illegal.
+ "--b": "",
+ },
+ },
+ // Same as the previous test, with a fallback (i.e. we shouldn't quit before
+ // resolving).
+ { registrations: [
+ { name: "--a",
+ syntax: "*",
+ inherits: true,
+ },
+ ],
+ parent: "--b: happy birthday; --b: hi var(--a,to you);",
+ expected: {
+ // The leading space is part of the value!
+ "--b": " hi to you",
+ },
+ },
+ // Make sure we calculate font-relative lengths in lengths and
+ // length-percentages.
+ { registrations: [
+ { name: "--a",
+ syntax: "<length>",
+ },
+ { name: "--b",
+ syntax: "<length-percentage>",
+ },
+ ],
+ parent: "font-size: 15px",
+ child: "--a: 1em; --b: calc(1em + 20%);",
+ expected: {
+ "--a": "15px",
+ "--b": "calc(15px + 20%)",
+ },
+ },
+ // Make sure we set initial values on the root, even if nothing is ever
+ // specified.
+ { registrations: [
+ { name: "--a",
+ syntax: "<number>",
+ initialValue: "42",
+ },
+ ],
+ expected: {
+ "--a": "42",
+ },
+ },
+];
+
+
+let idempotentPassTests = [
+ // https://github.com/w3c/css-houdini-drafts/issues/247
+ { syntax: "<color>", initialValue: "currentColor" },
+ { syntax: "<length>", initialValue: "5px" },
+ { syntax: "<length>", initialValue: "calc(5px + 7px)" },
+ { syntax: "<transform-function>", initialValue: "translateX(5px)" },
+ { syntax: "<transform-function>+", initialValue: "translateX(5px) translateX(-10px)" },
+];
+
+let idempotentFailTests = [
+ { syntax: "<color>", initialValue: "inherit" },
+ { syntax: "<length>", initialValue: "5em" },
+ { syntax: "<length>", initialValue: "calc(5px + 5em)" },
+ { syntax: "<transform-function>", initialValue: "translateX(5em)" },
+ { syntax: "<integer> | <length>+", initialValue: "5px 0 -5em 0 12px" },
+ { syntax: "<color> | <transform-function>+", initialValue: "translateX(-10em)" },
+];
+
+let invalidSyntaxTests = [
+ { syntax: "<number>+ <color>" },
+ { syntax: "<i-don't-exist>+" },
+ { syntax: "" },
+ { syntax: "<number> || <color>" },
+ { syntax: "|"},
+ { syntax: "+", initialValue: "calc(5 + 7)" },
+ { syntax: " ", initialValue: "calc(5 + 7)" },
+ { syntax: "initial" },
+ { syntax: "inherit" },
+ { syntax: "unset" },
+ // We don't support revert yet.
+ //{ syntax: "revert" },
+ { syntax: "* worn out places" },
+ { syntax: "* | worn | out | faces" },
+ { syntax: "*+" },
+];
+
+let validSyntaxTests = [
+ { syntax: "*" },
+ { syntax: "<color>" },
+ { syntax: " <color> " },
+ { syntax: "<color> +" },
+
+ // Allow some CSS keywords, just not the CSS-wide keywords or revert.
+ { syntax: "center" },
+];
+
+let syntaxFailTests = [
+ { syntax: "<color>", initialValue: "5px" },
+ { syntax: "<transform-function>", initialValue: "red" },
+ { syntax: "<transform-function>+", initialValue: " " },
+ { syntax: "<resolution>", initialValue: "calc(0)" },
+ { syntax: "<custom-ident>", initialValue: "5px" },
+ { syntax: "to-me", initialValue: "who-are-you" },
+ // https://drafts.csswg.org/css-variables/#syntax
+ // "While <declaration-value> must represent at least one token, that one
+ // token may be whitespace. This implies that --foo: ; is valid, and the
+ // corresponding var(--foo) call would have a single space as its
+ // substitution value, but --foo:; is invalid."
+ { syntax: "*", initialValue: "" },
+];
+
+let syntaxPassTests = [
+ { syntax: "*", initialValue: "all around me are familiar faces" },
+ { syntax: "*", initialValue: "translateX(5px) 5em 73" },
+ { syntax: "<number>", initialValue: "5" },
+ { syntax: "<number> | <color>", initialValue: "red" },
+ { syntax: "<number>+", initialValue: "4 8 15 16 23 42" },
+ { syntax: "<number>+ | <url>", initialValue: "url(http://mozilla.org)" },
+ { syntax: "<transform-function>", initialValue: "translateX(5px)" },
+ { syntax: "<resolution>", initialValue: "5dpi" },
+ { syntax: "<custom-ident>", initialValue: "doesnt-really-matter" },
+ { syntax: "to-me", initialValue: "to-me" },
+ { syntax: "to-me | just-a-poor-boy", initialValue: "just-a-poor-boy" },
+ // https://drafts.csswg.org/css-variables/#syntax
+ // "While <declaration-value> must represent at least one token, that one
+ // token may be whitespace. This implies that --foo: ; is valid, and the
+ // corresponding var(--foo) call would have a single space as its
+ // substitution value, but --foo:; is invalid."
+ { syntax: "*", initialValue: " " },
+];
+
+let invalidNameTests = [
+ // https://drafts.csswg.org/css-syntax-3/#typedef-ident-token
+ { name: "--" },
+ { name: "-" },
+ { name: " " },
+ { name: "--5" },
+ { name: "--!" },
+];
+
+let validNameTests = [
+ { name: "--a" },
+ { name: "--A" },
+ { name: "--ç" },
+ { name: "--\\n" },
+];
+
+// https://drafts.csswg.org/cssom/#serialize-a-css-value
+
+let computedValueTests = [
+ // Parsing & computing (incl. serializing) lists of idents.
+ {
+ syntax: "a+ | <custom-ident>+",
+ declared: "a a a a b c d",
+ expected: "a a a a b c d",
+ },
+ // Angle units by themselves and in transform functions.
+ {
+ syntax: "<angle>",
+ declared: "0deg",
+ expected: "0deg",
+ },
+ {
+ syntax: "<angle>",
+ declared: "0grad",
+ expected: "0grad",
+ },
+ {
+ syntax: "<angle>",
+ declared: "0rad",
+ expected: "0rad",
+ },
+ {
+ syntax: "<angle>",
+ declared: "0turn",
+ expected: "0turn",
+ },
+ {
+ syntax: "<transform-function>",
+ declared: "rotate(0deg)",
+ expected: "rotate(0deg)",
+ },
+ {
+ syntax: "<transform-function>",
+ declared: "rotate(0grad)",
+ expected: "rotate(0grad)",
+ },
+ {
+ syntax: "<transform-function>",
+ declared: "rotate(0rad)",
+ expected: "rotate(0rad)",
+ },
+ {
+ syntax: "<transform-function>",
+ declared: "rotate(0turn)",
+ expected: "rotate(0turn)",
+ },
+ // Correctly serializing unitless 0 in '<length> | <percentage>'.
+ { syntax: "<length> | <percentage>",
+ declared: "0",
+ expected: "0px",
+ },
+
+ // Normalization of <length-percentage> values [1]
+ // [1]: https://drafts.css-houdini.org/css-properties-values-api/#calculation-of-computed-values
+
+ { syntax: "<length-percentage>",
+ declared: "calc(70% - 70% + 20px)",
+ expected: "calc(20px + 0%)",
+ },
+ {
+ syntax: "<length-percentage>",
+ declared: "calc(20px - 20px - 30%)",
+ expected: "calc(0px + -30%)",
+ },
+ // Should simplify calc(Xpx) to Xpx
+ { syntax: "<length-percentage>",
+ declared: "calc(20px - 17px)",
+ expected: "3px",
+ },
+ // Should simplify calc(X%) to X%
+ { syntax: "<length-percentage>",
+ declared: "calc(15% + 5%)",
+ expected: "20%",
+ },
+ { syntax: "<percentage> | <length>",
+ declared: "calc(15px)",
+ expected: "15px",
+ },
+ { syntax: "<length> | <percentage>",
+ declared: "calc(15%)",
+ expected: "15%",
+ },
+
+ // Test basic computation of each of the basic syntax strings.
+
+ { syntax: "<length>",
+ declared: "10px",
+ expected: "10px",
+ },
+
+ { syntax: "<number>",
+ declared: "3.1415",
+ expected: "3.1415",
+ },
+ { syntax: "<number>",
+ declared: "calc(1 * 2 + 3 / 4)",
+ expected: "2.75",
+ },
+ { syntax: "<number>",
+ declared: "calc(1 / 0)",
+ expected: "",
+ },
+
+ { syntax: "<percentage>",
+ declared: "2.7%",
+ expected: "2.7%",
+ },
+
+ { syntax: "<length-percentage>",
+ declared: "calc(13px + 27%)",
+ expected: "calc(13px + 27%)",
+ },
+
+ { syntax: "<color>",
+ declared: "#abcdef01",
+ expected: "rgba(171, 205, 239, 0.004)",
+ },
+ { syntax: "<color>",
+ declared: "red",
+ expected: "red",
+ },
+ { syntax: "<color>",
+ declared: "currentcolor",
+ expected: "currentcolor",
+ },
+
+ { syntax: "<image>",
+ declared: "url(\"http://example.com/some_image.jpg\")",
+ expected: "url(\"http://example.com/some_image.jpg\")",
+ },
+
+ { syntax: "<image>",
+ declared: "linear-gradient(45deg, white, black)",
+ expected: "linear-gradient(45deg, rgb(255, 255, 255), rgb(0, 0, 0))",
+ },
+
+ { syntax: "<image>",
+ declared: "radial-gradient(2em at 60px 50% , #000000 0%, #000000 14px, rgba(0, 0, 0, 0.3) 18px, rgba(0, 0, 0, 0) 4em)",
+ expected: "radial-gradient(32px at 60px 50%, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 14px, rgba(0, 0, 0, 0.3) 18px, transparent 64px)",
+ // This is the serialization result we get from calling getComputedStyle
+ // and getPropertyValue on a background-image property set to this value.
+ },
+
+ { syntax: "<url>",
+ declared: "url(\"http://example.com/\")",
+ expected: "url(\"http://example.com/\")",
+ },
+
+ { syntax: "<url>",
+ declared: "url(\"/some_link\")",
+ expected: "url(\"" + baseURL + "some_link\")",
+ },
+
+ { syntax: "<integer>",
+ declared: "42",
+ expected: "42",
+ },
+ { syntax: "<integer>",
+ declared: "calc(1 + 2 * 3 / 6)",
+ // Any divisions in <integer> calc()s cause the whole thing to be invalid at parse time.
+ expected: "",
+ },
+ { syntax: "<integer>",
+ declared: "calc(1 + 2 * 3)",
+ expected: "7",
+ },
+ { syntax: "<integer>",
+ declared: "calc(1 + 2 + 3)",
+ expected: "6",
+ },
+ { syntax: "<integer>",
+ declared: "calc(1 * 2 * 3)",
+ expected: "6",
+ },
+ { syntax: "<integer>",
+ declared: "calc(0.5 * 2 * 3)",
+ expected: "",
+ },
+ // Additive coefficients should be resolved.
+ { syntax: "<integer>",
+ declared: "calc((1 + 2) * 3)",
+ expected: "9",
+ },
+ { syntax: "<integer>",
+ declared: "calc((1 * 2) * 3)",
+ expected: "6",
+ },
+ { syntax: "<integer>",
+ declared: "calc(1 * (2 * 3))",
+ expected: "6",
+ },
+ // This isn't unitless zero -- this is an integral zero!
+ { syntax: "<integer>",
+ declared: "calc(0)",
+ expected: "0",
+ },
+ { syntax: "<integer>",
+ declared: "calc(((0) * 3) + 7)",
+ expected: "7",
+ },
+
+ { syntax: "<angle>",
+ declared: "37deg",
+ expected: "37deg",
+ },
+
+ { syntax: "<time>",
+ declared: "5s",
+ expected: "5s",
+ },
+
+ { syntax: "<resolution>",
+ declared: "50dppx",
+ expected: "50dppx",
+ },
+
+ { syntax: "<transform-function>",
+ declared: "matrix(1, 0, 1, 0, 1, 1)",
+ expected: "matrix(1, 0, 1, 0, 1, 1)",
+ },
+
+ { syntax: "<transform-function>",
+ declared: "matrix(1, 0, 1, 0, 1, 1)",
+ expected: "matrix(1, 0, 1, 0, 1, 1)",
+ },
+
+ { syntax: "Mozilla | Firefox",
+ declared: "Mozilla",
+ expected: "Mozilla",
+ },
+
+ { syntax: "<custom-ident>",
+ declared: "Mozilla",
+ expected: "Mozilla",
+ },
+
+ { syntax: "*",
+ declared: " ",
+ expected: " ",
+ },
+
+ { syntax: "*",
+ declared: "i was the shadow of the waxwing slain",
+ expected: "i was the shadow of the waxwing slain",
+ },
+
+ // https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-getpropertyvalue
+ // We should return the empty string when there is no declaration (in this
+ // case, beacuse couldn't compute.)
+
+ { syntax: "<length>",
+ declared: "calc(5px + 7%)",
+ expected: "",
+ },
+
+ { syntax: "<percentage>",
+ declared: "calc(7% + 5px)",
+ expected: "",
+ },
+
+ { syntax: "<length>",
+ declared: "Mozilla",
+ expected: "",
+ },
+
+ { syntax: "<length>+",
+ declared: "5px calc(5px + 7%) 1em",
+ // This should be invalid at parse time.
+ expected: "",
+ },
+];
+
+// Testing functions.
+
+// Animation tests.
+
+let grandparent = document.getElementById("grandparent");
+let parent = document.getElementById("parent");
+let child = document.getElementById("child");
+let sandbox = document.getElementById("sandbox");
+
+for (let spec of animTests) {
+ let { keyframes, property, syntax, halfway } = spec;
+ test(function () {
+ try {
+ CSS.registerProperty({ name: property, syntax: syntax });
+
+ // Store into |computedExpected| the computed value we get from specifying
+ // |halfway|. This should normalize the value.
+ sandbox.style.setProperty(property, halfway);
+ let computedExpected = window.getComputedStyle(sandbox).getPropertyValue(property);
+ sandbox.style.removeProperty(property);
+
+ grandparent.style.setProperty("animation-name", keyframes);
+ grandparent.style.setProperty("animation-duration", "1s");
+ grandparent.style.setProperty("animation-timing-function", "linear");
+
+ let anim = grandparent.getAnimations()[0];
+ anim.pause();
+ anim.currentTime = 500;
+
+ let computed = window.getComputedStyle(grandparent).getPropertyValue(property);
+ assert_equals(computed, computedExpected, "halfway value equals expected value");
+ } finally {
+ grandparent.style.removeProperty("animation-name");
+ grandparent.style.removeProperty("animation-duration");
+ grandparent.style.removeProperty("animation-timing-function");
+ CSS.unregisterProperty(property);
+ }
+ }, "anim: " + JSON.stringify(spec));
+}
+
+for (let spec of animTests2) {
+ let { keyframes, initial, syntax, checks } = spec;
+ test(function () {
+ try {
+ CSS.registerProperty({ name: "--test", syntax: syntax });
+
+ let anim = grandparent.animate(keyframes.map(value => ({ "--test": value })), 1000);
+ try {
+ anim.pause();
+ for (let point in checks) {
+ let time = parseFloat(point);
+ anim.currentTime = time * 1000;
+ let computed = window.getComputedStyle(grandparent).getPropertyValue("--test");
+ assert_equals(computed, checks[point], `value at time ${time} equals expected value`);
+ }
+ } finally {
+ anim.cancel();
+ }
+ } finally {
+ grandparent.style.removeProperty("--test");
+ CSS.unregisterProperty("--test");
+ }
+ }, "anim #2: " + JSON.stringify(spec));
+}
+
+// Make sure that we fill in missing keyframe values.
+// It's possible to have custom properties without initial values (i.e. they
+// have invalid initial values).
+// CSS Animations says:
+// If a ‘0%’ or ‘from’ keyframe is not specified, then the user agent
+// constructs a ‘0%’ keyframe using the computed values of the properties
+// being animated. If a ‘100%’ or ‘to’ keyframe is not specified, then the
+// user agent constructs a ‘100%’ keyframe using the computed values of the
+// properties being animated.
+// But for a custom property without an initial value, it's not possible to
+// fill in a value.
+
+test(function () {
+ CSS.registerProperty({ name: "--number", syntax: "<number>" });
+
+ grandparent.style.setProperty("animation-name", "missing");
+ grandparent.style.setProperty("animation-duration", "1s");
+ grandparent.style.setProperty("animation-timing-function", "linear");
+
+ let anim = grandparent.getAnimations()[0];
+ anim.pause();
+ anim.currentTime = 500;
+
+ try {
+ assert_equals(window.getComputedStyle(grandparent)
+ .getPropertyValue("--number"),
+ "",
+ "There should be no animation if there is no property to " +
+ "fall back on.");
+ // Shouldn't segfault while uncomputing values either!
+ for (let keyframe of anim.effect.getKeyframes()) {
+ assert_equals(keyframe["--number"], undefined,
+ "Shouldn't have --number in keyframes when we were missing a " +
+ "keyframe value.");
+ }
+ } finally {
+ CSS.unregisterProperty("--number");
+ grandparent.style.removeProperty("animation-name");
+ grandparent.style.removeProperty("animation-duration");
+ grandparent.style.removeProperty("animation-timing-function");
+ }
+}, "Custom properties without initial values missing in keyframes should be " +
+ "removed.");
+
+test(function () {
+ CSS.registerProperty({ name: "--number", syntax: "<number>" });
+
+ grandparent.style.setProperty("animation-name", "missing");
+ grandparent.style.setProperty("animation-duration", "1s");
+ grandparent.style.setProperty("animation-timing-function", "linear");
+ grandparent.style.setProperty("--number", "1");
+
+ let anim = grandparent.getAnimations()[0];
+ anim.pause();
+ anim.currentTime = 500;
+
+ try {
+ assert_equals(window.getComputedStyle(grandparent)
+ .getPropertyValue("--number"),
+ "0.5",
+ "Animation value should have been filled in using computed " +
+ "value.");
+ for (let keyframe of anim.effect.getKeyframes()) {
+ assert_true(keyframe["--number"] != undefined,
+ "Should have --number in keyframes, serialized as a custom property.");
+ }
+ } finally {
+ CSS.unregisterProperty("--number");
+ grandparent.style.removeProperty("animation-name");
+ grandparent.style.removeProperty("animation-duration");
+ grandparent.style.removeProperty("animation-timing-function");
+ }
+}, "Custom properties in missing keyframes using computed values should be " +
+ "filled in.");
+
+for (let spec of familyTests) {
+ test(function () {
+ let registered = [];
+ try {
+ for (let registration of spec.registrations) {
+ CSS.registerProperty(registration);
+ registered.push(registration.name);
+ }
+
+ grandparent.setAttribute("style", spec.grandparent || "");
+ parent.setAttribute("style", spec.parent || "");
+ child.setAttribute("style", spec.child || "");
+
+ for (let key in spec.expected) {
+ assert_equals(window.getComputedStyle(child).getPropertyValue(key),
+ spec.expected[key], "property value equals expected value");
+ }
+ } finally {
+ grandparent.removeAttribute("style");
+ parent.removeAttribute("style");
+ child.removeAttribute("style");
+
+ for (let name of registered) {
+ CSS.unregisterProperty(name);
+ }
+ }
+ }, "family: " + JSON.stringify(spec));
+}
+
+let expectFail = function (testName, specs, type, message) {
+ for (let spec of specs) {
+ test(function () {
+ if (!spec.name) {
+ spec.name = "--test-property";
+ }
+ let caught = null;
+ try {
+ CSS.registerProperty(spec);
+ } catch (e) {
+ caught = e;
+ }
+ let passed = caught instanceof DOMException && caught.name == type;
+ try {
+ assert_true(passed, message + " " + JSON.stringify(spec));
+ } finally {
+ if (!passed) {
+ CSS.unregisterProperty(spec.name);
+ throw caught;
+ }
+ }
+ }, testName + ": " + JSON.stringify(spec));
+ }
+};
+
+let expectSucceed = function (testName, specs, message) {
+ for (let spec of specs) {
+ test(function () {
+ if (!spec.name) {
+ spec.name = "--test-property";
+ }
+ let caught = null;
+ try {
+ CSS.registerProperty(spec);
+ } catch (e) {
+ caught = e;
+ }
+ let passed = !caught;
+ assert_true(passed, message + " " + JSON.stringify(spec));
+ if (passed) {
+ CSS.unregisterProperty(spec.name);
+ }
+ }, testName + ": " + JSON.stringify(spec));
+ }
+};
+
+expectFail("idempotent fail", idempotentFailTests, "SyntaxError",
+ "Initial values that are not computationally idempotent must trigger a " +
+ "SyntaxError.");
+
+expectSucceed("idempotent pass", idempotentPassTests,
+ "Initial values that are computationally idempotent must not trigger a " +
+ "SyntaxError.");
+
+expectFail("invalid syntax", invalidSyntaxTests, "SyntaxError",
+ "Invalid syntaxes must trigger a SyntaxError.");
+
+expectSucceed("valid syntax", validSyntaxTests,
+ "Valid syntaxes must not trigger a SyntaxError.");
+
+expectFail("syntax fail", syntaxFailTests, "SyntaxError",
+ "Initial values that are syntactically invalid must trigger a " +
+ "SyntaxError.");
+
+expectSucceed("syntax pass", syntaxPassTests,
+ "Initial values that are syntactically valid must not trigger a " +
+ "SyntaxError.");
+
+expectFail("invalid name", invalidNameTests, "SyntaxError",
+ "Attempting to register properties with a name that doesn’t correspond " +
+ "to the <custom-property-name> production must cause registerProperty() " +
+ "to throw a SyntaxError.");
+
+expectSucceed("valid name", validNameTests, "Allow valid custom property names.");
+
+// We should error on duplicate registrations.
+test(function () {
+ let caughtDuplicate = false;
+ CSS.registerProperty({ name: "--duplicate-ime" });
+ try {
+ CSS.registerProperty({ name: "--duplicate-ime" });
+ CSS.unregisterProperty("--duplicate-ime");
+ } catch (e) {
+ caughtDuplicate =
+ e instanceof DOMException &&
+ e.name == "InvalidModificationError";
+ }
+ assert_true(caughtDuplicate, "caught exception");
+}, "Attempting to register a property with a name that has already been " +
+ "registered should result in an InvalidModificationError.");
+
+// We should error when trying to unregister a property that hasn't been
+// registered.
+test(function () {
+ let caughtNotFound = false;
+ try {
+ CSS.unregisterProperty("--not-found-nfe");
+ } catch (e) {
+ caughtNotFound =
+ e instanceof DOMException &&
+ e.name == "NotFoundError";
+ }
+ assert_true(caughtNotFound, "caught exception");
+}, "Attempting to unregister a property that doesn't exist should result in " +
+ "a NotFoundError.");
+
+for (let spec of computedValueTests) {
+ let { syntax, declared, expected } = spec;
+ test(function () {
+ try {
+ CSS.registerProperty({ name: "--computed", syntax: syntax });
+ child.setAttribute("style", `--computed:${declared};`);
+
+ assert_equals(window.getComputedStyle(child).getPropertyValue("--computed"),
+ expected, "computed value equals expected value");
+
+ } finally {
+ child.removeAttribute("style");
+ CSS.unregisterProperty("--computed");
+ }
+ }, "computed value: " + JSON.stringify(spec));
+}
+</script>