summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/css/css-properties-values-api
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/css/css-properties-values-api')
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/META.yml4
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animate-invalid.html20
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-custom-ident.html13
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-image.html13
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-inherited-used-by-standard-property.html29
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-list-type-mismatch.html18
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-non-inherited-used-by-standard-property.html27
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-comma-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-space-list.html59
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-function.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-multiple-values.html68
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-single-values.html57
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-none.tentative.html30
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-url.html13
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-used-in-shorthand.html34
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-angle.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-color.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-custom-ident.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-image.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-inherited-used-by-standard-property.html31
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-integer.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length-percentage.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-inherited-property-numbers.html43
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-list.html201
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-property-numbers.html39
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-non-inherited-used-by-standard-property.html28
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-number.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-percentage.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-property-all.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-resolution.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-time.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-box-size.tentative.html19
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-matrix.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-none.tentative.html18
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-to-list.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-box-size.tentative.html19
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-matrix.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-none.tentative.html18
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-url.html17
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-animation.html451
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-cssom.html191
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-non-matching-media-crash.html19
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-shadow.html46
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-stylesheets.html106
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-typedom.html64
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units-dynamic.html41
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units.html91
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/at-property.html316
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/conditional-rules.html43
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/crashtests/initial-in-audio-crash.html13
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/determine-registration.html264
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/font-size-animation.html44
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/get-computed-style-enumeration.html96
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/idlharness.html16
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/invalid-at-computed-value-time.html73
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/property-cascade.html63
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/register-property-syntax-parsing.html287
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/register-property.html75
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-properties-inheritance.html96
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-001.html50
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002-ref.html25
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002.html45
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-computation.html181
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-crosstalk.html43
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-cssom.html91
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-initial.html71
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/registered-property-revert.html94
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/resources/utils.js245
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/self-utils.html41
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.css4
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.js11
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/support/main/main.css22
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/support/main/main.js25
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/support/main/main.utf16be.cssbin0 -> 184 bytes
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/typedom.html993
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/unit-cycles.html341
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/url-resolution.html137
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html183
-rw-r--r--testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties.html207
107 files changed, 7875 insertions, 0 deletions
diff --git a/testing/web-platform/tests/css/css-properties-values-api/META.yml b/testing/web-platform/tests/css/css-properties-values-api/META.yml
new file mode 100644
index 0000000000..38cd166ca8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/META.yml
@@ -0,0 +1,4 @@
+spec: https://drafts.css-houdini.org/css-properties-values-api/
+suggested_reviewers:
+ - tabatkins
+ - astearns
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animate-invalid.html b/testing/web-platform/tests/css/css-properties-values-api/animate-invalid.html
new file mode 100644
index 0000000000..41cbd067b5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animate-invalid.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Do not crash when animating to unresolved var()</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty">
+<link rel="help" href="https://crbug.com/1185524">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="target"></div>
+<script>
+ promise_test(async function(){
+ CSS.registerProperty({
+ name: '--x',
+ syntax: '<number>',
+ initialValue: '1',
+ inherits: false
+ });
+ let animation = target.animate({'--x': [ 'var(--unknown)']}, 100);
+ await animation.ready;
+ assert_equals(getComputedStyle(target).getPropertyValue('--x'), '1');
+ }, 'Do not crash when animating to unresolved var()');
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-comma-list.html
new file mode 100644
index 0000000000..a14b0bb09e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<angle>#",
+ inherits: false,
+ initialValue: "0deg"
+}, {
+ keyframes: ["100deg, 150deg", "200deg, 250deg"],
+ expected: "150deg, 200deg"
+}, 'Animating a custom property of type <angle>#');
+
+animation_test({
+ syntax: "<angle>#",
+ inherits: false,
+ initialValue: "100deg, 150deg"
+}, {
+ keyframes: "200deg, 250deg",
+ expected: "150deg, 200deg"
+}, 'Animating a custom property of type <angle># with a single keyframe');
+
+animation_test({
+ syntax: "<angle>#",
+ inherits: false,
+ initialValue: "50deg, 100deg"
+}, {
+ composite: "add",
+ keyframes: ["150deg, 200deg", "250deg, 300deg"],
+ expected: "250deg, 350deg"
+}, 'Animating a custom property of type <angle># with additivity');
+
+animation_test({
+ syntax: "<angle>#",
+ inherits: false,
+ initialValue: "50deg, 100deg"
+}, {
+ composite: "add",
+ keyframes: "150deg, 200deg",
+ expected: "125deg, 200deg"
+}, 'Animating a custom property of type <angle># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<angle>#",
+ inherits: false,
+ initialValue: "0deg, 0deg"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0deg, 50deg", "100deg, 100deg"],
+ expected: "250deg, 275deg"
+}, 'Animating a custom property of type <angle># with iterationComposite');
+
+discrete_animation_test("<angle>#", '10deg, 20deg', '30deg', 'Animating a custom property of type <angle># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-space-list.html
new file mode 100644
index 0000000000..8ce7a9deab
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<angle>+",
+ inherits: false,
+ initialValue: "0deg"
+}, {
+ keyframes: ["100deg 150deg", "200deg 250deg"],
+ expected: "150deg 200deg"
+}, 'Animating a custom property of type <angle>+');
+
+animation_test({
+ syntax: "<angle>+",
+ inherits: false,
+ initialValue: "100deg 150deg"
+}, {
+ keyframes: "200deg 250deg",
+ expected: "150deg 200deg"
+}, 'Animating a custom property of type <angle>+ with a single keyframe');
+
+animation_test({
+ syntax: "<angle>+",
+ inherits: false,
+ initialValue: "50deg 100deg"
+}, {
+ composite: "add",
+ keyframes: ["150deg 200deg", "250deg 300deg"],
+ expected: "250deg 350deg"
+}, 'Animating a custom property of type <angle>+ with additivity');
+
+animation_test({
+ syntax: "<angle>+",
+ inherits: false,
+ initialValue: "50deg 100deg"
+}, {
+ composite: "add",
+ keyframes: "150deg 200deg",
+ expected: "125deg 200deg"
+}, 'Animating a custom property of type <angle>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<angle>+",
+ inherits: false,
+ initialValue: "0deg 0deg"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0deg 50deg", "100deg 100deg"],
+ expected: "250deg 275deg"
+}, 'Animating a custom property of type <angle>+ with iterationComposite');
+
+discrete_animation_test("<angle>+", '10deg 20deg', '30deg', 'Animating a custom property of type <angle>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle.html
new file mode 100644
index 0000000000..0cb06aef0b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-angle.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<angle>",
+ inherits: false,
+ initialValue: "0deg"
+}, {
+ keyframes: ["100deg", "200deg"],
+ expected: "150deg"
+}, 'Animating a custom property of type <angle>');
+
+animation_test({
+ syntax: "<angle>",
+ inherits: false,
+ initialValue: "100deg"
+}, {
+ keyframes: "200deg",
+ expected: "150deg"
+}, 'Animating a custom property of type <angle> with a single keyframe');
+
+animation_test({
+ syntax: "<angle>",
+ inherits: false,
+ initialValue: "100deg"
+}, {
+ composite: "add",
+ keyframes: ["200deg", "300deg"],
+ expected: "350deg"
+}, 'Animating a custom property of type <angle> with additivity');
+
+animation_test({
+ syntax: "<angle>",
+ inherits: false,
+ initialValue: "100deg"
+}, {
+ composite: "add",
+ keyframes: "300deg",
+ expected: "250deg"
+}, 'Animating a custom property of type <angle> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<angle>",
+ inherits: false,
+ initialValue: "100deg"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0deg", "100deg"],
+ expected: "250deg"
+}, 'Animating a custom property of type <angle> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-comma-list.html
new file mode 100644
index 0000000000..7148df0724
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<color>#",
+ inherits: false,
+ initialValue: "black"
+}, {
+ keyframes: ["rgb(100, 100, 100), rgb(150, 150, 150)", "rgb(200, 200, 200), rgb(250, 250, 250)"],
+ expected: "rgb(150, 150, 150), rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color>#');
+
+animation_test({
+ syntax: "<color>#",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100), rgb(150, 150, 150)"
+}, {
+ keyframes: "rgb(200, 200, 200), rgb(250, 250, 250)",
+ expected: "rgb(150, 150, 150), rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color># with a single keyframe');
+
+animation_test({
+ syntax: "<color>#",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100), rgb(150, 150, 150)"
+}, {
+ composite: "add",
+ keyframes: ["rgb(25, 25, 25), rgb(50, 50, 50)", "rgb(75, 75, 75), rgb(100, 100, 100)"],
+ expected: "rgb(150, 150, 150), rgb(225, 225, 225)"
+}, 'Animating a custom property of type <color># with additivity');
+
+animation_test({
+ syntax: "<color>#",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100), rgb(150, 150, 150)"
+}, {
+ composite: "add",
+ keyframes: "rgb(50, 50, 50), rgb(100, 100, 100)",
+ expected: "rgb(125, 125, 125), rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<color>#",
+ inherits: false,
+ initialValue: "black"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["rgb(0, 0, 0), rgb(0, 0, 0)", "rgb(50, 50, 50), rgb(100, 100, 100)"],
+ expected: "rgb(125, 125, 125), rgb(250, 250, 250)"
+}, 'Animating a custom property of type <color># with iterationComposite');
+
+discrete_animation_test("<color>#", 'rgb(255, 0, 0), rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'Animating a custom property of type <color># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-space-list.html
new file mode 100644
index 0000000000..679e244667
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<color>+",
+ inherits: false,
+ initialValue: "black"
+}, {
+ keyframes: ["rgb(100, 100, 100) rgb(150, 150, 150)", "rgb(200, 200, 200) rgb(250, 250, 250)"],
+ expected: "rgb(150, 150, 150) rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color>+');
+
+animation_test({
+ syntax: "<color>+",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100) rgb(150, 150, 150)"
+}, {
+ keyframes: "rgb(200, 200, 200) rgb(250, 250, 250)",
+ expected: "rgb(150, 150, 150) rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color>+ with a single keyframe');
+
+animation_test({
+ syntax: "<color>+",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100) rgb(150, 150, 150)"
+}, {
+ composite: "add",
+ keyframes: ["rgb(25, 25, 25) rgb(50, 50, 50)", "rgb(75, 75, 75) rgb(100, 100, 100)"],
+ expected: "rgb(150, 150, 150) rgb(225, 225, 225)"
+}, 'Animating a custom property of type <color>+ with additivity');
+
+animation_test({
+ syntax: "<color>+",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100) rgb(150, 150, 150)"
+}, {
+ composite: "add",
+ keyframes: "rgb(50, 50, 50) rgb(100, 100, 100)",
+ expected: "rgb(125, 125, 125) rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<color>+",
+ inherits: false,
+ initialValue: "black"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["rgb(0, 0, 0) rgb(0, 0, 0)", "rgb(50, 50, 50) rgb(100, 100, 100)"],
+ expected: "rgb(125, 125, 125) rgb(250, 250, 250)"
+}, 'Animating a custom property of type <color>+ with iterationComposite');
+
+discrete_animation_test("<color>+", 'rgb(255, 0, 0) rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'Animating a custom property of type <color>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color.html
new file mode 100644
index 0000000000..001fc407e7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-color.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<color>",
+ inherits: false,
+ initialValue: "black"
+}, {
+ keyframes: ["rgb(100, 100, 100)", "rgb(200, 200, 200)"],
+ expected: "rgb(150, 150, 150)"
+}, 'Animating a custom property of type <color>');
+
+animation_test({
+ syntax: "<color>",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100)"
+}, {
+ keyframes: "rgb(200, 200, 200)",
+ expected: "rgb(150, 150, 150)"
+}, 'Animating a custom property of type <color> with a single keyframe');
+
+animation_test({
+ syntax: "<color>",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100)"
+}, {
+ composite: "add",
+ keyframes: ["rgb(50, 50, 50)", "rgb(150, 150, 150)"],
+ expected: "rgb(200, 200, 200)"
+}, 'Animating a custom property of type <color> with additivity');
+
+animation_test({
+ syntax: "<color>",
+ inherits: false,
+ initialValue: "rgb(100, 100, 100)"
+}, {
+ composite: "add",
+ keyframes: "rgb(150, 150, 150)",
+ expected: "rgb(175, 175, 175)"
+}, 'Animating a custom property of type <color> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<color>",
+ inherits: false,
+ initialValue: "black"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["rgb(0, 0, 0)", "rgb(100, 100, 100)"],
+ expected: "rgb(250, 250, 250)"
+}, 'Animating a custom property of type <color> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-custom-ident.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-custom-ident.html
new file mode 100644
index 0000000000..5bc2bed343
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-custom-ident.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+discrete_animation_test("<custom-ident>", "from", "to");
+discrete_animation_test("<custom-ident>+", "from1 from2", "to1 to2");
+discrete_animation_test("<custom-ident>#", "from1, from2", "to1, to2");
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-image.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-image.html
new file mode 100644
index 0000000000..4f9505f9a8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-image.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+discrete_animation_test("<image>", 'url("https://example.com/from")', 'url("https://example.com/to")');
+discrete_animation_test("<image>+", 'url("https://example.com/from1") url("https://example.com/from2")', 'url("https://example.com/to1") url("https://example.com/to2")');
+discrete_animation_test("<image>#", 'url("https://example.com/from1"), url("https://example.com/from2")', 'url("https://example.com/to1"), url("https://example.com/to2")');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-inherited-used-by-standard-property.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-inherited-used-by-standard-property.html
new file mode 100644
index 0000000000..231ecca8c7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-inherited-used-by-standard-property.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="container">
+ <div id="target"></div>
+</div>
+<script>
+
+test(() => {
+ CSS.registerProperty({
+ name: "--my-length",
+ syntax: "<length>",
+ inherits: true,
+ initialValue: "0px"
+ });
+
+ target.style.marginLeft = "var(--my-length)";
+
+ const duration = 1000;
+ const animation = container.animate({ "--my-length": "100px" }, duration);
+ animation.pause();
+ animation.currentTime = duration / 4;
+
+ assert_equals(getComputedStyle(target).marginLeft, "25px");
+}, "Animating an inherited CSS variable on a parent is reflected on a standard property using that variable as a value on a child");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-comma-list.html
new file mode 100644
index 0000000000..3f4beaedf1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<integer>#",
+ inherits: false,
+ initialValue: "0"
+}, {
+ keyframes: ["100, 150", "200, 250"],
+ expected: "150, 200"
+}, 'Animating a custom property of type <integer>#');
+
+animation_test({
+ syntax: "<integer>#",
+ inherits: false,
+ initialValue: "100, 150"
+}, {
+ keyframes: "200, 250",
+ expected: "150, 200"
+}, 'Animating a custom property of type <integer># with a single keyframe');
+
+animation_test({
+ syntax: "<integer>#",
+ inherits: false,
+ initialValue: "50, 100"
+}, {
+ composite: "add",
+ keyframes: ["150, 200", "250, 300"],
+ expected: "250, 350"
+}, 'Animating a custom property of type <integer># with additivity');
+
+animation_test({
+ syntax: "<integer>#",
+ inherits: false,
+ initialValue: "50, 100"
+}, {
+ composite: "add",
+ keyframes: "150, 200",
+ expected: "125, 200"
+}, 'Animating a custom property of type <integer># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<integer>#",
+ inherits: false,
+ initialValue: "0, 0"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0, 50", "100, 100"],
+ expected: "250, 275"
+}, 'Animating a custom property of type <integer># with iterationComposite');
+
+discrete_animation_test("<integer>#", '10, 20', '30', 'Animating a custom property of type <integer># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-space-list.html
new file mode 100644
index 0000000000..89feb3af15
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<integer>+",
+ inherits: false,
+ initialValue: "0"
+}, {
+ keyframes: ["100 150", "200 250"],
+ expected: "150 200"
+}, 'Animating a custom property of type <integer>+');
+
+animation_test({
+ syntax: "<integer>+",
+ inherits: false,
+ initialValue: "100 150"
+}, {
+ keyframes: "200 250",
+ expected: "150 200"
+}, 'Animating a custom property of type <integer>+ with a single keyframe');
+
+animation_test({
+ syntax: "<integer>+",
+ inherits: false,
+ initialValue: "50 100"
+}, {
+ composite: "add",
+ keyframes: ["150 200", "250 300"],
+ expected: "250 350"
+}, 'Animating a custom property of type <integer>+ with additivity');
+
+animation_test({
+ syntax: "<integer>+",
+ inherits: false,
+ initialValue: "50 100"
+}, {
+ composite: "add",
+ keyframes: "150 200",
+ expected: "125 200"
+}, 'Animating a custom property of type <integer>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<integer>+",
+ inherits: false,
+ initialValue: "0 0"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0 50", "100 100"],
+ expected: "250 275"
+}, 'Animating a custom property of type <integer>+ with iterationComposite');
+
+discrete_animation_test("<integer>+", '10 20', '30', 'Animating a custom property of type <integer>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer.html
new file mode 100644
index 0000000000..400308a92e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-integer.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<integer>",
+ inherits: false,
+ initialValue: 0
+}, {
+ keyframes: [100, 200],
+ expected: "150"
+}, 'Animating a custom property of type <integer>');
+
+animation_test({
+ syntax: "<integer>",
+ inherits: false,
+ initialValue: 100
+}, {
+ keyframes: 200,
+ expected: "150"
+}, 'Animating a custom property of type <integer> with a single keyframe');
+
+animation_test({
+ syntax: "<integer>",
+ inherits: false,
+ initialValue: 100
+}, {
+ composite: "add",
+ keyframes: [200, 300],
+ expected: "350"
+}, 'Animating a custom property of type <integer> with additivity');
+
+animation_test({
+ syntax: "<integer>",
+ inherits: false,
+ initialValue: 100
+}, {
+ composite: "add",
+ keyframes: 300,
+ expected: "250"
+}, 'Animating a custom property of type <integer> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<integer>",
+ inherits: false,
+ initialValue: 100
+}, {
+ iterationComposite: "accumulate",
+ keyframes: [0, 100],
+ expected: "250"
+}, 'Animating a custom property of type <integer> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-comma-list.html
new file mode 100644
index 0000000000..6f1fefb76f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<length>#",
+ inherits: false,
+ initialValue: "0px"
+}, {
+ keyframes: ["100px, 150px", "200px, 250px"],
+ expected: "150px, 200px"
+}, 'Animating a custom property of type <length>#');
+
+animation_test({
+ syntax: "<length>#",
+ inherits: false,
+ initialValue: "100px, 150px"
+}, {
+ keyframes: "200px, 250px",
+ expected: "150px, 200px"
+}, 'Animating a custom property of type <length># with a single keyframe');
+
+animation_test({
+ syntax: "<length>#",
+ inherits: false,
+ initialValue: "50px, 100px"
+}, {
+ composite: "add",
+ keyframes: ["150px, 200px", "250px, 300px"],
+ expected: "250px, 350px"
+}, 'Animating a custom property of type <length># with additivity');
+
+animation_test({
+ syntax: "<length>#",
+ inherits: false,
+ initialValue: "50px, 100px"
+}, {
+ composite: "add",
+ keyframes: "150px, 200px",
+ expected: "125px, 200px"
+}, 'Animating a custom property of type <length># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<length>#",
+ inherits: false,
+ initialValue: "0px, 0px"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0px, 50px", "100px, 100px"],
+ expected: "250px, 275px"
+}, 'Animating a custom property of type <length># with iterationComposite');
+
+discrete_animation_test("<length>#", '10px, 20px', '30px', 'Animating a custom property of type <length># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-comma-list.html
new file mode 100644
index 0000000000..bf3e75b791
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<length-percentage>#",
+ inherits: false,
+ initialValue: "0px"
+}, {
+ keyframes: ["100px, 150%", "200%, 250px"],
+ expected: "calc(100% + 50px), calc(75% + 125px)"
+}, 'Animating a custom property of type <length-percentage>#');
+
+animation_test({
+ syntax: "<length-percentage>#",
+ inherits: false,
+ initialValue: "100px, 150%"
+}, {
+ keyframes: "200%, 250px",
+ expected: "calc(100% + 50px), calc(75% + 125px)"
+}, 'Animating a custom property of type <length-percentage># with a single keyframe');
+
+animation_test({
+ syntax: "<length-percentage>#",
+ inherits: false,
+ initialValue: "50px, 100%"
+}, {
+ composite: "add",
+ keyframes: ["150%, 200px", "250%, 300px"],
+ expected: "calc(200% + 50px), calc(100% + 250px)"
+}, 'Animating a custom property of type <length-percentage># with additivity');
+
+animation_test({
+ syntax: "<length-percentage>#",
+ inherits: false,
+ initialValue: "50px, 100%"
+}, {
+ composite: "add",
+ keyframes: "150%, 200px",
+ expected: "calc(75% + 50px), calc(100% + 100px)"
+}, 'Animating a custom property of type <length-percentage># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<length-percentage>#",
+ inherits: false,
+ initialValue: "0px, 0px"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0px, 50%", "100%, 100px"],
+ expected: "250%, calc(25% + 50px)"
+}, 'Animating a custom property of type <length-percentage># with iterationComposite');
+
+discrete_animation_test("<length-percentage>#", '10px, 20%', '30px', 'Animating a custom property of type <length-percentage># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-space-list.html
new file mode 100644
index 0000000000..9497093b6e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<length-percentage>+",
+ inherits: false,
+ initialValue: "0px"
+}, {
+ keyframes: ["100px 150%", "200% 250px"],
+ expected: "calc(100% + 50px) calc(75% + 125px)"
+}, 'Animating a custom property of type <length-percentage>+');
+
+animation_test({
+ syntax: "<length-percentage>+",
+ inherits: false,
+ initialValue: "100px 150%"
+}, {
+ keyframes: "200% 250px",
+ expected: "calc(100% + 50px) calc(75% + 125px)"
+}, 'Animating a custom property of type <length-percentage>+ with a single keyframe');
+
+animation_test({
+ syntax: "<length-percentage>+",
+ inherits: false,
+ initialValue: "50px 100%"
+}, {
+ composite: "add",
+ keyframes: ["150% 200px", "250% 300px"],
+ expected: "calc(200% + 50px) calc(100% + 250px)"
+}, 'Animating a custom property of type <length-percentage>+ with additivity');
+
+animation_test({
+ syntax: "<length-percentage>+",
+ inherits: false,
+ initialValue: "50px 100%"
+}, {
+ composite: "add",
+ keyframes: "150% 200px",
+ expected: "calc(75% + 50px) calc(100% + 100px)"
+}, 'Animating a custom property of type <length-percentage>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<length-percentage>+",
+ inherits: false,
+ initialValue: "0px 0px"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0px 50%", "100% 100px"],
+ expected: "250% calc(25% + 50px)"
+}, 'Animating a custom property of type <length-percentage>+ with iterationComposite');
+
+discrete_animation_test("<length-percentage>+", '10px 20%', '30px', 'Animating a custom property of type <length-percentage>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage.html
new file mode 100644
index 0000000000..096efd6ba7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-percentage.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<length-percentage>",
+ inherits: false,
+ initialValue: "0px"
+}, {
+ keyframes: ["100px", "100%"],
+ expected: "calc(50% + 50px)"
+}, 'Animating a custom property of type <length-percentage>');
+
+animation_test({
+ syntax: "<length-percentage>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ keyframes: "100%",
+ expected: "calc(50% + 50px)"
+}, 'Animating a custom property of type <length-percentage> with a single keyframe');
+
+animation_test({
+ syntax: "<length-percentage>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ composite: "add",
+ keyframes: ["200%", "300%"],
+ expected: "calc(250% + 100px)"
+}, 'Animating a custom property of type <length-percentage> with additivity');
+
+animation_test({
+ syntax: "<length-percentage>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ composite: "add",
+ keyframes: "300%",
+ expected: "calc(150% + 100px)"
+}, 'Animating a custom property of type <length-percentage> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<length-percentage>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["50px", "100%"],
+ expected: "calc(250% + 25px)"
+}, 'Animating a custom property of type <length-percentage> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-space-list.html
new file mode 100644
index 0000000000..102259a070
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<length>+",
+ inherits: false,
+ initialValue: "0px"
+}, {
+ keyframes: ["100px 150px", "200px 250px"],
+ expected: "150px 200px"
+}, 'Animating a custom property of type <length>+');
+
+animation_test({
+ syntax: "<length>+",
+ inherits: false,
+ initialValue: "100px 150px"
+}, {
+ keyframes: "200px 250px",
+ expected: "150px 200px"
+}, 'Animating a custom property of type <length>+ with a single keyframe');
+
+animation_test({
+ syntax: "<length>+",
+ inherits: false,
+ initialValue: "50px 100px"
+}, {
+ composite: "add",
+ keyframes: ["150px 200px", "250px 300px"],
+ expected: "250px 350px"
+}, 'Animating a custom property of type <length>+ with additivity');
+
+animation_test({
+ syntax: "<length>+",
+ inherits: false,
+ initialValue: "50px 100px"
+}, {
+ composite: "add",
+ keyframes: "150px 200px",
+ expected: "125px 200px"
+}, 'Animating a custom property of type <length>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<length>+",
+ inherits: false,
+ initialValue: "0px 0px"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0px 50px", "100px 100px"],
+ expected: "250px 275px"
+}, 'Animating a custom property of type <length>+ with iterationComposite');
+
+discrete_animation_test("<length>+", '10px 20px', '30px', 'Animating a custom property of type <length>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length.html
new file mode 100644
index 0000000000..8849bf52d6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-length.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "0px"
+}, {
+ keyframes: ["100px", "200px"],
+ expected: "150px"
+}, 'Animating a custom property of type <length>');
+
+animation_test({
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ keyframes: "200px",
+ expected: "150px"
+}, 'Animating a custom property of type <length> with a single keyframe');
+
+animation_test({
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ composite: "add",
+ keyframes: ["200px", "300px"],
+ expected: "350px"
+}, 'Animating a custom property of type <length> with additivity');
+
+animation_test({
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ composite: "add",
+ keyframes: "300px",
+ expected: "250px"
+}, 'Animating a custom property of type <length> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "100px"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0px", "100px"],
+ expected: "250px"
+}, 'Animating a custom property of type <length> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-list-type-mismatch.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-list-type-mismatch.html
new file mode 100644
index 0000000000..95757445ae
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-list-type-mismatch.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<number>+ | <transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ keyframes: ["200"],
+ expected: "200"
+}, 'Animating a custom property allowing multiple list types with two mismatching types');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-non-inherited-used-by-standard-property.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-non-inherited-used-by-standard-property.html
new file mode 100644
index 0000000000..11d3f6740d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-non-inherited-used-by-standard-property.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+test(() => {
+ CSS.registerProperty({
+ name: "--my-length",
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "0px"
+ });
+
+ target.style.marginLeft = "var(--my-length)";
+
+ const duration = 1000;
+ const animation = target.animate({ "--my-length": "100px" }, duration);
+ animation.pause();
+ animation.currentTime = duration / 4;
+
+ assert_equals(getComputedStyle(target).marginLeft, "25px");
+}, "Animating a non-inherited CSS variable is reflected on a standard property using that variable as a value");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-comma-list.html
new file mode 100644
index 0000000000..937b6caeeb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<number>#",
+ inherits: false,
+ initialValue: "0"
+}, {
+ keyframes: ["100, 150", "200, 250"],
+ expected: "150, 200"
+}, 'Animating a custom property of type <number>#');
+
+animation_test({
+ syntax: "<number>#",
+ inherits: false,
+ initialValue: "100, 150"
+}, {
+ keyframes: "200, 250",
+ expected: "150, 200"
+}, 'Animating a custom property of type <number># with a single keyframe');
+
+animation_test({
+ syntax: "<number>#",
+ inherits: false,
+ initialValue: "50, 100"
+}, {
+ composite: "add",
+ keyframes: ["150, 200", "250, 300"],
+ expected: "250, 350"
+}, 'Animating a custom property of type <number># with additivity');
+
+animation_test({
+ syntax: "<number>#",
+ inherits: false,
+ initialValue: "50, 100"
+}, {
+ composite: "add",
+ keyframes: "150, 200",
+ expected: "125, 200"
+}, 'Animating a custom property of type <number># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<number>#",
+ inherits: false,
+ initialValue: "0, 0"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0, 50", "100, 100"],
+ expected: "250, 275"
+}, 'Animating a custom property of type <number># with iterationComposite');
+
+discrete_animation_test("<number>#", '10, 20', '30', 'Animating a custom property of type <number># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-space-list.html
new file mode 100644
index 0000000000..61f177eb05
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<number>+",
+ inherits: false,
+ initialValue: "0"
+}, {
+ keyframes: ["100 150", "200 250"],
+ expected: "150 200"
+}, 'Animating a custom property of type <number>+');
+
+animation_test({
+ syntax: "<number>+",
+ inherits: false,
+ initialValue: "100 150"
+}, {
+ keyframes: "200 250",
+ expected: "150 200"
+}, 'Animating a custom property of type <number>+ with a single keyframe');
+
+animation_test({
+ syntax: "<number>+",
+ inherits: false,
+ initialValue: "50 100"
+}, {
+ composite: "add",
+ keyframes: ["150 200", "250 300"],
+ expected: "250 350"
+}, 'Animating a custom property of type <number>+ with additivity');
+
+animation_test({
+ syntax: "<number>+",
+ inherits: false,
+ initialValue: "50 100"
+}, {
+ composite: "add",
+ keyframes: "150 200",
+ expected: "125 200"
+}, 'Animating a custom property of type <number>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<number>+",
+ inherits: false,
+ initialValue: "0 0"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0 50", "100 100"],
+ expected: "250 275"
+}, 'Animating a custom property of type <number>+ with iterationComposite');
+
+discrete_animation_test("<number>+", '10 20', '30', 'Animating a custom property of type <number>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number.html
new file mode 100644
index 0000000000..ea28acacf6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-number.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<number>",
+ inherits: false,
+ initialValue: 0
+}, {
+ keyframes: [100, 200],
+ expected: "150"
+}, 'Animating a custom property of type <number>');
+
+animation_test({
+ syntax: "<number>",
+ inherits: false,
+ initialValue: 100
+}, {
+ keyframes: 200,
+ expected: "150"
+}, 'Animating a custom property of type <number> with a single keyframe');
+
+animation_test({
+ syntax: "<number>",
+ inherits: false,
+ initialValue: 100
+}, {
+ composite: "add",
+ keyframes: [200, 300],
+ expected: "350"
+}, 'Animating a custom property of type <number> with additivity');
+
+animation_test({
+ syntax: "<number>",
+ inherits: false,
+ initialValue: 100
+}, {
+ composite: "add",
+ keyframes: 300,
+ expected: "250"
+}, 'Animating a custom property of type <number> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<number>",
+ inherits: false,
+ initialValue: 100
+}, {
+ iterationComposite: "accumulate",
+ keyframes: [0, 100],
+ expected: "250"
+}, 'Animating a custom property of type <number> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-comma-list.html
new file mode 100644
index 0000000000..c690046646
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<percentage>#",
+ inherits: false,
+ initialValue: "0%"
+}, {
+ keyframes: ["100%, 150%", "200%, 250%"],
+ expected: "150%, 200%"
+}, 'Animating a custom property of type <percentage>#');
+
+animation_test({
+ syntax: "<percentage>#",
+ inherits: false,
+ initialValue: "100%, 150%"
+}, {
+ keyframes: "200%, 250%",
+ expected: "150%, 200%"
+}, 'Animating a custom property of type <percentage># with a single keyframe');
+
+animation_test({
+ syntax: "<percentage>#",
+ inherits: false,
+ initialValue: "50%, 100%"
+}, {
+ composite: "add",
+ keyframes: ["150%, 200%", "250%, 300%"],
+ expected: "250%, 350%"
+}, 'Animating a custom property of type <percentage># with additivity');
+
+animation_test({
+ syntax: "<percentage>#",
+ inherits: false,
+ initialValue: "50%, 100%"
+}, {
+ composite: "add",
+ keyframes: "150%, 200%",
+ expected: "125%, 200%"
+}, 'Animating a custom property of type <percentage># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<percentage>#",
+ inherits: false,
+ initialValue: "0%, 0%"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0%, 50%", "100%, 100%"],
+ expected: "250%, 275%"
+}, 'Animating a custom property of type <percentage># with iterationComposite');
+
+discrete_animation_test("<percentage>#", '10%, 20%', '30%', 'Animating a custom property of type <percentage># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-space-list.html
new file mode 100644
index 0000000000..d012b851fd
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<percentage>+",
+ inherits: false,
+ initialValue: "0%"
+}, {
+ keyframes: ["100% 150%", "200% 250%"],
+ expected: "150% 200%"
+}, 'Animating a custom property of type <percentage>+');
+
+animation_test({
+ syntax: "<percentage>+",
+ inherits: false,
+ initialValue: "100% 150%"
+}, {
+ keyframes: "200% 250%",
+ expected: "150% 200%"
+}, 'Animating a custom property of type <percentage>+ with a single keyframe');
+
+animation_test({
+ syntax: "<percentage>+",
+ inherits: false,
+ initialValue: "50% 100%"
+}, {
+ composite: "add",
+ keyframes: ["150% 200%", "250% 300%"],
+ expected: "250% 350%"
+}, 'Animating a custom property of type <percentage>+ with additivity');
+
+animation_test({
+ syntax: "<percentage>+",
+ inherits: false,
+ initialValue: "50% 100%"
+}, {
+ composite: "add",
+ keyframes: "150% 200%",
+ expected: "125% 200%"
+}, 'Animating a custom property of type <percentage>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<percentage>+",
+ inherits: false,
+ initialValue: "0% 0%"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0% 50%", "100% 100%"],
+ expected: "250% 275%"
+}, 'Animating a custom property of type <percentage>+ with iterationComposite');
+
+discrete_animation_test("<percentage>+", '10% 20%', '30%', 'Animating a custom property of type <percentage>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage.html
new file mode 100644
index 0000000000..6af498e702
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-percentage.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<percentage>",
+ inherits: false,
+ initialValue: "0%"
+}, {
+ keyframes: ["100%", "200%"],
+ expected: "150%"
+}, 'Animating a custom property of type <percentage>');
+
+animation_test({
+ syntax: "<percentage>",
+ inherits: false,
+ initialValue: "100%"
+}, {
+ keyframes: "200%",
+ expected: "150%"
+}, 'Animating a custom property of type <percentage> with a single keyframe');
+
+animation_test({
+ syntax: "<percentage>",
+ inherits: false,
+ initialValue: "100%"
+}, {
+ composite: "add",
+ keyframes: ["200%", "300%"],
+ expected: "350%"
+}, 'Animating a custom property of type <percentage> with additivity');
+
+animation_test({
+ syntax: "<percentage>",
+ inherits: false,
+ initialValue: "100%"
+}, {
+ composite: "add",
+ keyframes: "300%",
+ expected: "250%"
+}, 'Animating a custom property of type <percentage> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<percentage>",
+ inherits: false,
+ initialValue: "100%"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0%", "100%"],
+ expected: "250%"
+}, 'Animating a custom property of type <percentage> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-comma-list.html
new file mode 100644
index 0000000000..22201947ff
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<resolution>#",
+ inherits: false,
+ initialValue: "0dppx"
+}, {
+ keyframes: ["100dppx, 150dppx", "200dppx, 250dppx"],
+ expected: "150dppx, 200dppx"
+}, 'Animating a custom property of type <resolution>#');
+
+animation_test({
+ syntax: "<resolution>#",
+ inherits: false,
+ initialValue: "100dppx, 150dppx"
+}, {
+ keyframes: "200dppx, 250dppx",
+ expected: "150dppx, 200dppx"
+}, 'Animating a custom property of type <resolution># with a single keyframe');
+
+animation_test({
+ syntax: "<resolution>#",
+ inherits: false,
+ initialValue: "50dppx, 100dppx"
+}, {
+ composite: "add",
+ keyframes: ["150dppx, 200dppx", "250dppx, 300dppx"],
+ expected: "250dppx, 350dppx"
+}, 'Animating a custom property of type <resolution># with additivity');
+
+animation_test({
+ syntax: "<resolution>#",
+ inherits: false,
+ initialValue: "50dppx, 100dppx"
+}, {
+ composite: "add",
+ keyframes: "150dppx, 200dppx",
+ expected: "125dppx, 200dppx"
+}, 'Animating a custom property of type <resolution># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<resolution>#",
+ inherits: false,
+ initialValue: "0dppx, 0dppx"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0dppx, 50dppx", "100dppx, 100dppx"],
+ expected: "250dppx, 275dppx"
+}, 'Animating a custom property of type <resolution># with iterationComposite');
+
+discrete_animation_test("<resolution>#", '10dppx, 20dppx', '30dppx', 'Animating a custom property of type <resolution># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-space-list.html
new file mode 100644
index 0000000000..d4765d4e32
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<resolution>+",
+ inherits: false,
+ initialValue: "0dppx"
+}, {
+ keyframes: ["100dppx 150dppx", "200dppx 250dppx"],
+ expected: "150dppx 200dppx"
+}, 'Animating a custom property of type <resolution>+');
+
+animation_test({
+ syntax: "<resolution>+",
+ inherits: false,
+ initialValue: "100dppx 150dppx"
+}, {
+ keyframes: "200dppx 250dppx",
+ expected: "150dppx 200dppx"
+}, 'Animating a custom property of type <resolution>+ with a single keyframe');
+
+animation_test({
+ syntax: "<resolution>+",
+ inherits: false,
+ initialValue: "50dppx 100dppx"
+}, {
+ composite: "add",
+ keyframes: ["150dppx 200dppx", "250dppx 300dppx"],
+ expected: "250dppx 350dppx"
+}, 'Animating a custom property of type <resolution>+ with additivity');
+
+animation_test({
+ syntax: "<resolution>+",
+ inherits: false,
+ initialValue: "50dppx 100dppx"
+}, {
+ composite: "add",
+ keyframes: "150dppx 200dppx",
+ expected: "125dppx 200dppx"
+}, 'Animating a custom property of type <resolution>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<resolution>+",
+ inherits: false,
+ initialValue: "0dppx 0dppx"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0dppx 50dppx", "100dppx 100dppx"],
+ expected: "250dppx 275dppx"
+}, 'Animating a custom property of type <resolution>+ with iterationComposite');
+
+discrete_animation_test("<resolution>+", '10dppx 20dppx', '30dppx', 'Animating a custom property of type <resolution>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution.html
new file mode 100644
index 0000000000..3d05139edc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-resolution.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<resolution>",
+ inherits: false,
+ initialValue: "0dppx"
+}, {
+ keyframes: ["100dppx", "200dppx"],
+ expected: "150dppx"
+}, 'Animating a custom property of type <resolution>');
+
+animation_test({
+ syntax: "<resolution>",
+ inherits: false,
+ initialValue: "100dppx"
+}, {
+ keyframes: "200dppx",
+ expected: "150dppx"
+}, 'Animating a custom property of type <resolution> with a single keyframe');
+
+animation_test({
+ syntax: "<resolution>",
+ inherits: false,
+ initialValue: "100dppx"
+}, {
+ composite: "add",
+ keyframes: ["200dppx", "300dppx"],
+ expected: "350dppx"
+}, 'Animating a custom property of type <resolution> with additivity');
+
+animation_test({
+ syntax: "<resolution>",
+ inherits: false,
+ initialValue: "100dppx"
+}, {
+ composite: "add",
+ keyframes: "300dppx",
+ expected: "250dppx"
+}, 'Animating a custom property of type <resolution> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<resolution>",
+ inherits: false,
+ initialValue: "100dppx"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0dppx", "100dppx"],
+ expected: "250dppx"
+}, 'Animating a custom property of type <resolution> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-comma-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-comma-list.html
new file mode 100644
index 0000000000..bf07baf130
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-comma-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<time>#",
+ inherits: false,
+ initialValue: "0s"
+}, {
+ keyframes: ["100s, 150s", "200s, 250s"],
+ expected: "150s, 200s"
+}, 'Animating a custom property of type <time>#');
+
+animation_test({
+ syntax: "<time>#",
+ inherits: false,
+ initialValue: "100s, 150s"
+}, {
+ keyframes: "200s, 250s",
+ expected: "150s, 200s"
+}, 'Animating a custom property of type <time># with a single keyframe');
+
+animation_test({
+ syntax: "<time>#",
+ inherits: false,
+ initialValue: "50s, 100s"
+}, {
+ composite: "add",
+ keyframes: ["150s, 200s", "250s, 300s"],
+ expected: "250s, 350s"
+}, 'Animating a custom property of type <time># with additivity');
+
+animation_test({
+ syntax: "<time>#",
+ inherits: false,
+ initialValue: "50s, 100s"
+}, {
+ composite: "add",
+ keyframes: "150s, 200s",
+ expected: "125s, 200s"
+}, 'Animating a custom property of type <time># with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<time>#",
+ inherits: false,
+ initialValue: "0s, 0s"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0s, 50s", "100s, 100s"],
+ expected: "250s, 275s"
+}, 'Animating a custom property of type <time># with iterationComposite');
+
+discrete_animation_test("<time>#", '10s, 20s', '30s', 'Animating a custom property of type <time># with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-space-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-space-list.html
new file mode 100644
index 0000000000..c0fe206ea7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time-space-list.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<time>+",
+ inherits: false,
+ initialValue: "0s"
+}, {
+ keyframes: ["100s 150s", "200s 250s"],
+ expected: "150s 200s"
+}, 'Animating a custom property of type <time>+');
+
+animation_test({
+ syntax: "<time>+",
+ inherits: false,
+ initialValue: "100s 150s"
+}, {
+ keyframes: "200s 250s",
+ expected: "150s 200s"
+}, 'Animating a custom property of type <time>+ with a single keyframe');
+
+animation_test({
+ syntax: "<time>+",
+ inherits: false,
+ initialValue: "50s 100s"
+}, {
+ composite: "add",
+ keyframes: ["150s 200s", "250s 300s"],
+ expected: "250s 350s"
+}, 'Animating a custom property of type <time>+ with additivity');
+
+animation_test({
+ syntax: "<time>+",
+ inherits: false,
+ initialValue: "50s 100s"
+}, {
+ composite: "add",
+ keyframes: "150s 200s",
+ expected: "125s 200s"
+}, 'Animating a custom property of type <time>+ with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<time>+",
+ inherits: false,
+ initialValue: "0s 0s"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0s 50s", "100s 100s"],
+ expected: "250s 275s"
+}, 'Animating a custom property of type <time>+ with iterationComposite');
+
+discrete_animation_test("<time>+", '10s 20s', '30s', 'Animating a custom property of type <time>+ with different lengths is discrete');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time.html
new file mode 100644
index 0000000000..b9f5984db7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-time.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<time>",
+ inherits: false,
+ initialValue: "0s"
+}, {
+ keyframes: ["100s", "200s"],
+ expected: "150s"
+}, 'Animating a custom property of type <time>');
+
+animation_test({
+ syntax: "<time>",
+ inherits: false,
+ initialValue: "100s"
+}, {
+ keyframes: "200s",
+ expected: "150s"
+}, 'Animating a custom property of type <time> with a single keyframe');
+
+animation_test({
+ syntax: "<time>",
+ inherits: false,
+ initialValue: "100s"
+}, {
+ composite: "add",
+ keyframes: ["200s", "300s"],
+ expected: "350s"
+}, 'Animating a custom property of type <time> with additivity');
+
+animation_test({
+ syntax: "<time>",
+ inherits: false,
+ initialValue: "100s"
+}, {
+ composite: "add",
+ keyframes: "300s",
+ expected: "250s"
+}, 'Animating a custom property of type <time> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<time>",
+ inherits: false,
+ initialValue: "100s"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["0s", "100s"],
+ expected: "250s"
+}, 'Animating a custom property of type <time> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-function.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-function.html
new file mode 100644
index 0000000000..c4fcd5ce4c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-function.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<transform-function>",
+ inherits: false,
+ initialValue: "translateX(0px)"
+}, {
+ keyframes: ["translateX(100px)", "translateX(200px)"],
+ expected: "translateX(150px)"
+}, 'Animating a custom property of type <transform-function>');
+
+animation_test({
+ syntax: "<transform-function>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ keyframes: "translateX(200px)",
+ expected: "translateX(150px)"
+}, 'Animating a custom property of type <transform-function> with a single keyframe');
+
+animation_test({
+ syntax: "<transform-function>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ composite: "add",
+ keyframes: ["translateX(200px)", "translateX(300px)"],
+ expected: "translateX(350px)"
+}, 'Animating a custom property of type <transform-function> with additivity');
+
+animation_test({
+ syntax: "<transform-function>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ composite: "add",
+ keyframes: "translateX(300px)",
+ expected: "translateX(250px)"
+}, 'Animating a custom property of type <transform-function> with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<transform-function>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["translateX(0px)", "translateX(100px)"],
+ expected: "translateX(250px)"
+}, 'Animating a custom property of type <transform-function> with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-multiple-values.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-multiple-values.html
new file mode 100644
index 0000000000..1b2fc907f2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-multiple-values.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(0px)"
+}, {
+ keyframes: ["translateX(100px) scale(2)", "translateX(200px) scale(4)"],
+ expected: "translateX(150px) scale(3)"
+}, 'Animating a custom property of type <transform-list> containing multiple values');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px) scale(2)"
+}, {
+ keyframes: "translateX(200px) scale(4)",
+ expected: "translateX(150px) scale(3)"
+}, 'Animating a custom property of type <transform-list> containing multiple values with a single keyframe');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px) scale(2)"
+}, {
+ composite: "add",
+ keyframes: ["translateX(200px) scale(3)", "translateX(300px) scale(4)"],
+ expected: "translateX(100px) scale(2) translateX(250px) scale(3.5)"
+}, 'Animating a custom property of type <transform-list> containing multiple values with additivity');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px) scale(2)"
+}, {
+ composite: "add",
+ keyframes: "translateX(300px) scale(3)",
+ expected: "translateX(100px) scale(2) translateX(150px) scale(2)"
+}, 'Animating a custom property of type <transform-list> containing multiple values with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px) scale(2)"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["translateX(0px) scale(3)", "translateX(100px) scale(4)"],
+ expected: "translateX(250px) scale(11.5)"
+}, 'Animating a custom property of type <transform-list> containing multiple values with iterationComposite');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(0px)"
+}, {
+ keyframes: ["translateX(100px) scale(2)", "rotate(180deg)"],
+ expected: "matrix(0, -1.5, 1.5, 0, 50, 0)",
+ assert_function: assert_matrix_equals
+}, 'Animating a custom property of type <transform-list> containing multiple values and with mismatching list lengths');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-single-values.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-single-values.html
new file mode 100644
index 0000000000..b9bce6dd0b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-list-single-values.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(0px)"
+}, {
+ keyframes: ["translateX(100px)", "translateX(200px)"],
+ expected: "translateX(150px)"
+}, 'Animating a custom property of type <transform-list> containing a single value');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ keyframes: "translateX(200px)",
+ expected: "translateX(150px)"
+}, 'Animating a custom property of type <transform-list> containing a single value with a single keyframe');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ composite: "add",
+ keyframes: ["translateX(200px)", "translateX(300px)"],
+ expected: "translateX(100px) translateX(250px)"
+}, 'Animating a custom property of type <transform-list> containing a single value with additivity');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ composite: "add",
+ keyframes: "translateX(300px)",
+ expected: "translateX(100px) translateX(150px)"
+}, 'Animating a custom property of type <transform-list> containing a single value with a single keyframe and additivity');
+
+animation_test({
+ syntax: "<transform-list>",
+ inherits: false,
+ initialValue: "translateX(100px)"
+}, {
+ iterationComposite: "accumulate",
+ keyframes: ["translateX(0px)", "translateX(100px)"],
+ expected: "translateX(250px)"
+}, 'Animating a custom property of type <transform-list> containing a single value with iterationComposite');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-none.tentative.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-none.tentative.html
new file mode 100644
index 0000000000..37abddd227
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-transform-none.tentative.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9522">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+// Move to custom-property-animation-transform-list-single-values.html when no longer tentative
+animation_test({
+ syntax: "<transform-list>|none",
+ inherits: false,
+ initialValue: "none"
+}, {
+ keyframes: ["translateX(200px)"],
+ expected: "translateX(200px)"
+}, 'Animating a custom property of type "<transform-list>|none" from "none" to <transform-list> value');
+
+// Move to custom-property-animation-transform-function.html when no longer tentative
+animation_test({
+ syntax: "<transform-function>|none",
+ inherits: false,
+ initialValue: "none"
+}, {
+ keyframes: ["translateX(200px)"],
+ expected: "translateX(200px)"
+}, 'Animating a custom property of type "<transform-function>|none" from "none" to <transform-function> value');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-url.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-url.html
new file mode 100644
index 0000000000..830b9e1f49
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-url.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+discrete_animation_test("<url>", 'url("https://example.com/from")', 'url("https://example.com/to")');
+discrete_animation_test("<url>+", 'url("https://example.com/from1") url("https://example.com/from2")', 'url("https://example.com/to1") url("https://example.com/to2")');
+discrete_animation_test("<url>#", 'url("https://example.com/from1"), url("https://example.com/from2")', 'url("https://example.com/to1"), url("https://example.com/to2")');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-used-in-shorthand.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-used-in-shorthand.html
new file mode 100644
index 0000000000..63f7fd3fe7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-animation-used-in-shorthand.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+
+@property --angle {
+ syntax: "<angle>";
+ inherits: false;
+ initial-value: 0deg;
+}
+
+@keyframes angle-animation {
+ from { --angle: 0deg }
+ to { --angle: 180deg }
+}
+
+#target {
+ animation: angle-animation 1000s linear -500s;
+ background: linear-gradient(var(--angle), black, white);
+}
+
+</style>
+
+<div id="target"></div>
+
+<script>
+
+test(() => {
+ assert_equals(getComputedStyle(target).backgroundImage, 'linear-gradient(90deg, rgb(0, 0, 0), rgb(255, 255, 255))')
+}, "Animated custom property is applied in a shorthand property.");
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-angle.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-angle.html
new file mode 100644
index 0000000000..974fc6c0db
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-angle.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<angle>",
+ from: "100deg",
+ to: "200deg",
+ expected: "150deg"
+}, 'A custom property of type <angle> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-color.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-color.html
new file mode 100644
index 0000000000..445a2a6391
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-color.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<color>",
+ from: "rgb(100, 100, 100)",
+ to: "rgb(200, 200, 200)",
+ expected: "rgb(150, 150, 150)"
+}, 'A custom property of type <color> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-custom-ident.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-custom-ident.html
new file mode 100644
index 0000000000..b7ca1ec785
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-custom-ident.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<custom-ident>",
+ from: "from",
+ to: "to",
+ expected: "to",
+ behavior: "allow-discrete",
+}, 'A custom property of type <custom-ident> can yield a discrete CSS Transition');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-image.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-image.html
new file mode 100644
index 0000000000..4fd3fe649b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-image.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<image>",
+ from: 'url("https://example.com/from")',
+ to: 'url("https://example.com/to")',
+ expected: 'url("https://example.com/to")',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <image> can yield a CSS Transition');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-inherited-used-by-standard-property.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-inherited-used-by-standard-property.html
new file mode 100644
index 0000000000..0680722d4a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-inherited-used-by-standard-property.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="container">
+ <div id="target"></div>
+</div>
+<script>
+
+test(() => {
+ const customProperty = "--my-length";
+
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: "<length>",
+ inherits: true,
+ initialValue: "100px"
+ });
+
+ target.style.marginLeft = `var(${customProperty})`;
+ assert_equals(getComputedStyle(target).marginLeft, "100px");
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), "100px");
+
+ container.style.transition = `${customProperty} 1s -500ms linear`;
+ container.style.setProperty(customProperty, "200px");
+
+ assert_equals(getComputedStyle(target).marginLeft, "150px");
+}, "Running a transition an inherited CSS variable is reflected on a standard property using that variable as a value");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-integer.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-integer.html
new file mode 100644
index 0000000000..64685fe07c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-integer.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<integer>",
+ from: "100",
+ to: "200",
+ expected: "150"
+}, 'A custom property of type <integer> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length-percentage.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length-percentage.html
new file mode 100644
index 0000000000..f1ed1dec26
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length-percentage.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<length-percentage>",
+ from: "100px",
+ to: "100%",
+ expected: "calc(50% + 50px)"
+}, 'A custom property of type <length-percentage> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length.html
new file mode 100644
index 0000000000..f5a76490cc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-length.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<length>",
+ from: "100px",
+ to: "200px",
+ expected: "150px"
+}, 'A custom property of type <length> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-inherited-property-numbers.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-inherited-property-numbers.html
new file mode 100644
index 0000000000..292e23b1fe
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-inherited-property-numbers.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="container"><div id="target"></div></div>
+<script>
+
+test(() => {
+ const customProperty = generate_name();
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: "<number>",
+ inherits: false,
+ initialValue: "1"
+ });
+
+ // Create transitions for our custom property with
+ // a longer list of transition-duration values.
+ const container = document.getElementById("container");
+ container.style.transitionProperty = customProperty;
+ container.style.transitionDuration = "100s, 200s";
+
+ const target = document.getElementById("target");
+ target.style.transitionProperty = "inherit";
+ target.style.transitionDuration = "inherit";
+
+ // Trigger a style change by getting the custom property
+ // value from the computed style.
+ getComputedStyle(target).getPropertyValue(customProperty);
+
+ // Set a new value for the custom property, which will yield a
+ // transition.
+ target.style.setProperty(customProperty, "2");
+ const animations = target.getAnimations();
+ assert_equals(animations.length, 1, "A single transition was generated");
+
+ const transition = animations[0];
+ assert_class_string(transition, "CSSTransition", "A CSSTransition is running");
+ assert_equals(transition.transitionProperty, customProperty);
+}, 'Using a single "transition-property" value set to a custom property and two "transition-duration" values correctly yields a CSS Transition when the transition properties are set on a parent and the child inherits.');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-list.html
new file mode 100644
index 0000000000..b7d964b92c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-list.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<angle>#",
+ from: '100deg, 200deg',
+ to: '300deg',
+ expected: '300deg',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <angle># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<angle>+",
+ from: '100deg 200deg',
+ to: '300deg',
+ expected: '300deg',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <angle>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<color>#",
+ from: 'rgb(100, 100, 100), rgb(150, 150, 150)',
+ to: 'rgb(200, 200, 200)',
+ expected: 'rgb(200, 200, 200)',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <color># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<color>+",
+ from: 'rgb(100, 100, 100) rgb(150, 150, 150)',
+ to: 'rgb(200, 200, 200)',
+ expected: 'rgb(200, 200, 200)',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <color>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<custom-ident>#",
+ from: 'foo, bar',
+ to: 'baz',
+ expected: 'baz',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <custom-ident># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<custom-ident>+",
+ from: 'foo bar',
+ to: 'baz',
+ expected: 'baz',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <custom-ident>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<image>#",
+ from: 'url("https://example.com/foo"), url("https://example.com/bar")',
+ to: 'url("https://example.com/to")',
+ expected: 'url("https://example.com/to")',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <image># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<image>+",
+ from: 'url("https://example.com/foo") url("https://example.com/bar")',
+ to: 'url("https://example.com/to")',
+ expected: 'url("https://example.com/to")',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <image>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<integer>#",
+ from: '100, 200',
+ to: '300',
+ expected: '300',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <integer># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<integer>+",
+ from: '100 200',
+ to: '300',
+ expected: '300',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <integer>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<length-percentage>#",
+ from: '100px, 200px',
+ to: '300%',
+ expected: '300%',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <length-percentage># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<length-percentage>+",
+ from: '100px 200px',
+ to: '300%',
+ expected: '300%',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <length-percentage>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<length>#",
+ from: '100px, 200px',
+ to: '300px',
+ expected: '300px',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <length># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<length>+",
+ from: '100px 200px',
+ to: '300px',
+ expected: '300px',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <length>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<number>#",
+ from: '100, 200',
+ to: '300',
+ expected: '300',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <number># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<number>+",
+ from: '100 200',
+ to: '300',
+ expected: '300',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <number>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<percentage>#",
+ from: '100%, 200%',
+ to: '300%',
+ expected: '300%',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <percentage># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<percentage>+",
+ from: '100% 200%',
+ to: '300%',
+ expected: '300%',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <percentage>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<resolution>#",
+ from: '100dppx, 200dppx',
+ to: '300dppx',
+ expected: '300dppx',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <resolution># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<resolution>+",
+ from: '100dppx 200dppx',
+ to: '300dppx',
+ expected: '300dppx',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <resolution>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<time>#",
+ from: '100s, 200s',
+ to: '300s',
+ expected: '300s',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <time># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<time>+",
+ from: '100s 200s',
+ to: '300s',
+ expected: '300s',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <time>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<url>#",
+ from: 'url("https://example.com/foo"), url("https://example.com/bar")',
+ to: 'url("https://example.com/to")',
+ expected: 'url("https://example.com/to")',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <url># yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+transition_test({
+ syntax: "<url>+",
+ from: 'url("https://example.com/foo") url("https://example.com/bar")',
+ to: 'url("https://example.com/to")',
+ expected: 'url("https://example.com/to")',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <url>+ yields a discrete CSS Transition if the lists do not contain the same number of values');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-property-numbers.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-property-numbers.html
new file mode 100644
index 0000000000..713a035320
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-mismatched-property-numbers.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+test(() => {
+ const customProperty = generate_name();
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: "<number>",
+ inherits: false,
+ initialValue: "1"
+ });
+
+ // Create transitions for our custom property with
+ // a longer list of transition-duration values.
+ const target = document.getElementById("target");
+ target.style.transitionProperty = customProperty;
+ target.style.transitionDuration = "100s, 200s";
+
+ // Trigger a style change by getting the custom property
+ // value from the computed style.
+ getComputedStyle(target).getPropertyValue(customProperty);
+
+ // Set a new value for the custom property, which will yield a
+ // transition.
+ target.style.setProperty(customProperty, "2");
+ const animations = target.getAnimations();
+ assert_equals(animations.length, 1, "A single transition was generated");
+
+ const transition = animations[0];
+ assert_class_string(transition, "CSSTransition", "A CSSTransition is running");
+ assert_equals(transition.transitionProperty, customProperty);
+}, 'Using a single "transition-property" value set to a custom property and two "transition-duration" values correctly yields a CSS Transition.');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-non-inherited-used-by-standard-property.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-non-inherited-used-by-standard-property.html
new file mode 100644
index 0000000000..b96c28bc88
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-non-inherited-used-by-standard-property.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+test(() => {
+ const customProperty = "--my-length";
+
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: "<length>",
+ inherits: false,
+ initialValue: "100px"
+ });
+
+ target.style.marginLeft = `var(${customProperty})`;
+ assert_equals(getComputedStyle(target).marginLeft, "100px");
+
+ target.style.transition = `${customProperty} 1s -500ms linear`;
+ target.style.setProperty(customProperty, "200px");
+
+ assert_equals(getComputedStyle(target).marginLeft, "150px");
+}, "Running a transition a non-inherited CSS variable is reflected on a standard property using that variable as a value");
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-number.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-number.html
new file mode 100644
index 0000000000..a96e319686
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-number.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<number>",
+ from: "100",
+ to: "200",
+ expected: "150"
+}, 'A custom property of type <number> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-percentage.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-percentage.html
new file mode 100644
index 0000000000..71d51b0e27
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-percentage.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<percentage>",
+ from: "100%",
+ to: "200%",
+ expected: "150%"
+}, 'A custom property of type <percentage> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-property-all.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-property-all.html
new file mode 100644
index 0000000000..6fc724cd35
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-property-all.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<length>",
+ from: "100px",
+ to: "200px",
+ expected: "150px",
+ transitionProperty: "all"
+}, 'A custom property can yield a CSS Transition with transition-property: all');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-resolution.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-resolution.html
new file mode 100644
index 0000000000..5631910b7c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-resolution.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<resolution>",
+ from: "100dppx",
+ to: "200dppx",
+ expected: "150dppx"
+}, 'A custom property of type <resolution> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-time.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-time.html
new file mode 100644
index 0000000000..eb579b71f1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-time.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<time>",
+ from: "100s",
+ to: "200s",
+ expected: "150s"
+}, 'A custom property of type <time> can yield a CSS Transition');
+
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-box-size.tentative.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-box-size.tentative.html
new file mode 100644
index 0000000000..23ac09b1fc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-box-size.tentative.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#interpolate">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/2854">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-function>",
+ from: "translateX(50%)",
+ to: "scale(4)",
+ expected: "mix(50%; translateX(50%); scale(4))",
+ behavior: 'allow-discrete',
+}, 'A custom property of type <transform-function> yields a CSS Transition using the mix function for a box size dependent matrix interpolation');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-matrix.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-matrix.html
new file mode 100644
index 0000000000..00b5018142
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-matrix.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-function>",
+ from: "translateZ(100px)",
+ to: "scale(4)",
+ expected: "matrix3d(2.5, 0, 0, 0, 0, 2.5, 0, 0, 0, 0, 1, 0, 0, 0, 50, 1)",
+ behavior: 'allow-discrete',
+}, 'A custom property of type <transform-function> can yield a CSS Transition between different function types');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-none.tentative.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-none.tentative.html
new file mode 100644
index 0000000000..29a5715fec
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-none.tentative.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9522">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-function>|none",
+ from: "none",
+ to: "translateX(200px)",
+ expected: "translateX(200px)",
+ behavior: 'allow-discrete',
+}, 'A custom property keyword none is not a <transform-function> value and is not interpolable with <transform-function> values');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-to-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-to-list.html
new file mode 100644
index 0000000000..d09fb6416a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function-to-list.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-function>|<transform-list>",
+ from: "translateX(100px)",
+ to: "translateX(200px) rotate(90deg)",
+ expected: "translateX(200px) rotate(90deg)",
+ behavior: 'allow-discrete',
+}, 'A custom property cannot yield a CSS Transition from <transform-function> to <transform-list>');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function.html
new file mode 100644
index 0000000000..17ad1067a4
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-function.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-function>",
+ from: "translateX(100px)",
+ to: "translateX(200px)",
+ expected: "translateX(150px)",
+ behavior: 'allow-discrete',
+}, 'A custom property of type <transform-function> can yield a CSS Transition');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-box-size.tentative.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-box-size.tentative.html
new file mode 100644
index 0000000000..d1b882f708
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-box-size.tentative.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#interpolate">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/2854">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-list>",
+ from: "translateX(50%)",
+ to: "scale(4)",
+ expected: "mix(50%; translateX(50%); scale(4))",
+ behavior: 'allow-discrete',
+}, 'A custom property of type <transform-list> yields a CSS Transition using the mix function for a box size dependent matrix interpolation');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-matrix.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-matrix.html
new file mode 100644
index 0000000000..8f1fa84294
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-matrix.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-list>",
+ from: "translateZ(100px)",
+ to: "scale(4)",
+ expected: "matrix3d(2.5, 0, 0, 0, 0, 2.5, 0, 0, 0, 0, 1, 0, 0, 0, 50, 1)",
+ behavior: 'allow-discrete',
+}, 'A custom property of type <transform-list> can yield a CSS Transition between different function types');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-none.tentative.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-none.tentative.html
new file mode 100644
index 0000000000..d09f5854b3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list-none.tentative.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9522">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-list>|none",
+ from: "none",
+ to: "translateX(200px)",
+ expected: "translateX(200px)",
+ behavior: 'allow-discrete',
+}, 'A custom property keyword none is not a <transform-list> value and is not interpolable with <transform-list> values');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list.html
new file mode 100644
index 0000000000..c9d3cb94d1
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-transform-list.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<transform-list>",
+ from: "translateX(100px) scale(2)",
+ to: "translateX(200px) scale(4)",
+ expected: "translateX(150px) scale(3)",
+ behavior: 'allow-discrete',
+}, 'A custom property of type <transform-list> can yield a CSS Transition');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-url.html b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-url.html
new file mode 100644
index 0000000000..c5d438e549
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/animation/custom-property-transition-url.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="target"></div>
+<script>
+
+transition_test({
+ syntax: "<url>",
+ from: 'url("https://example.com/from")',
+ to: 'url("https://example.com/to")',
+ expected: 'url("https://example.com/to")',
+ behavior: 'allow-discrete',
+}, 'A custom property of type <url> can yield a discrete CSS Transition');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-animation.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-animation.html
new file mode 100644
index 0000000000..233b63239f
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-animation.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=outer>
+ <div id=div></div>
+</div>
+<script>
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ with_style_node(`
+ @keyframes test {
+ from { ${name}: 100px; }
+ to { ${name}: 200px; }
+ }
+ #div { animation: test 100s -50s linear; }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
+ });
+}, '@keyframes works with @property');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ with_style_node(`
+ @property ${name} {
+ syntax: "<color>";
+ inherits: false;
+ initial-value: black;
+ }
+ @keyframes test {
+ from { ${name}: rgb(100, 100, 100); }
+ to { ${name}: rgb(200, 200, 200); }
+ }
+ #div { animation: test 100s -50s linear; }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(150, 150, 150)');
+ });
+}, '@keyframes picks up the latest @property in the document');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ // These keyframes are initially invalid for the declared custom property.
+ let animation = div.animate([
+ { [name]: 'rgb(100, 100, 100)'},
+ { [name]: 'rgb(200, 200, 200)'},
+ ], { duration: 10000, delay: -5000, easing: 'linear' });
+ let cs = getComputedStyle(div);
+ assert_equals(cs.getPropertyValue(name), '0px');
+
+ // Redeclare the property as a <color>, effectively making the existing
+ // keyframes valid.
+ with_at_property({
+ name: name,
+ syntax: '"<color>"',
+ inherits: false,
+ initialValue: 'black'
+ }, (name) => {
+ assert_equals(cs.getPropertyValue(name), 'rgb(150, 150, 150)');
+ });
+
+ animation.finish();
+}, 'Ongoing animation picks up redeclared custom property');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ // These keyframes are initially invalid for the declared custom property.
+ let animation = div.animate([
+ { [name]: 'rgb(100, 100, 100)'},
+ { [name]: 'rgb(200, 200, 200)'},
+ ], { duration: 10000, delay: -5000, easing: 'linear' });
+ let cs = getComputedStyle(div);
+ assert_equals(cs.getPropertyValue(name), '0px');
+
+ // Setting the keyframes to something that matches <length> makes the
+ // interpolation valid.
+ animation.effect.setKeyframes([
+ {[name]: '100px'},
+ {[name]: '200px'}
+ ]);
+ assert_equals(cs.getPropertyValue(name), '150px');
+
+ animation.finish();
+}, 'Ongoing animation matches new keyframes against the current registration');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ let animation = div.animate([
+ { [name]: 'initial'},
+ { [name]: '400px'},
+ ], { duration: 10000, delay: -5000, easing: 'linear' });
+ let cs = getComputedStyle(div);
+ assert_equals(cs.getPropertyValue(name), '200px');
+
+ // Change initial value.
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '100px'
+ }, (name) => {
+ assert_equals(cs.getPropertyValue(name), '250px');
+ });
+
+ animation.finish();
+}, 'Ongoing animation picks up redeclared intial value');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ try {
+ document.body.style = `${name}: 100px`;
+ // Note that 'inherit' here refers to #outer, which has the initial
+ // value. (#outer did not inherit from body, since the property is not
+ // yet declared as inherited).
+ let animation = div.animate([
+ { [name]: 'inherit'},
+ { [name]: '400px'},
+ ], { duration: 10000, delay: -5000, easing: 'linear' });
+ let cs = getComputedStyle(div);
+ assert_equals(cs.getPropertyValue(name), '200px');
+
+ // Change inherits to 'true'. The value should now propagate from body
+ // to #outer.
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: true,
+ initialValue: '0px'
+ }, (name) => {
+ assert_equals(cs.getPropertyValue(name), '250px');
+ });
+
+ animation.finish();
+ } finally {
+ document.body.style = '';
+ }
+}, 'Ongoing animation picks up redeclared inherits flag');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ try {
+ outer.style = `${name}: 100px`;
+ // 'unset' should take the initial value (not the value from #outer), since
+ // the property is not declared as inherited.
+ let animation = div.animate([
+ { [name]: 'unset'},
+ { [name]: '400px'},
+ ], { duration: 10000, delay: -5000, easing: 'linear' });
+ let cs = getComputedStyle(div);
+ assert_equals(cs.getPropertyValue(name), '200px');
+
+ // Change inherits to 'true'. 'unset' now refers to #outer's value.
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: true,
+ initialValue: '0px'
+ }, (name) => {
+ assert_equals(cs.getPropertyValue(name), '250px');
+ });
+
+ animation.finish();
+ } finally {
+ outer.style = '';
+ }
+}, 'Ongoing animation picks up redeclared meaning of \'unset\'');
+
+test_with_at_property({
+ syntax: '"<color>"',
+ inherits: false,
+ initialValue: 'red'
+}, (name) => {
+ try {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(255, 0, 0)');
+ div.style = `transition: ${name} steps(2, start) 100s; ${name}: blue`;
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(128, 0, 128)');
+ } finally {
+ div.style = '';
+ }
+}, 'Transitioning from initial value');
+
+test_with_at_property({
+ syntax: '"<color>"',
+ inherits: false,
+ initialValue: 'red'
+}, (name) => {
+ try {
+ div.style = `${name}: blue;`;
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 0, 255)');
+ div.style = `transition: ${name} steps(2, start) 100s; ${name}: green`;
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 64, 128)');
+ } finally {
+ div.style = '';
+ }
+}, 'Transitioning from specified value');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '100px'
+}, (name) => {
+ with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
+ // Re-declaring the property with a different initial value effectively
+ // means the computed value has changed. This means we should transition
+ // from the old initial value to the new initial value.
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '200px'
+ }, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
+ });
+ });
+}, 'Transition triggered by initial value change');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '100px'
+}, (name) => {
+ with_style_node(`div { transition: ${name} steps(2, start) 100s; }`, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
+ with_at_property({
+ name: name,
+ syntax: '"<color>"',
+ inherits: false,
+ initialValue: 'green'
+ }, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 128, 0)');
+ });
+ });
+}, 'No transition when changing types');
+
+test(() => {
+ let name = generate_name();
+ with_style_node(`div { ${name}: 100px; transition: ${name} steps(2, start) 100s; }`, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
+
+ let style1 = document.createElement('style');
+ style1.textContent = `
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 200px;
+ }
+ `;
+
+ let style2 = document.createElement('style');
+ style2.textContent = `div { ${name}: 400px; }`;
+
+ try {
+ // Register the property:
+ document.body.append(style1);
+ // The token sequence ' 100px' is now interpreted as a length '100px'.
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '100px');
+
+ // Change the computed value:
+ document.body.append(style2);
+ // This should cause an interpolation between 100px and 400px:
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
+
+ // In the middle of the transition above, remove the @property rule
+ // (making the computed value a token sequence again). We should snap
+ // to the new token sequence.
+ style1.remove();
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '400px');
+ } finally {
+ style1.remove();
+ style2.remove();
+ }
+ });
+}, 'No transition when removing @property rule');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ with_style_node(`
+ @keyframes test {
+ from { ${name}: 100px; }
+ to { ${name}: 200px; }
+ }
+ #div {
+ animation: test 100s -50s linear;
+ --unregistered: var(${name});
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue('--unregistered'), '150px');
+ });
+}, 'Unregistered properties referencing animated properties update correctly.');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ with_style_node(`
+ @keyframes test {
+ from { ${name}: 100px; }
+ to { ${name}: 200px; }
+ }
+ @property --registered {
+ syntax: "<length>";
+ inherits: false;
+ initialValue: 0px;
+ }
+ #div {
+ animation: test 100s -50s linear;
+ --registered: var(${name});
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue('--registered'), '150px');
+ });
+}, 'Registered properties referencing animated properties update correctly.');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ with_style_node(`
+ @keyframes test {
+ from { ${name}: inherit; }
+ to { ${name}: 300px; }
+ }
+ #outer {
+ ${name}: 100px;
+ }
+ #div {
+ animation: test 100s -50s linear paused;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '200px');
+
+ outer.style.setProperty(name, '200px');
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
+
+ outer.style.setProperty(name, '0px');
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
+
+ outer.style.removeProperty(name);
+ });
+}, 'CSS animation setting "inherit" for a custom property on a keyframe is responsive to changing that custom property on the parent.');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name) => {
+ with_style_node(`
+ #outer {
+ ${name}: 100px;
+ }
+ `, () => {
+ const animation = div.animate({ [name]: ["inherit", "300px"]}, 1000);
+ animation.currentTime = 500;
+ animation.pause();
+
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '200px');
+
+ outer.style.setProperty(name, '200px');
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '250px');
+
+ outer.style.setProperty(name, '0px');
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '150px');
+
+ outer.style.removeProperty(name);
+ });
+}, 'JS-originated animation setting "inherit" for a custom property on a keyframe is responsive to changing that custom property on the parent.');
+
+test_with_at_property({
+ syntax: '"<color>"',
+ inherits: false,
+ initialValue: 'black'
+}, (name) => {
+ with_style_node(`
+ @keyframes test {
+ from { ${name}: currentcolor; }
+ to { ${name}: rgb(200, 200, 200); }
+ }
+ #outer {
+ color: rgb(100, 100, 100);
+ }
+ #div {
+ animation: test 100s -50s linear paused;
+ }
+ `, (style) => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(150, 150, 150)');
+
+ outer.style.color = 'rgb(50, 50, 50)';
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(125, 125, 125)');
+
+ outer.style.color = 'rgb(150, 150, 150)';
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(175, 175, 175)');
+
+ outer.style.removeProperty("color");
+ });
+}, 'CSS animation setting "currentColor" for a custom property on a keyframe is responsive to changing "color" on the parent.');
+
+test_with_at_property({
+ syntax: '"<color>"',
+ inherits: false,
+ initialValue: 'black'
+}, (name) => {
+ with_style_node(`
+ #outer {
+ color: rgb(100, 100, 100);
+ }
+ `, () => {
+ const animation = div.animate({ [name]: ["currentcolor", "rgb(200, 200, 200)"]}, 1000);
+ animation.currentTime = 500;
+ animation.pause();
+
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(150, 150, 150)');
+
+ outer.style.color = 'rgb(50, 50, 50)';
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(125, 125, 125)');
+
+ outer.style.color = 'rgb(150, 150, 150)';
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(175, 175, 175)');
+
+ outer.style.removeProperty("color");
+ });
+}, 'JS-originated animation setting "currentColor" for a custom property on a keyframe is responsive to changing "color" on the parent.');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-cssom.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-cssom.html
new file mode 100644
index 0000000000..27041c2ad0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-cssom.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#cssom">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @property --valid {
+ syntax: "<color> | none";
+ inherits: false;
+ initial-value: red;
+ }
+ @property --valid-reverse {
+ initial-value: 0px;
+ inherits: true;
+ syntax: "<length>";
+ }
+ @property --valid-universal {
+ syntax: "*";
+ inherits: false;
+ }
+ @property --valid-whitespace {
+ syntax: " <color># ";
+ inherits: false;
+ initial-value: red, blue;
+ }
+ @property --vALId {
+ syntax: "<color> | none";
+ inherits: false;
+ initial-value: red;
+ }
+ @property --no-descriptors {
+
+ }
+ @property --no-syntax {
+ inherits: false;
+ initial-value: red;
+ }
+ @property --no-inherits {
+ syntax: "<color> | none";
+ initial-value: red;
+ }
+ @property --no-initial-color-value {
+ syntax: "<color> | none";
+ inherits: false;
+ }
+ @property --no-initial-universal-value {
+ syntax: "*";
+ inherits: false;
+ }
+ @property --syntax-only {
+ syntax: "<color> | none";
+ }
+ @property --inherits-only {
+ inherits: true;
+ }
+ @property --initial-value-only {
+ initial-value: red;
+ }
+ /* U+0009 CHARACTER TABULATION */
+ @property --tab\9 tab {
+ syntax: "*";
+ inherits: true;
+ }
+</style>
+<script>
+
+function find_at_property_rule(name) {
+ for (let rule of document.styleSheets[0].cssRules) {
+ if (rule.constructor.name != "CSSPropertyRule")
+ continue;
+ if (rule.name == name)
+ return rule;
+ }
+ return null;
+}
+
+function test_invalid(name) {
+ test(() => {
+ let rule = find_at_property_rule(name);
+ assert_true(!rule);
+ }, `Rule for ${name} is invalid`);
+}
+
+function test_css_text(name, expected) {
+ test(() => {
+ let rule = find_at_property_rule(name);
+ assert_true(!!rule);
+ assert_equals(rule.cssText, expected);
+ }, `Rule for ${name} has expected cssText`);
+}
+
+function test_name(name) {
+ test(() => {
+ let rule = find_at_property_rule(name);
+ assert_true(!!rule);
+ assert_equals(rule.name, name);
+ }, `Rule for ${name} returns expected value for CSSPropertyRule.name`);
+}
+
+function test_syntax(name, expected) {
+ test(() => {
+ let rule = find_at_property_rule(name);
+ assert_true(!!rule);
+ assert_equals(rule.syntax, expected);
+ }, `Rule for ${name} returns expected value for CSSPropertyRule.syntax`);
+}
+
+function test_inherits(name, expected) {
+ test(() => {
+ let rule = find_at_property_rule(name);
+ assert_true(!!rule);
+ assert_equals(rule.inherits, expected);
+ }, `Rule for ${name} returns expected value for CSSPropertyRule.inherits`);
+}
+
+function test_initial_value(name, expected) {
+ test(() => {
+ let rule = find_at_property_rule(name);
+ assert_true(!!rule);
+ assert_equals(rule.initialValue, expected);
+ }, `Rule for ${name} returns expected value for CSSPropertyRule.initialValue`);
+}
+
+// Invalid @property rules.
+test_invalid('--no-descriptors');
+test_invalid('--no-syntax');
+test_invalid('--no-inherits');
+test_invalid('--no-initial-color-value');
+test_invalid('--syntax-only', '@property --syntax-only { syntax: "<color> | none"; }');
+test_invalid('--inherits-only', '@property --inherits-only { inherits: true; }');
+test_invalid('--initial-value-only', '@property --initial-value-only { initial-value: red; }');
+
+// CSSPropertyRule.cssText
+
+test_css_text('--valid', '@property --valid { syntax: "<color> | none"; inherits: false; initial-value: red; }');
+test_css_text('--valid-reverse', '@property --valid-reverse { syntax: "<length>"; inherits: true; initial-value: 0px; }');
+test_css_text('--valid-universal', '@property --valid-universal { syntax: "*"; inherits: false; }');
+test_css_text('--valid-whitespace', '@property --valid-whitespace { syntax: " <color># "; inherits: false; initial-value: red, blue; }');
+test_css_text('--vALId', '@property --vALId { syntax: "<color> | none"; inherits: false; initial-value: red; }');
+
+test_css_text('--no-initial-universal-value', '@property --no-initial-universal-value { syntax: "*"; inherits: false; }');
+
+test_css_text('--tab\ttab', '@property --tab\\9 tab { syntax: "*"; inherits: true; }');
+
+// CSSRule.type
+
+test(() => {
+ let rule = find_at_property_rule('--valid');
+ assert_equals(rule.type, 0);
+}, 'CSSRule.type returns 0');
+
+// CSSPropertyRule.name
+
+test_name('--valid');
+test_name('--valid-reverse');
+test_name('--valid-universal');
+test_name('--valid-whitespace');
+test_name('--vALId');
+
+test_name('--no-initial-universal-value');
+
+// CSSPropertyRule.syntax
+
+test_syntax('--valid', '<color> | none');
+test_syntax('--valid-reverse', '<length>');
+test_syntax('--valid-universal', '*');
+test_syntax('--valid-whitespace', ' <color># ');
+test_syntax('--vALId', '<color> | none');
+
+test_syntax('--no-initial-universal-value', '*');
+
+// CSSPropertyRule.inherits
+
+test_inherits('--valid', false);
+test_inherits('--valid-reverse', true);
+test_inherits('--valid-universal', false);
+test_inherits('--valid-whitespace', false);
+test_inherits('--vALId', false);
+
+test_inherits('--no-initial-universal-value', false);
+
+// CSSPropertyRule.initialValue
+
+test_initial_value('--valid', 'red');
+test_initial_value('--valid-reverse', '0px');
+test_initial_value('--valid-universal', null);
+test_initial_value('--valid-whitespace', 'red, blue');
+test_initial_value('--vALId', 'red');
+
+test_initial_value('--no-initial-universal-value', null);
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-non-matching-media-crash.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-non-matching-media-crash.html
new file mode 100644
index 0000000000..c5b894f349
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-non-matching-media-crash.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html class="test-wait">
+ <link rel="help" href="https://crbug.com/1085994">
+ <style id="style">
+ @property --x {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 0px;
+ }
+ </style>
+ <script>
+ document.documentElement.offsetTop;
+ style.setAttribute('media', 'braille');
+ document.documentElement.className = '';
+ </script>
+ <p>
+ PASS if no crash
+ </p>
+</html>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-shadow.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-shadow.html
new file mode 100644
index 0000000000..db282d10b0
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-shadow.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<style>
+ @property --x {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 0px;
+ }
+ #outside {
+ --x: calc(1px + 1px);
+ --y: calc(1px + 1px);
+ }
+</style>
+<template id=template>
+ <style>
+ /* This rule should have no effect */
+ @property --y {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 0px;
+ }
+ #inside {
+ --x: calc(1px + 1px);
+ --y: calc(1px + 1px);
+ }
+ </style>
+ <div id=inside></div>
+</template>
+<div id=host></div>
+<div id=outside></div>
+<script>
+
+test(() => {
+ let root = host.attachShadow({ mode: 'open' });
+ root.append(template.content.cloneNode(true));
+ let inside = root.querySelector('#inside');
+ assert_equals(getComputedStyle(outside).getPropertyValue('--x'), '2px');
+ assert_equals(getComputedStyle(outside).getPropertyValue('--y'), 'calc(1px + 1px)');
+ assert_equals(getComputedStyle(inside).getPropertyValue('--x'), '2px');
+ assert_equals(getComputedStyle(inside).getPropertyValue('--y'), 'calc(1px + 1px)');
+}, '@property rules in shadow trees should have no effect');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-stylesheets.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-stylesheets.html
new file mode 100644
index 0000000000..a7f3ea3948
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-stylesheets.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<title>Verify that the correct registration is selected for mutated stylesheets</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#determining-registration">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=div></div>
+<script>
+
+test(() => {
+ with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '1px'
+ }, (name) => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+}, '@property detected when stylesheet appears');
+
+test(() => {
+ let name = generate_name();
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '1px'
+ }, (name) => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '');
+}, '@property removal detected when last @property rule disappears');
+
+test(() => {
+ with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '1px'
+ }, (name1) => {
+ with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '2px'
+ }, (name2) => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '2px');
+ });
+ });
+}, '@property detected in second stylesheet');
+
+test(() => {
+ let name2 = generate_name();
+ with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '1px'
+ }, (name1) => {
+ with_at_property({
+ name2: name2,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '2px'
+ }, (name2) => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '2px');
+ });
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '');
+ });
+}, '@property removal detected with removal of second stylesheet');
+
+test(() => {
+ let name1 = generate_name();
+ let name2 = generate_name();
+
+ let sheet1 = `
+ @property ${name1} {
+ inherits: false;
+ syntax: "<length>";
+ initial-value: 1px;
+ }
+ `;
+ let sheet2 = `
+ @property ${name2} {
+ inherits: false;
+ syntax: "<length>";
+ initial-value: 2px;
+ }
+ `;
+
+ let node1 = document.createElement('style');
+ let node2 = document.createElement('style');
+
+ node1.textContent = sheet1;
+ node2.textContent = sheet2;
+
+ try {
+ document.body.append(node1, node2);
+ assert_equals(getComputedStyle(div).getPropertyValue(name1), '1px');
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '2px');
+ node1.remove();
+ assert_equals(getComputedStyle(div).getPropertyValue(name1), '');
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '2px');
+ } finally {
+ node1.remove();
+ node2.remove();
+ }
+}, '@property removal detected with removal of first stylesheet');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-typedom.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-typedom.html
new file mode 100644
index 0000000000..beee032429
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-typedom.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#css-style-value-reification">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=div></div>
+<script>
+
+test(() => {
+ let name = generate_name();
+ with_style_node(`div { ${name}: 100px; }`, () => {
+ // Before registering the property, ${name} should reify as a
+ // a token sequence.
+ assert_equals(div.computedStyleMap().get(name).constructor.name, 'CSSUnparsedValue');
+ assert_equals(div.computedStyleMap().get(name).toString(), '100px');
+
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+ }, () => {
+ // After registering, it should reify as a <length>.
+ assert_equals(div.computedStyleMap().get(name).constructor.name, 'CSSUnitValue');
+ assert_equals(div.computedStyleMap().get(name).value, 100);
+ assert_equals(div.computedStyleMap().get(name).unit, 'px');
+ });
+
+ // After @property is removed, the computed value is once again a token
+ // sequence.
+ assert_equals(div.computedStyleMap().get(name).constructor.name, 'CSSUnparsedValue');
+ assert_equals(div.computedStyleMap().get(name).toString(), '100px');
+ });
+}, 'Properties declared with @property reify correctly');
+
+test(() => {
+ let name = generate_name();
+ // 0 is valid as a both <length> and <integer>, which reify differently.
+ with_style_node(`div { ${name}: 0; }`, () => {
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '1000px'
+ }, () => {
+ assert_equals(div.computedStyleMap().get(name).constructor.name, 'CSSUnitValue');
+ assert_equals(div.computedStyleMap().get(name).value, 0);
+ assert_equals(div.computedStyleMap().get(name).unit, 'px');
+
+ with_at_property({
+ name: name,
+ syntax: '"<integer>"',
+ inherits: false,
+ initialValue: '1000'
+ }, () => {
+ assert_equals(div.computedStyleMap().get(name).constructor.name, 'CSSUnitValue');
+ assert_equals(div.computedStyleMap().get(name).value, 0);
+ assert_equals(div.computedStyleMap().get(name).unit, 'number');
+ });
+ });
+ });
+}, 'Re-declaring a property with a different type affects reification');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units-dynamic.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units-dynamic.html
new file mode 100644
index 0000000000..68adff6887
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units-dynamic.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>@property: viewport units in initial value (dynamic)</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ iframe {
+ width: 400px;
+ height: 200px;
+ }
+</style>
+<iframe id=iframe srcdoc="
+ <style>
+ @property --10vw { syntax: '<length>'; inherits: true; initial-value: 10vw}
+ @property --10vh { syntax: '<length>'; inherits: true; initial-value: 10vh}
+ div {
+ background: green;
+ width: var(--10vw);
+ height: var(--10vh);
+ }
+ </style>
+ <div style='width:10vw'></div>
+"></iframe>
+<script>
+ iframe.offsetTop;
+
+ function waitForLoad(w) {
+ return new Promise(resolve => w.addEventListener('load', resolve));
+ }
+
+ promise_test(async (t) => {
+ await waitForLoad(window);
+ let element = iframe.contentDocument.querySelector('div');
+ assert_equals(getComputedStyle(element).getPropertyValue('--10vw'), '40px');
+ assert_equals(getComputedStyle(element).getPropertyValue('--10vh'), '20px');
+
+ iframe.style.width = '100px';
+ assert_equals(getComputedStyle(element).getPropertyValue('--10vw'), '10px');
+ assert_equals(getComputedStyle(element).getPropertyValue('--10vh'), '20px');
+ });
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units.html b/testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units.html
new file mode 100644
index 0000000000..51520c2a70
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property-viewport-units.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<title>@property: viewport units in initial value</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ iframe {
+ width: 400px;
+ height: 200px;
+ }
+</style>
+<iframe id=iframe srcdoc="
+ <style>
+ @property --10vw { syntax: '<length>'; inherits: true; initial-value: 10vw}
+ @property --10vh { syntax: '<length>'; inherits: true; initial-value: 10vh}
+ @property --10vi { syntax: '<length>'; inherits: true; initial-value: 10vi}
+ @property --10vb { syntax: '<length>'; inherits: true; initial-value: 10vb}
+ @property --10vmin { syntax: '<length>'; inherits: true; initial-value: 10vmin}
+ @property --10vmax { syntax: '<length>'; inherits: true; initial-value: 10vmax}
+
+ @property --10svw { syntax: '<length>'; inherits: true; initial-value: 10svw}
+ @property --10svh { syntax: '<length>'; inherits: true; initial-value: 10svh}
+ @property --10svi { syntax: '<length>'; inherits: true; initial-value: 10svi}
+ @property --10svb { syntax: '<length>'; inherits: true; initial-value: 10svb}
+ @property --10svmin { syntax: '<length>'; inherits: true; initial-value: 10svmin}
+ @property --10svmax { syntax: '<length>'; inherits: true; initial-value: 10svmax}
+
+ @property --10lvw { syntax: '<length>'; inherits: true; initial-value: 10lvw}
+ @property --10lvh { syntax: '<length>'; inherits: true; initial-value: 10lvh}
+ @property --10lvi { syntax: '<length>'; inherits: true; initial-value: 10lvi}
+ @property --10lvb { syntax: '<length>'; inherits: true; initial-value: 10lvb}
+ @property --10lvmin { syntax: '<length>'; inherits: true; initial-value: 10lvmin}
+ @property --10lvmax { syntax: '<length>'; inherits: true; initial-value: 10lvmax}
+
+ @property --10dvw { syntax: '<length>'; inherits: true; initial-value: 10dvw}
+ @property --10dvh { syntax: '<length>'; inherits: true; initial-value: 10dvh}
+ @property --10dvi { syntax: '<length>'; inherits: true; initial-value: 10dvi}
+ @property --10dvb { syntax: '<length>'; inherits: true; initial-value: 10dvb}
+ @property --10dvmin { syntax: '<length>'; inherits: true; initial-value: 10dvmin}
+ @property --10dvmax { syntax: '<length>'; inherits: true; initial-value: 10dvmax}
+ </style>
+ <div></div>
+"></iframe>
+<script>
+ iframe.offsetTop;
+
+ function waitForLoad(w) {
+ return new Promise(resolve => {
+ if (w.document.readyState == 'complete')
+ resolve();
+ else
+ w.addEventListener('load', resolve)
+ });
+ }
+
+ function test_unit(element, actual, expected) {
+ promise_test(async (t) => {
+ await waitForLoad(window);
+ let element = iframe.contentDocument.querySelector('div');
+ assert_equals(getComputedStyle(element).getPropertyValue(`--${actual}`), expected);
+ },`${actual} is ${expected}`);
+ }
+
+ test_unit(iframe, '10vw', '40px');
+ test_unit(iframe, '10vh', '20px');
+ test_unit(iframe, '10vi', '40px');
+ test_unit(iframe, '10vb', '20px');
+ test_unit(iframe, '10vmin', '20px');
+ test_unit(iframe, '10vmax', '40px');
+
+ test_unit(iframe, '10svw', '40px');
+ test_unit(iframe, '10svh', '20px');
+ test_unit(iframe, '10svi', '40px');
+ test_unit(iframe, '10svb', '20px');
+ test_unit(iframe, '10svmin', '20px');
+ test_unit(iframe, '10svmax', '40px');
+
+ test_unit(iframe, '10lvw', '40px');
+ test_unit(iframe, '10lvh', '20px');
+ test_unit(iframe, '10lvi', '40px');
+ test_unit(iframe, '10lvb', '20px');
+ test_unit(iframe, '10lvmin', '20px');
+ test_unit(iframe, '10lvmax', '40px');
+
+ test_unit(iframe, '10dvw', '40px');
+ test_unit(iframe, '10dvh', '20px');
+ test_unit(iframe, '10dvi', '40px');
+ test_unit(iframe, '10dvb', '20px');
+ test_unit(iframe, '10dvmin', '20px');
+ test_unit(iframe, '10dvmax', '40px');
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/at-property.html b/testing/web-platform/tests/css/css-properties-values-api/at-property.html
new file mode 100644
index 0000000000..950d9b02d7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/at-property.html
@@ -0,0 +1,316 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id="outer">
+ <div id="target"></div>
+</div>
+<script>
+
+// Parsing:
+
+let uppercase_first = (x) => x.charAt(0).toUpperCase() + x.slice(1);
+let to_camel_case = (x) => x.split('-')[0] + x.split('-').slice(1).map(uppercase_first).join('');
+
+function get_cssom_descriptor_value(rule, descriptor) {
+ switch (descriptor) {
+ case 'syntax':
+ return rule.syntax;
+ case 'inherits':
+ return rule.inherits;
+ case 'initial-value':
+ return rule.initialValue;
+ default:
+ assert_true(false, 'Should not reach here');
+ return null;
+ }
+}
+
+// Test that for the given descriptor (e.g. 'syntax'), the specified value
+// will yield the expected_value when observed using CSSOM. If the expected_value
+// is omitted, it is the same as the specified value.
+function test_descriptor(descriptor, specified_value, expected_value, other_descriptors) {
+ // Try and build a valid @property form the specified descriptor.
+ let at_property = { [to_camel_case(descriptor)]: specified_value };
+
+ // If extra values are specified in other_descriptors, just use them.
+ if (typeof(other_descriptors) !== 'unspecified') {
+ for (let name in other_descriptors) {
+ if (other_descriptors.hasOwnProperty(name)) {
+ if (name == descriptor) {
+ throw `Unexpected ${name} in other_descriptors`;
+ }
+ at_property[to_camel_case(name)] = other_descriptors[name];
+ }
+ }
+ }
+
+ if (!('syntax' in at_property)) {
+ // The syntax descriptor is required. Use the universal one as a fallback.
+ // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor
+ at_property.syntax = '"*"';
+ }
+ if (!('inherits' in at_property)) {
+ // The inherits descriptor is required. Make it true as a fallback.
+ // https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor
+ at_property.inherits = true;
+ }
+ if (!at_property.syntax.match(/^"\s*\*\s*"$/) &&
+ !('initialValue' in at_property)) {
+ // The initial-value is required for non-universal syntax.
+ // Pick a computationally independent value that follows specified syntax.
+ // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor
+ at_property.initialValue = (() => {
+ let first_syntax_component = specified_value
+ .replace(/^"(.*)"$/, '$1') // unquote
+ .replace(/[\s\uFEFF\xA0]+/g, ' ') // collapse whitespaces
+ .match(/^[^|\#\+]*/)[0] // pick first component
+ .trim();
+ switch (first_syntax_component) {
+ case '<color>': return 'blue';
+ case '<length>': return '42px';
+ default:
+ if (first_syntax_component.startsWith('<')) {
+ throw `Unsupported data type name '${first_syntax_component}'`;
+ }
+ return first_syntax_component; // <custom-ident>
+ }
+ })();
+ }
+
+ if (expected_value === null) {
+ test_with_at_property(at_property, (name, rule) => {
+ assert_true(!rule);
+ }, `Attribute '${descriptor}' makes the @property rule invalid for [${specified_value}]`);
+ } else {
+ if (typeof(expected_value) === 'undefined')
+ expected_value = specified_value;
+ test_with_at_property(at_property, (name, rule) => {
+ assert_equals(get_cssom_descriptor_value(rule, descriptor), expected_value);
+ }, `Attribute '${descriptor}' returns expected value for [${specified_value}]`);
+ }
+}
+
+// syntax
+test_descriptor('syntax', '"<color>"', '<color>');
+test_descriptor('syntax', '"<color> | none"', '<color> | none');
+test_descriptor('syntax', '"<color># | <image> | none"', '<color># | <image> | none');
+test_descriptor('syntax', '"foo | <length>#"', 'foo | <length>#');
+test_descriptor('syntax', '"foo | bar | baz"', 'foo | bar | baz');
+test_descriptor('syntax', '"notasyntax"', 'notasyntax');
+
+// syntax: universal
+for (const syntax of ["*", " * ", "* ", "\t*\t"]) {
+ test_descriptor('syntax', `"${syntax}"`, syntax);
+}
+
+// syntax: <color> value
+test_descriptor('syntax', '"red"', "red"); // treated as <custom-ident>.
+test_descriptor('syntax', '"rgb(255, 0, 0)"', null);
+
+// syntax: missing quotes
+test_descriptor('syntax', '<color>', null);
+test_descriptor('syntax', 'foo | bar', null);
+
+// syntax: invalid <custom-ident>
+// https://drafts.csswg.org/css-values-4/#custom-idents
+for (const syntax of
+ ["default",
+ "initial",
+ "inherit",
+ "unset",
+ "revert",
+ "revert-layer",
+ ]) {
+ test_descriptor('syntax', `"${syntax}"`, null);
+ test_descriptor('syntax', `"${uppercase_first(syntax)}"`, null);
+}
+
+// syntax: pipe between components
+test_descriptor('syntax', '"foo bar"', null, {'initial-value': 'foo bar'});
+test_descriptor('syntax', '"Foo <length>"', null, {'initial-value': 'Foo 42px'});
+test_descriptor('syntax', '"foo, bar"', null, {'initial-value': 'foo, bar'});
+test_descriptor('syntax', '"<length> <percentage>"', null, {'initial-value': '42px 100%'});
+
+// syntax: leading bar
+test_descriptor('syntax', '"|<length>"', null, {'initial-value': '42px'});
+
+// initial-value
+test_descriptor('initial-value', '10px');
+test_descriptor('initial-value', 'rgb(1, 2, 3)');
+test_descriptor('initial-value', 'red');
+test_descriptor('initial-value', 'foo');
+test_descriptor('initial-value', 'if(){}');
+
+// initial-value: not computationally independent
+test_descriptor('initial-value', '3em', null, {'syntax': '"<length>"'});
+test_descriptor('initial-value', 'var(--x)', null);
+
+// inherits
+test_descriptor('inherits', 'true', true);
+test_descriptor('inherits', 'false', false);
+
+test_descriptor('inherits', 'none', null);
+test_descriptor('inherits', '0', null);
+test_descriptor('inherits', '1', null);
+test_descriptor('inherits', '"true"', null);
+test_descriptor('inherits', '"false"', null);
+test_descriptor('inherits', 'calc(0)', null);
+
+test_with_style_node('@property foo { }', (node) => {
+ assert_equals(node.sheet.rules.length, 0);
+}, 'Invalid property name does not parse [foo]');
+
+test_with_style_node('@property -foo { }', (node) => {
+ assert_equals(node.sheet.rules.length, 0);
+}, 'Invalid property name does not parse [-foo]');
+
+// Applying @property rules
+
+function test_applied(syntax, initial, inherits, expected) {
+ test_with_at_property({
+ syntax: `"${syntax}"`,
+ initialValue: initial,
+ inherits: inherits
+ }, (name, rule) => {
+ let actual = getComputedStyle(target).getPropertyValue(name);
+ assert_equals(actual, expected);
+ }, `Rule applied [${syntax}, ${initial}, ${inherits}]`);
+}
+
+function test_not_applied(syntax, initial, inherits) {
+ test_with_at_property({
+ syntax: `"${syntax}"`,
+ initialValue: initial,
+ inherits: inherits
+ }, (name, rule) => {
+ let actual = getComputedStyle(target).getPropertyValue(name);
+ assert_equals(actual, '');
+ }, `Rule not applied [${syntax}, ${initial}, ${inherits}]`);
+}
+
+// syntax, initialValue, inherits, expected
+test_applied('*', 'if(){}', false, 'if(){}');
+test_applied('<angle>', '42deg', false, '42deg');
+test_applied('<angle>', '1turn', false, '360deg');
+test_applied('<color>', 'green', false, 'rgb(0, 128, 0)');
+test_applied('<color>', 'rgb(1, 2, 3)', false, 'rgb(1, 2, 3)');
+test_applied('<image>', 'url("http://a/")', false, 'url("http://a/")');
+test_applied('<integer>', '5', false, '5');
+test_applied('<length-percentage>', '10px', false, '10px');
+test_applied('<length-percentage>', '10%', false, '10%');
+test_applied('<length-percentage>', 'calc(10% + 10px)', false, 'calc(10% + 10px)');
+test_applied('<length>', '10px', false, '10px');
+test_applied('<number>', '2.5', false, '2.5');
+test_applied('<percentage>', '10%', false, '10%');
+test_applied('<resolution>', '50dppx', false, '50dppx');
+test_applied('<resolution>', '96dpi', false, '1dppx');
+test_applied('<time>', '10s', false, '10s');
+test_applied('<time>', '1000ms', false, '1s');
+test_applied('<transform-function>', 'rotateX(0deg)', false, 'rotateX(0deg)');
+test_applied('<transform-list>', 'rotateX(0deg)', false, 'rotateX(0deg)');
+test_applied('<transform-list>', 'rotateX(0deg) translateX(10px)', false, 'rotateX(0deg) translateX(10px)');
+test_applied('<url>', 'url("http://a/")', false, 'url("http://a/")');
+
+// inherits: true/false
+test_applied('<color>', 'tomato', false, 'rgb(255, 99, 71)');
+test_applied('<color>', 'tomato', true, 'rgb(255, 99, 71)');
+
+test_with_at_property({ syntax: '"*"', inherits: true }, (name, rule) => {
+ try {
+ outer.style.setProperty(name, 'foo');
+ let actual = getComputedStyle(target).getPropertyValue(name);
+ assert_equals(actual, 'foo');
+ } finally {
+ outer.style = '';
+ }
+}, 'Rule applied for "*", even with no initial value');
+
+test_not_applied(undefined, 'green', false);
+test_not_applied('<color>', undefined, false);
+test_not_applied('<color>', 'green', undefined);
+test_not_applied('<gandalf>', 'grey', false);
+test_not_applied('gandalf', 'grey', false);
+test_not_applied('<color>', 'notacolor', false);
+test_not_applied('<length>', '10em', false);
+
+test_not_applied('<transform-function>', 'translateX(1em)', false);
+test_not_applied('<transform-function>', 'translateY(1lh)', false);
+test_not_applied('<transform-list>', 'rotate(10deg) translateX(1em)', false);
+test_not_applied('<transform-list>', 'rotate(10deg) translateY(1lh)', false);
+
+// Inheritance
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+}, (name, rule) => {
+ try {
+ outer.style = `${name}: 40px`;
+ assert_equals(getComputedStyle(outer).getPropertyValue(name), '40px');
+ assert_equals(getComputedStyle(target).getPropertyValue(name), '0px');
+ } finally {
+ outer.style = '';
+ }
+}, 'Non-inherited properties do not inherit');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: true,
+ initialValue: '0px'
+}, (name, rule) => {
+ try {
+ outer.style = `${name}: 40px`;
+ assert_equals(getComputedStyle(outer).getPropertyValue(name), '40px');
+ assert_equals(getComputedStyle(target).getPropertyValue(name), '40px');
+ } finally {
+ outer.style = '';
+ }
+}, 'Inherited properties inherit');
+
+// Initial values
+
+test_with_at_property({
+ syntax: '"<color>"',
+ inherits: true,
+ initialValue: 'green'
+}, (name, rule) => {
+ try {
+ target.style = `--x:var(${name})`;
+ assert_equals(getComputedStyle(target).getPropertyValue(name), 'rgb(0, 128, 0)');
+ } finally {
+ target.style = '';
+ }
+}, 'Initial values substituted as computed value');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: undefined
+}, (name, rule) => {
+ try {
+ target.style = `${name}: calc(1px + 1px);`;
+ assert_equals(getComputedStyle(target).getPropertyValue(name), 'calc(1px + 1px)');
+ } finally {
+ target.style = '';
+ }
+}, 'Non-universal registration are invalid without an initial value');
+
+test_with_at_property({
+ syntax: '"*"',
+ inherits: false,
+ initialValue: undefined
+}, (name, rule) => {
+ try {
+ // If the registration suceeded, ${name} does *not* inherit, and hence
+ // the computed value on 'target' should be empty.
+ outer.style = `${name}: calc(1px + 1px);`;
+ assert_equals(getComputedStyle(target).getPropertyValue(name), '');
+ } finally {
+ outer.style = '';
+ }
+}, 'Initial value may be omitted for universal registration');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/conditional-rules.html b/testing/web-platform/tests/css/css-properties-values-api/conditional-rules.html
new file mode 100644
index 0000000000..0bff879856
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/conditional-rules.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#conditional-rules">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+ CSS.registerProperty({
+ name: '--length',
+ syntax: '<length>',
+ initialValue: '0px',
+ inherits: false
+ });
+</script>
+
+<style>
+ #target { color: red; }
+ @supports(--length: green) {
+ #target { color: rgb(1, 2, 3); }
+ }
+</style>
+
+<div id=target></div>
+
+<script>
+
+test(function() {
+ let cs = getComputedStyle(target);
+ assert_equals(cs.getPropertyValue('color'), 'rgb(1, 2, 3)');
+}, '@supports should ignore registered syntax');
+
+test(function() {
+ assert_true(CSS.supports('--length: red'));
+ assert_true(CSS.supports('--length: 10px'));
+ assert_true(CSS.supports('--length: anything, really'));
+}, 'CSS.supports(conditionText) should ignore registered syntax');
+
+test(function() {
+ assert_true(CSS.supports('--length', 'red'));
+ assert_true(CSS.supports('--length', '10px'));
+ assert_true(CSS.supports('--length', ' anything, really'));
+}, 'CSS.supports(property, value) should ignore registered syntax');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/crashtests/initial-in-audio-crash.html b/testing/web-platform/tests/css/css-properties-values-api/crashtests/initial-in-audio-crash.html
new file mode 100644
index 0000000000..dfc2b850a5
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/crashtests/initial-in-audio-crash.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/1356699">
+<audio>
+ <div>
+ <div id=test1></div>
+ </div>
+</audio>
+<script>
+getComputedStyle(test1).getPropertyValue('--a');
+CSS.registerProperty({name: '--a', syntax: '<length>', initialValue: '2px', inherits: false});
+test1.style.setProperty('--a', 'initial');
+getComputedStyle(test1).getPropertyValue('--a');
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/determine-registration.html b/testing/web-platform/tests/css/css-properties-values-api/determine-registration.html
new file mode 100644
index 0000000000..99e39c191e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/determine-registration.html
@@ -0,0 +1,264 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#determining-registration">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=outer>
+ <div id=div></div>
+</div>
+<script>
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '1px'
+}, (name) => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+}, '@property determines the registration when uncontested');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '2px'
+}, (name) => {
+ CSS.registerProperty({
+ name: name,
+ syntax: '<color>',
+ inherits: false,
+ initialValue: 'green'
+ });
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 128, 0)');
+}, 'CSS.registerProperty wins over @property');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '3px'
+}, (name1) => {
+ with_at_property({
+ name: name1,
+ syntax: '"<integer>"',
+ inherits: false,
+ initialValue: '6'
+ }, (name2) => {
+ assert_equals(name1, name2);
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '6');
+ });
+}, '@property later in document order wins');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '10px'
+}, (name1) => {
+ with_at_property({
+ name: name1,
+ syntax: '"<length>"',
+ inherits: true,
+ initialValue: '20px'
+ }, (name2) => {
+ assert_equals(name1, name2);
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '20px');
+ });
+}, '@property later in document order wins (overridding definition with inherits=true)');
+
+test_with_at_property({
+ syntax: '"<length>"',
+ inherits: true,
+ initialValue: '10px'
+}, (name1) => {
+ with_at_property({
+ name: name1,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '20px'
+ }, (name2) => {
+ assert_equals(name1, name2);
+ assert_equals(getComputedStyle(div).getPropertyValue(name2), '20px');
+ });
+}, '@property later in document order wins (overridding definition with inherits=false)');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 4px;
+ }
+
+ @property ${name} {
+ syntax: "<color>";
+ inherits: false;
+ initial-value: red;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(255, 0, 0)');
+ });
+}, '@property later in stylesheet wins');
+
+test(() => {
+ let name = generate_name();
+ CSS.registerProperty({
+ name: name,
+ syntax: '<color>',
+ inherits: false,
+ initialValue: 'green'
+ });
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'rgb(0, 128, 0)');
+}, 'CSS.registerProperty determines the registration when uncontested');
+
+test(() => {
+ let name = generate_name();
+
+ // ${name} is initially not registered, hence has no initial value.
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '');
+
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '10px'
+ }, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '10px');
+ });
+
+ // When the style node is removed, ${name} should be unregistered again.
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '');
+}, '@property registrations are cleared when rule removed');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`div { ${name}: calc(1px + 1px); }`, () => {
+ // ${name} should be a token sequence at this point.
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'calc(1px + 1px)');
+
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+ }, () => {
+ // ${name} is now a <length>, hence the calc() should be simplified.
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '2px');
+ });
+
+ // ${name} should be a token sequence again.
+ assert_equals(getComputedStyle(div).getPropertyValue(name), 'calc(1px + 1px)');
+ });
+}, 'Computed value becomes token sequence when @property is removed');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`#outer { ${name}: 10px; }`, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '10px');
+
+ with_at_property({
+ name: name,
+ syntax: '"<length>"',
+ inherits: false,
+ initialValue: '0px'
+ }, () => {
+ // ${name} is no longer inherited
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '0px');
+ });
+
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '10px');
+ });
+}, 'Inherited status is reflected in computed styles when @property is removed');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 1px;
+ }
+
+ @property ${name} {
+ inherits: false;
+ initial-value: green;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+}, 'Invalid @property rule (missing syntax) does not overwrite previous valid rule');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 1px;
+ }
+
+ @property ${name} {
+ syntax: "<color>";
+ initial-value: green;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+}, 'Invalid @property rule (missing inherits descriptor) does not overwrite previous valid rule');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 1px;
+ }
+
+ @property ${name} {
+ syntax: "<color>";
+ inherits: false;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+}, 'Invalid @property rule (missing initial-value) does not overwrite previous valid rule');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`
+ @property ${name} {
+ syntax: "<color>";
+ inherits: false;
+ }
+
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 1px;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+}, 'Previous invalid rule does not prevent valid rule from causing registration');
+
+test(() => {
+ let name = generate_name();
+
+ with_style_node(`
+ @property ${name} {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 1px;
+ quite-unknown: 200;
+ }
+ `, () => {
+ assert_equals(getComputedStyle(div).getPropertyValue(name), '1px');
+ });
+}, 'Unknown descriptors are ignored and do not invalidate rule');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/font-size-animation.html b/testing/web-platform/tests/css/css-properties-values-api/font-size-animation.html
new file mode 100644
index 0000000000..4b8ce1c255
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/font-size-animation.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/3751">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ CSS.registerProperty({
+ name: '--length',
+ syntax: '<length>',
+ initialValue: '0px',
+ inherits: false
+ });
+</script>
+<style>
+ @keyframes font_size_animation {
+ from {
+ font-size: 10px;
+ width: 10em;
+ --length: 10em;
+ }
+ to {
+ font-size: 20px;
+ width: 20em;
+ --length: 20em;
+ }
+ }
+ #target1 {
+ font-size: 1px;
+ animation: font_size_animation 10s -5s linear paused;
+ }
+</style>
+<div id=target1></div>
+<script>
+ test(function() {
+ // At the time of writing, the correct (absolute) answer is not
+ // yet defined. However, whatever the correct answer is, there should
+ // be no difference in 'width' and a custom property registered with
+ // "<length>".
+ //
+ // See https://github.com/w3c/csswg-drafts/issues/3751
+ assert_equals(getComputedStyle(target1).getPropertyValue('width'),
+ getComputedStyle(target1).getPropertyValue('--length'));
+ }, 'Animating font-size handled identically for standard and custom properties');
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/get-computed-style-enumeration.html b/testing/web-platform/tests/css/css-properties-values-api/get-computed-style-enumeration.html
new file mode 100644
index 0000000000..909247f69a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/get-computed-style-enumeration.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ @property --inherited-length-1 {
+ syntax: "<length>";
+ inherits: true;
+ initial-value: 10px;
+ }
+ @property --inherited-length-2 {
+ syntax: "<length>";
+ inherits: true;
+ initial-value: 20px;
+ }
+ @property --non-inherited-length-1 {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 30px;
+ }
+ @property --non-inherited-length-2 {
+ syntax: "<length>";
+ inherits: false;
+ initial-value: 40px;
+ }
+ @property --inherited-no-initial-value-1 {
+ syntax: "*";
+ inherits: true;
+ }
+ @property --inherited-no-initial-value-2 {
+ syntax: "*";
+ inherits: true;
+ }
+ @property --inherited-no-initial-value-3 {
+ syntax: "*";
+ inherits: true;
+ }
+ @property --non-inherited-no-initial-value-1 {
+ syntax: "*";
+ inherits: false;
+ }
+ @property --non-inherited-no-initial-value-2 {
+ syntax: "*";
+ inherits: false;
+ }
+ @property --non-inherited-no-initial-value-3 {
+ syntax: "*";
+ inherits: false;
+ }
+ #parent {
+ --inherited-no-initial-value-2: parent-A;
+ --non-inherited-no-initial-value-2: parent-B;
+ --non-registered-property-2: parent-C;
+ }
+ #node {
+ --inherited-length-1: 50px;
+ --non-inherited-length-1: 60px;
+ --inherited-no-initial-value-1: child-A;
+ --non-inherited-no-initial-value-1: child-B;
+ --non-registered-property-1: child-C;
+ }
+</style>
+<div id="parent"><div id="node"></div></div>
+<script>
+ let style = window.getComputedStyle(document.getElementById("node"));
+ let properties = new Map();
+ Array.from(style).filter(name => name.startsWith("--"))
+ .forEach(name => properties.set(name, style.getPropertyValue(name)));
+
+ test(() => {
+ assert_equals(properties.get("--inherited-length-1"), "50px");
+ assert_equals(properties.get("--non-inherited-length-1"), "60px");
+ assert_equals(properties.get("--inherited-no-initial-value-1"), "child-A");
+ assert_equals(properties.get("--non-inherited-no-initial-value-1"), "child-B");
+ assert_equals(properties.get("--non-registered-property-1"), "child-C");
+ }, "Custom properties specified on the node exposed when enumerating computed style.");
+
+ test(() => {
+ assert_equals(properties.get("--inherited-no-initial-value-2"), "parent-A");
+ assert_equals(properties.get("--non-registered-property-2"), "parent-C");
+ }, "Inherited custom properties specified on the parent exposed when enumerating computed style.");
+
+ test(() => {
+ assert_equals(properties.get("--inherited-length-2"), "20px");
+ assert_equals(properties.get("--non-inherited-length-2"), "40px");
+ }, "Unspecified properties with initial values exposed when enumerating computed style.");
+
+ test(() => {
+ assert_false(properties.has("--non-inherited-no-initial-value-2"));
+ }, "Non-inherited custom properties specified on the parent without initial values not exposed when enumerating computed style.");
+
+ test(() => {
+ assert_false(properties.has("--inherited-no-initial-value-3"), "inherited");
+ assert_false(properties.has("--non-inherited-no-initial-value-3"), "non-inherited");
+ }, "Unspecified properties without initial values not exposed when enumerating computed style.");
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/idlharness.html b/testing/web-platform/tests/css/css-properties-values-api/idlharness.html
new file mode 100644
index 0000000000..6f053757c3
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/idlharness.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<title>CSS Properties Values API IDL tests</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+ "use strict";
+
+ idl_test(
+ ["css-properties-values-api"],
+ ["cssom"]
+ // No objects
+ );
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/invalid-at-computed-value-time.html b/testing/web-platform/tests/css/css-properties-values-api/invalid-at-computed-value-time.html
new file mode 100644
index 0000000000..56a7756f40
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/invalid-at-computed-value-time.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-propertydescriptor-inherits" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#register-a-custom-property" />
+<link rel="help" href="https://drafts.csswg.org/css-variables-2/#invalid-at-computed-value-time" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=outer><div id=inner></div></div>
+<script>
+
+test(function(){
+ CSS.registerProperty({name: '--p1', syntax: '*', initialValue: '0px', inherits: true});
+ outer.style = '--p1: 42px';
+ inner.style = '--p1: var(--undefined)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p1'), '');
+
+ CSS.registerProperty({name: '--p2', syntax: '*', initialValue: '0px', inherits: false});
+ outer.style = '--p2: 42px';
+ inner.style = '--p2: var(--undefined)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p2'), '');
+}, "Universal syntax: Undefined reference results in guaranteed-invalid value.");
+
+test(function(){
+ CSS.registerProperty({name: '--p3', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = '--p3: 42px';
+ inner.style = '--p3: var(--undefined)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p3'), '42px');
+
+ CSS.registerProperty({name: '--p4', syntax: '<length>', initialValue: '0px', inherits: false});
+ outer.style = '--p4: 42px';
+ inner.style = '--incompatible: nolength; --p4: var(--undefined)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p4'), '0px');
+}, "Undefined reference results in unsetting the property.");
+
+test(function(){
+ CSS.registerProperty({name: '--p5', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = '--p5: 42px';
+ inner.style = '--incompatible: nolength; --p5: var(--incompatible)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p5'), '42px');
+
+ CSS.registerProperty({name: '--p6', syntax: '<length>', initialValue: '0px', inherits: false});
+ outer.style = '--p6: 42px';
+ inner.style = '--incompatible: nolength; --p6: var(--incompatible)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p6'), '0px');
+}, "Syntax-incompatible reference results in unsetting the property.");
+
+test(function(){
+ CSS.registerProperty({name: '--p7', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = '--p7: 42px';
+ inner.style = '--p7: var(--undefined, nolength)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p7'), '42px');
+
+ CSS.registerProperty({name: '--p8', syntax: '<length>', initialValue: '0px', inherits: false});
+ outer.style = '--p8: 42px';
+ inner.style = 'var(--undefined, nolength)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--p8'), '0px');
+}, "Syntax-incompatible fallback results in unsetting the property.");
+
+test(function(){
+ const rootElement = document.documentElement;
+ CSS.registerProperty({name: '--p9', syntax: '<length>', initialValue: '0px', inherits: true});
+ rootElement.style = '--p9: var(--undefined);';
+ assert_equals(getComputedStyle(rootElement).getPropertyValue('--p9'), '0px');
+
+ CSS.registerProperty({name: '--p10', syntax: '<length>', initialValue: '0px', inherits: true});
+ rootElement.style = '--incompatible: nolength; --p10: var(--incompatible)';
+ assert_equals(getComputedStyle(rootElement).getPropertyValue('--p10'), '0px');
+
+ CSS.registerProperty({name: '--p11', syntax: '<length>', initialValue: '0px', inherits: true});
+ rootElement.style = '--p11: var(--undefined, nolength)';
+ assert_equals(getComputedStyle(rootElement).getPropertyValue('--p11'), '0px');
+}, "Unsetting inherited properties on the root results in initial value.");
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/property-cascade.html b/testing/web-platform/tests/css/css-properties-values-api/property-cascade.html
new file mode 100644
index 0000000000..68417a578a
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/property-cascade.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function" />
+<meta name="assert" content="Verifies that registering a property does not affect the cascade" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+
+#outer { color: rgb(1, 1, 1); }
+#inner {
+ --my-color: rgb(2, 2, 2);
+ --my-color: url(not-a-color);
+ color: var(--my-color);
+}
+
+</style>
+
+<div id=outer>
+ <div id=inner></div>
+</div>
+
+<script>
+
+test(function(){
+ // Because var(--my-color) is invalid, our color declaration should behave
+ // like color:unset, i.e. it should compute to the inherited color.
+ assert_equals(getComputedStyle(inner).color, 'rgb(1, 1, 1)');
+
+ CSS.registerProperty({
+ name: '--my-color',
+ syntax: '<color>',
+ initialValue: 'rgb(3, 3, 3)',
+ inherits: false
+ });
+
+ // After registering, var(--my-color) is still invalid. The important thing
+ // here is that the computed value of color is the initialValue of
+ // --my-color, and not rgb(2, 2, 2).
+ assert_equals(getComputedStyle(inner).color, 'rgb(3, 3, 3)');
+}, 'Registering a property does not affect cascade');
+
+test(function(){
+ CSS.registerProperty({
+ name: '--my-color-2',
+ syntax: '<color>',
+ initialValue: 'rgb(4, 4, 4)',
+ inherits: false
+ });
+
+ let element = document.createElement('div');
+ element.style = `
+ --my-color-2: rgb(2, 2, 2);
+ --my-color-2: url(not-a-color);
+ color: var(--my-color-2);
+ `;
+
+ outer.appendChild(element);
+
+ assert_equals(getComputedStyle(element).color, 'rgb(4, 4, 4)');
+}, 'Registering a property does not affect parsing');
+
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/register-property-syntax-parsing.html b/testing/web-platform/tests/css/css-properties-values-api/register-property-syntax-parsing.html
new file mode 100644
index 0000000000..537a921efc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/register-property-syntax-parsing.html
@@ -0,0 +1,287 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#supported-syntax-strings" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test_count = 0;
+
+function assert_valid(syntax, initialValue) {
+ // No actual assertions, this just shouldn't throw
+ test(function() {
+ var name = '--syntax-test-' + (test_count++);
+ CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false});
+ }, "syntax:'" + syntax + "', initialValue:'" + initialValue + "' is valid");
+}
+
+function assert_invalid(syntax, initialValue) {
+ test(function(){
+ var name = '--syntax-test-' + (test_count++);
+ assert_throws_dom("SyntaxError",
+ () => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false}));
+ }, "syntax:'" + syntax + "', initialValue:'" + initialValue + "' is invalid");
+}
+
+assert_valid("*", "a");
+assert_valid(" * ", "b");
+assert_valid("<length>", "2px");
+assert_valid(" <number>", "5");
+assert_valid("<percentage> ", "10%");
+assert_valid("<color>+", "red");
+assert_valid(" <length>+ | <percentage>", "2px 8px");
+assert_valid(" <length>+ | <color>#", "red, blue");
+assert_valid("<length>|<percentage>|<length-percentage>", "2px"); // Valid but silly
+assert_valid("<color> | <image> | <url> | <integer> | <angle>", "red");
+assert_valid("<time> | <resolution> | <transform-list> | <custom-ident>", "red");
+assert_valid("\t<color>\n| foo", "foo");
+
+assert_valid("*", ":> hello");
+assert_valid("*", "([ brackets ]) { yay (??)}");
+assert_valid("*", "yep 'this is valid too'");
+assert_valid("*", "unmatched opening bracket is valid :(");
+assert_valid("*", '"');
+assert_valid("*", "default");
+
+assert_valid("<length>", "0");
+assert_valid("<length>", "10px /*:)*/");
+assert_valid("<length>", " calc(-2px)");
+assert_valid("<length>", "calc(2px*4 + 10px)");
+assert_valid("<length>", "7.1e-4cm");
+assert_valid("<length>", "calc(7in - 12px)");
+assert_valid("<length>+", "2px 7px calc(8px)");
+assert_valid("<length>#", "2px, 7px, calc(8px)");
+assert_valid("<percentage>", "-9.3e3%");
+assert_valid("<length-percentage>", "-54%");
+assert_valid("<length-percentage>", "0");
+assert_valid("<length-percentage>", "calc(-11px + 10.4%)");
+assert_valid("<length>", "10vmin");
+assert_valid("<percentage> | <length>+", "calc(100vh - 10px) 30px");
+
+assert_valid("<number>", "-109");
+assert_valid("<number>", "2.3e4");
+assert_valid("<number>", "calc(1 / 2)");
+assert_valid("<integer>", "-109");
+assert_valid("<integer>", "19");
+assert_valid("<integer>", "calc(1)");
+assert_valid("<integer>", "calc(1 + 2)");
+assert_valid("<integer>", "calc(3.1415)");
+assert_valid("<integer>", "calc(3.1415 + 3.1415)");
+
+assert_valid("<angle>", "10deg");
+assert_valid("<angle>", "20.5rad");
+assert_valid("<angle>", "calc(50grad + 3.14159rad)");
+assert_valid("<time>", "2s");
+assert_valid("<time>", "calc(2s - 9ms)");
+assert_valid("<resolution>", "10dpi");
+assert_valid("<resolution>", "3dPpX");
+assert_valid("<transform-function>", "translateX(2px)");
+assert_valid("<transform-function>|<integer>", "5");
+assert_valid("<transform-function>|<integer>", "scale(2)");
+assert_valid("<transform-function>+", "translateX(2px) rotate(42deg)");
+assert_valid("<transform-list>", "scale(2)");
+assert_valid("<transform-list>", "translateX(2px) rotate(20deg)");
+
+// <string> is accepted in https://github.com/w3c/css-houdini-drafts/issues/1103
+assert_valid("<string>", "'foo bar'");
+assert_valid("<string>", " 'foo bar' ");
+assert_valid("<string>", "'foo bar");
+assert_valid("<string>", `'"foo" bar'`);
+assert_valid("<string>", '"bar baz"');
+assert_valid("<string>", `"bar 'baz'"`);
+assert_valid("<string>", `"bar 'baz'`);
+assert_valid("<string>+", "'foo' 'bar'");
+assert_valid("<string>#", "'foo', 'bar'");
+assert_valid("<string>+ | <string>#", "'foo' 'bar'");
+assert_valid("<string>+ | <string>#", " 'foo' 'bar'");
+assert_valid("<string>+ | <string>#", `'foo' "bar"`);
+assert_valid("<string>+ | <string>#", `'foo' "bar`);
+assert_valid("<string># | <string>+", "'foo', 'bar'");
+assert_valid("<string># | <string>+", "'foo', 'bar' ");
+assert_valid("<string># | <string>+", `"foo", 'bar'`);
+assert_valid("<string># | <string>+", `"foo", 'bar`);
+
+assert_valid("<color>", "rgb(12, 34, 56)");
+assert_valid("<color>", "lightgoldenrodyellow");
+assert_valid("<image>", "url(a)");
+assert_valid("<image>", "linear-gradient(yellow, blue)");
+assert_valid("<url>", "url(a)");
+
+assert_valid("<color>+", "yellow blue");
+assert_valid("<color>+", "yellow blue ");
+assert_valid("<color>+ | <color>", "yellow blue");
+assert_valid("<color> | <color>+", "yellow blue");
+assert_valid("<color># | <color>", "yellow, blue");
+assert_valid("<color> | <color>#", "yellow, blue");
+assert_valid("<color># | <color>+", "yellow blue");
+assert_valid("<color>+ | <color>#", "yellow, blue");
+assert_valid("<color>+ | yellow", "yellow blue");
+assert_valid("yellow", "yellow");
+assert_valid("yellow | <color>+", "yellow blue");
+assert_valid("<color># | yellow", "yellow, blue");
+assert_valid("yellow | <color>#", "yellow, blue");
+assert_valid("<transform-list> | <transform-function> ", "scale(2) rotate(90deg)");
+assert_valid("<transform-function> | <transform-list>", "scale(2) rotate(90deg)");
+assert_valid("<transform-list> | <transform-function>+ ", "scale(2) rotate(90deg)");
+assert_valid("<transform-function>+ | <transform-list>", "scale(2) rotate(90deg)");
+assert_valid("<transform-list> | <transform-function># ", "scale(2) rotate(90deg)");
+assert_valid("<transform-function># | <transform-list>", "scale(2) rotate(90deg)");
+assert_valid("<transform-list> | <transform-function># ", "scale(2), rotate(90deg)");
+assert_valid("<transform-function># | <transform-list>", "scale(2), rotate(90deg)");
+assert_valid("<transform-list>", "scale(2) rotate(90deg) ");
+assert_valid("<integer>+ | <percentage>+ | <length>+ ", "1");
+assert_valid("<integer>+ | <percentage>+ | <length>+ ", "1 1");
+assert_valid("<integer>+ | <percentage>+ | <length>+ ", "1%");
+assert_valid("<integer>+ | <percentage>+ | <length>+ ", "1% 1%");
+assert_valid("<integer>+ | <percentage>+ | <length>+ ", "1px");
+assert_valid("<integer>+ | <percentage>+ | <length>+ ", "1px 1px");
+
+assert_valid("banana", "banana");
+assert_valid("bAnAnA", "bAnAnA");
+assert_valid("ba-na-nya", "ba-na-nya");
+assert_valid("banana", "banan\\61");
+assert_valid("banan\\61", "banana");
+assert_valid("<custom-ident>", "banan\\61");
+assert_valid("big | bigger | BIGGER", "bigger");
+assert_valid("foo+|bar", "foo foo foo");
+
+assert_valid("banana\t", "banana");
+assert_valid("\nbanana\r\n", "banana");
+assert_valid("ba\f\n|\tna\r|nya", "nya");
+
+assert_valid(null, "null");
+assert_valid(undefined, "undefined");
+assert_valid(["array"], "array");
+
+assert_valid("\\1F914", "🤔");
+assert_valid("hmm\\1F914", "hmm🤔");
+assert_valid("\\1F914hmm", "🤔hmm");
+assert_valid("\\1F914 hmm", "🤔hmm");
+assert_valid("\\1F914\\1F914", "🤔🤔");
+
+// Invalid syntax
+assert_invalid("<color>#", "yellow blue");
+assert_invalid("banana,nya", "banana");
+assert_invalid("<\\6c ength>", "10px");
+assert_invalid("<banana>", "banana");
+assert_invalid("<Number>", "10");
+assert_invalid("<length", "10px");
+assert_invalid("<LENGTH>", "10px");
+assert_invalid("< length>", "10px");
+assert_invalid("<length >", "10px");
+assert_invalid("<length> +", "10px");
+assert_invalid("<transform-list>+", "scale(2)");
+assert_invalid("<transform-list>#", "scale(2)");
+
+assert_invalid("<length>++", "10px");
+assert_invalid("<length>##", "10px");
+assert_invalid("<length>+#", "10px");
+assert_invalid("<length>#+", "10px");
+assert_invalid("<length> | *", "10px");
+assert_invalid("<length>+", "2px,7px,calc(8px)");
+assert_invalid("<length>#", "2px 7px calc(8px)");
+assert_invalid("*|banana", "banana");
+assert_invalid("|banana", "banana");
+assert_invalid("*+", "banana");
+assert_invalid("|", "banana");
+assert_invalid(" |", "banana");
+assert_invalid("||", "banana");
+assert_invalid("foo bar", "foo bar");
+assert_invalid("foo foo foo", "foo foo foo");
+assert_invalid("foo § bar", "foo § bar");
+assert_invalid("foo \\1F914 bar", "foo \\1F914 bar");
+assert_invalid("<length> <number>", "0px 0");
+assert_invalid("<length> <length> <length>", "0px 0px 0px");
+
+assert_invalid("<integer>+ | <percentage>+ | <length>+ ", "1 1%");
+assert_invalid("<integer>+ | <percentage>+ | <length>+ ", "1% 1");
+assert_invalid("<integer>+ | <percentage>+ | <length>+ ", "1px 1");
+assert_invalid("<integer>+ | <percentage>+ | <length>+ ", "1 1px");
+assert_invalid("<integer>+ | <percentage>+ | <length>+ ", "1px 1%");
+assert_invalid("<integer>+ | <percentage>+ | <length>+ ", "1% 1px");
+
+assert_invalid("initial", "initial");
+assert_invalid("inherit", "inherit");
+assert_invalid("unset", "unset");
+assert_invalid("revert", "revert");
+assert_invalid("revert-layer", "revert-layer");
+assert_invalid("default", "default");
+assert_invalid("<length>|initial", "10px");
+assert_invalid("<length>|INHERIT", "10px");
+assert_invalid("<percentage>|unsEt", "2%");
+assert_invalid("<color>|REVert", "red");
+assert_invalid("<integer>|deFAUlt", "1");
+
+// Invalid initialValue
+// The 5 tests that follow are not clearly backed by the specification,
+// but they're probably a good idea and we should change the spec. See
+// https://github.com/w3c/css-houdini-drafts/issues/1076 .
+assert_invalid("*", "initial");
+assert_invalid("*", "inherit");
+assert_invalid("*", "unset");
+assert_invalid("*", "revert");
+assert_invalid("*", "revert-layer");
+// ... end possibly-invalid tests.
+assert_invalid("<custom-ident>", "initial");
+assert_invalid("<custom-ident>", "inherit");
+assert_invalid("<custom-ident>", "unset");
+assert_invalid("<custom-ident>", "revert");
+assert_invalid("<custom-ident>", "revert-layer");
+assert_invalid("<custom-ident>", "default");
+assert_invalid("<custom-ident>+", "foo initial bar");
+assert_invalid("<custom-ident>+", "foo inherit bar");
+assert_invalid("<custom-ident>+", "foo unset bar");
+assert_invalid("<custom-ident>+", "foo revert bar");
+assert_invalid("<custom-ident>+", "foo revert-layer bar");
+assert_invalid("<custom-ident>+", "foo default bar");
+
+assert_invalid("*", ")");
+assert_invalid("*", "([)]");
+assert_invalid("*", "whee!");
+assert_invalid("*", '"\n');
+assert_invalid("*", "url(moo '')");
+assert_invalid("*", "semi;colon");
+assert_invalid("*", "var(invalid var ref)");
+assert_invalid("*", "var(--foo)");
+
+assert_invalid("banana", "bAnAnA");
+assert_invalid("<length>", "var(--moo)");
+assert_invalid("<length>", "10");
+assert_invalid("<length>", "10%");
+assert_invalid("<length>", "calc(5px + 10%)");
+assert_invalid("<length>", "calc(5px * 3px / 6px)");
+assert_invalid("<length>", "10em");
+assert_invalid("<length>", "calc(4px + 3em)");
+assert_invalid("<length>", "calc(4px + calc(8 * 2em))");
+assert_invalid("<length>+", "calc(2ex + 16px)");
+assert_invalid("<length>+", "10px calc(20px + 4rem)");
+assert_invalid("<length>+", "");
+assert_invalid("<length>#", "");
+assert_invalid("<length>", "10px;");
+assert_invalid("<length-percentage>", "calc(2px + 10% + 7ex)");
+assert_invalid("<percentage>", "0");
+assert_invalid("<integer>", "1.0");
+assert_invalid("<integer>", "1e0");
+assert_invalid("<number>|foo", "foo var(--foo, bla)");
+assert_invalid("Foo | bar", "foo");
+assert_invalid("Foo | bar", "Bar");
+
+assert_invalid("<angle>", "0");
+assert_invalid("<angle>", "10%");
+assert_invalid("<time>", "2px");
+assert_invalid("<resolution>", "10");
+
+// "The allowed range of <resolution> values always excludes negative values"
+// https://www.w3.org/TR/css-values-4/#resolution-value
+assert_invalid("<resolution>", "-5.3dpcm");
+
+assert_invalid("<transform-function>", "scale()");
+assert_invalid("<transform-list>", "scale()");
+assert_invalid("<transform-list>+", "translateX(2px) rotate(20deg)");
+assert_invalid("<color>", "fancy-looking");
+assert_invalid("<image>", "banana.png");
+assert_invalid("<url>", "banana.png");
+
+assert_invalid("<string>", "foo bar'");
+assert_invalid("<string>", 'foo bar"');
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/register-property.html b/testing/web-platform/tests/css/css-properties-values-api/register-property.html
new file mode 100644
index 0000000000..feb6c28d47
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/register-property.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#register-a-custom-property" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=target></div>
+<script>
+// Tests for error checking during property registration
+
+test(function() {
+ assert_throws_js(TypeError, () => CSS.registerProperty());
+ assert_throws_js(TypeError, () => CSS.registerProperty(undefined));
+ assert_throws_js(TypeError, () => CSS.registerProperty(true));
+ assert_throws_js(TypeError, () => CSS.registerProperty(2));
+ assert_throws_js(TypeError, () => CSS.registerProperty("css"));
+ assert_throws_js(TypeError, () => CSS.registerProperty(null));
+}, "registerProperty requires a Dictionary type");
+
+test(function() {
+ // Valid property names, shouldn't throw
+ CSS.registerProperty({name: '--name1', inherits: false});
+ CSS.registerProperty({name: '--name2, no need for escapes', inherits: false});
+ CSS.registerProperty({name: ['--name', 3], inherits: false});
+
+ // Invalid property names
+ assert_throws_js(TypeError, () => CSS.registerProperty({}));
+ assert_throws_dom("SyntaxError", () => CSS.registerProperty({name: 'no-leading-dash', inherits: false}));
+ assert_throws_dom("SyntaxError", () => CSS.registerProperty({name: '', inherits: false}));
+ assert_throws_dom("SyntaxError", () => CSS.registerProperty({name: '\\--name', inherits: false}));
+}, "registerProperty requires a name matching <custom-property-name>");
+
+test(function() {
+ CSS.registerProperty({name: '--syntax-test-1', syntax: '*', inherits: false});
+ CSS.registerProperty({name: '--syntax-test-2', syntax: ' * ', inherits: false});
+ assert_throws_dom("SyntaxError",
+ () => CSS.registerProperty({name: '--syntax-test-3', syntax: 'length', inherits: false}));
+}, "registerProperty only allows omitting initialValue if syntax is '*'");
+
+test(function() {
+ CSS.registerProperty({name: '--re-register', syntax: '<length>', initialValue: '0px', inherits: false});
+ assert_throws_dom('InvalidModificationError',
+ () => CSS.registerProperty({name: '--re-register', syntax: '<percentage>', initialValue: '0%', inherits: false}));
+}, "registerProperty fails for an already registered property");
+
+test(function(){
+ CSS.registerProperty({name: '--inherit-test-1', syntax: '<length>', initialValue: '0px', inherits: true});
+ CSS.registerProperty({name: '--inherit-test-2', syntax: '<length>', initialValue: '0px', inherits: false});
+ assert_throws_js(TypeError, () => CSS.registerProperty({name: '--inherit-test-3', syntax: '<length>', initialValue: '0px'}));
+}, "registerProperty requires inherits");
+
+test(function(){
+ try {
+ let name = generate_name();
+
+ target.style.setProperty(name, 'green');
+ target.style.transitionProperty = name;
+ target.style.transitionDuration = '1s';
+ target.style.transitionTimingFunction = 'steps(1, end)';
+
+ assert_equals(getComputedStyle(target).getPropertyValue(name), 'green');
+
+ CSS.registerProperty({
+ name: name,
+ syntax: '<color>',
+ initialValue: 'red',
+ inherits: false
+ });
+
+ assert_equals(getComputedStyle(target).getPropertyValue(name), 'rgb(0, 128, 0)');
+ } finally {
+ target.style = '';
+ }
+}, 'Registering a property should not cause a transition');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-properties-inheritance.html b/testing/web-platform/tests/css/css-properties-values-api/registered-properties-inheritance.html
new file mode 100644
index 0000000000..7c8c2656c6
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-properties-inheritance.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-propertydescriptor-inherits" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#register-a-custom-property" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+#outer {
+ --inherited-length-1: 10px;
+ --inherited-length-2: var(--non-inherited-length-1);
+ --inherited-length-3: 30px;
+ --non-inherited-length-1: 22px;
+ --non-inherited-length-3: calc(var(--non-inherited-length-2) * 10);
+}
+
+#inner {
+ --inherited-length-3: 15px;
+ --non-inherited-length-1: 40px;
+ --non-inherited-length-2: 90px;
+}
+</style>
+<div id=outer><div id=inner></div></div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--inherited-length-1', syntax: '<length>', initialValue: '1px', inherits: true});
+ CSS.registerProperty({name: '--inherited-length-2', syntax: '<length>', initialValue: '2px', inherits: true});
+ CSS.registerProperty({name: '--inherited-length-3', syntax: '<length>', initialValue: '3px', inherits: true});
+ CSS.registerProperty({name: '--non-inherited-length-1', syntax: '<length>', initialValue: '4px', inherits: false});
+ CSS.registerProperty({name: '--non-inherited-length-2', syntax: '<length>', initialValue: '5px', inherits: false});
+ CSS.registerProperty({name: '--non-inherited-length-3', syntax: '<length>', initialValue: '6px', inherits: false});
+
+ outerComputedStyle = getComputedStyle(outer);
+ assert_equals(outerComputedStyle.getPropertyValue('--inherited-length-1'), '10px');
+ assert_equals(outerComputedStyle.getPropertyValue('--inherited-length-2'), '22px');
+ assert_equals(outerComputedStyle.getPropertyValue('--inherited-length-3'), '30px');
+ assert_equals(outerComputedStyle.getPropertyValue('--non-inherited-length-1'), '22px');
+ assert_equals(outerComputedStyle.getPropertyValue('--non-inherited-length-2'), '5px');
+ assert_equals(outerComputedStyle.getPropertyValue('--non-inherited-length-3'), '50px');
+
+ innerComputedStyle = getComputedStyle(inner);
+ assert_equals(innerComputedStyle.getPropertyValue('--inherited-length-1'), '10px');
+ assert_equals(innerComputedStyle.getPropertyValue('--inherited-length-2'), '22px');
+ assert_equals(innerComputedStyle.getPropertyValue('--inherited-length-3'), '15px');
+ assert_equals(innerComputedStyle.getPropertyValue('--non-inherited-length-1'), '40px');
+ assert_equals(innerComputedStyle.getPropertyValue('--non-inherited-length-2'), '90px');
+ assert_equals(innerComputedStyle.getPropertyValue('--non-inherited-length-3'), '6px');
+}, "Registered properties are correctly inherited (or not) depending on the inherits flag.");
+
+test(function(){
+ CSS.registerProperty({name: '--initial-length-1', syntax: '<length>', initialValue: '0px', inherits: false});
+ outer.style = '--initial-length-1: notalength';
+ inner.style = '--initial-length-1: inherit';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--initial-length-1'), '0px');
+}, "Explicitly inheriting from a parent with an invalid value results in initial value.");
+
+test(function(){
+ CSS.registerProperty({name: '--initial-length-2', syntax: '<length>', initialValue: '0px', inherits: false});
+ inner.style = '--initial-length-2: inherit';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--initial-length-2'), '0px');
+}, "Explicitly inheriting from a parent with no value results in initial value.");
+
+test(function(){
+ CSS.registerProperty({name: '--initial-length-3', syntax: '<length>', initialValue: '0px', inherits: false});
+ outer.style = '--initial-length-3: 100px';
+ inner.style = '--initial-length-3: inherit';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--initial-length-3'), '100px');
+}, "Explicitly inheriting from a parent with a value results in that value.");
+
+test(function(){
+ CSS.registerProperty({name: '--inherited-length-4', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = '--inherited-length-4: 42px';
+ inner.style = '--inherited-length-4: var(--undefined)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--inherited-length-4'), '42px');
+}, "Reference to undefined variable results in inherited value");
+
+test(function(){
+ CSS.registerProperty({name: '--inherited-length-5', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = '--inherited-length-5: 42px';
+ inner.style = '--incompatible: nolength; --inherited-length-5: var(--incompatible)';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--inherited-length-5'), '42px');
+}, "Reference to syntax-incompatible variable results in inherited value");
+
+test(function(){
+ CSS.registerProperty({name: '--inherited-em', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = 'font-size: 11px; --inherited-em: 10em;';
+ inner.style = 'font-size: 22px; --unregistered:var(--inherited-em);';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--unregistered'), '110px');
+}, "Font-relative units are absolutized before before inheritance");
+
+test(function(){
+ CSS.registerProperty({name: '--calc-length', syntax: '<length>', initialValue: '0px', inherits: true});
+ outer.style = '--calc-length: calc(10px + 10px);';
+ inner.style = '--unregistered:var(--calc-length);';
+ assert_equals(getComputedStyle(inner).getPropertyValue('--unregistered'), '20px');
+}, "Calc expressions are resolved before inheritance");
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-001.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-001.html
new file mode 100644
index 0000000000..320b44d82e
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-001.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Variables Test: Style changes on registered properties using variables</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-variables-1/#using-variables">
+<meta name="assert" content="A change in the custom property declaration must be propagated to all the descendants">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="outer">
+ <div id="inbetween">
+ <div id="inner"></div>
+ </div>
+</div>
+<script>
+ "use strict";
+ test( function () {
+ outer.style.cssText = '';
+ inbetween.style.cssText = '';
+ inner.style.cssText = 'color: var(--color1)';
+ let initialValue = getComputedStyle(inner).getPropertyValue('color');
+ assert_equals(initialValue, "rgb(0, 0, 0)", "Initial value");
+
+ inbetween.style.cssText = 'color: green';
+ let inheritedValue = getComputedStyle(inner).getPropertyValue('color');
+ assert_equals(inheritedValue, "rgb(0, 128, 0)", "Inherited value");
+
+ CSS.registerProperty({name: '--color1', syntax: '<color>', initialValue: 'red', inherits: true});
+ let actualValue = getComputedStyle(inner).getPropertyValue('color');
+ assert_equals(actualValue, "rgb(255, 0, 0)", "Resolved value");
+ }, "New registered property declaration");
+
+ test( function () {
+ outer.style.cssText = '';
+ inbetween.style.cssText = '';
+ inner.style.cssText = 'color: var(--color2)';
+ let initialValue = getComputedStyle(inner).getPropertyValue('color');
+ assert_equals(initialValue, "rgb(0, 0, 0)", "Initial value");
+
+ outer.style.cssText = '--color2: blue';
+ inbetween.style.cssText = 'color: green';
+ let resolvedValue = getComputedStyle(inner).getPropertyValue('color');
+ assert_equals(resolvedValue, "rgb(0, 0, 255)", "Resolved value");
+
+ outer.style.cssText = '';
+ CSS.registerProperty({name: '--color2', syntax: '<color>', initialValue: 'red', inherits: true});
+ let actualValue = getComputedStyle(inner).getPropertyValue('color');
+ assert_equals(actualValue, "rgb(255, 0, 0)", "Resolved value");
+ }, "Registered property overrides a previous declaration ");
+</script>
+
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002-ref.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002-ref.html
new file mode 100644
index 0000000000..758e769d78
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Invalidation after CSS.registerProperty (reference)</title>
+ <style>
+ .failure {
+ background: pink;
+ }
+ #visibility {
+ visibility: hidden;
+ }
+ #display {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <p>This test PASS if you see no red in the list below.</p>
+ <ul>
+ <li>visibility <span id="visibility" class="failure">FAIL</span></li>
+ <li>display <span id="display" class="failure">FAIL</span></li>
+ </ul>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002.html
new file mode 100644
index 0000000000..a62461c7d8
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-change-style-002.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <title>Invalidation after CSS.registerProperty</title>
+ <link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#registered-custom-property">
+ <link rel="match" href="registered-property-change-style-002-ref.html">
+ <meta name="assert" content="Rendering should properly be updated after call to CSS.registerProperty.">
+ <style>
+ .failure {
+ background: pink;
+ }
+ #visibility {
+ visibility: var(--my-visibility, visible);
+ }
+ #display {
+ display: var(--my-display, inline-block);
+ }
+ </style>
+ </head>
+ <body>
+ <p>This test PASS if you see no red in the list below.</p>
+ <ul>
+ <li>visibility <span id="visibility" class="failure">FAIL</span></li>
+ <li>display <span id="display" class="failure">FAIL</span></li>
+ </ul>
+ <script>
+ document.documentElement.addEventListener("TestRendered", () => {
+ CSS.registerProperty({
+ name: "--my-visibility",
+ syntax: "*",
+ initialValue: "hidden",
+ inherits: false,
+ });
+ CSS.registerProperty({
+ name: "--my-display",
+ syntax: "*",
+ initialValue: "none",
+ inherits: false,
+ });
+ document.documentElement.removeAttribute("class");
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-computation.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-computation.html
new file mode 100644
index 0000000000..f4c718b139
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-computation.html
@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#calculation-of-computed-values" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+
+<style>
+#divWithFontSizeSet, #parentDiv {
+ font-size: 10px;
+ line-height: 20px;
+}
+</style>
+
+<div id=divWithFontSizeSet></div>
+<div id=parentDiv>
+ <div id=divWithFontSizeInherited></div>
+</div>
+<div id="ref"></div>
+
+<script>
+
+// Generate a property and temporarily set its value. Then call 'fn' with
+// the name of the generated property.
+function with_custom_property(element, reg, value, fn) {
+ if (element.id.length == 0)
+ throw 'The specified element must have an ID';
+
+ let name = generate_property(reg);
+
+ // Because we want to include the parsing step, insert a stylesheet
+ // node with textContent.
+ let node = document.createElement('style');
+ node.textContent = `#${element.id} { ${name}:${value}; }`;
+ document.body.append(node);
+
+ try {
+ fn(name);
+ } finally {
+ node.remove();
+ }
+}
+
+function assert_computed_value(element, syntax, value, expected) {
+ with_custom_property(element, syntax, value, (name) => {
+ let actual = getComputedStyle(element).getPropertyValue(name);
+ assert_equals(actual, expected);
+ });
+}
+
+// Computes an absolute reference value for some length.
+//
+// E.g. to figure out how many pixels '10vh' is, do length_ref('10vh').
+function length_ref(value, refnode = ref) {
+ try {
+ // The reference property 'min-height' is chosen arbitrarily, but
+ // avoid properties with "resolved value is used value"-behavior
+ // [1], as it may affect rounding, and custom properties do not
+ // have this behavior.
+ //
+ // [1] https://drafts.csswg.org/cssom/#resolved-values
+ const ref_property = 'min-height';
+ refnode.style = `${ref_property}: ${value}`;
+ return getComputedStyle(refnode).getPropertyValue(ref_property);
+ } finally {
+ refnode.style = '';
+ }
+}
+
+function test_computed_value(syntax, value, expected) {
+ test(function() {
+ assert_computed_value(divWithFontSizeSet, syntax, value, expected);
+ }, `${syntax} values are computed correctly [${value}]`);
+}
+
+test(function(){
+ const element = divWithFontSizeSet;
+ with_custom_property(element, '<length>', '14em', (name) => {
+ assert_computed_value(element, '<length>', `var(${name})`, '140px');
+ });
+}, '<length> values computed are correctly via var()-reference');
+
+test(function(){
+ const element = divWithFontSizeInherited;
+ with_custom_property(element, '<length>', '14em', (name) => {
+ assert_computed_value(element, '<length>', `var(${name})`, '140px');
+ });
+}, '<length> values computed are correctly via var()-reference when font-size is inherited');
+
+test(function(){
+ const element = divWithFontSizeInherited;
+ assert_computed_value(element, '<length>', '14em', '140px');
+}, '<length> values are computed correctly when font-size is inherited [14em]');
+
+test(function(){
+ const element = divWithFontSizeInherited;
+ assert_computed_value(element, '<length>', 'calc(14em + 10px)', '150px');
+}, '<length> values are computed correctly when font-size is inherited [calc(14em + 10px)]');
+
+test_computed_value('<length>', '12px', '12px');
+test_computed_value('<length>', '13vw', length_ref('13vw'));
+test_computed_value('<length>', '14em', '140px');
+test_computed_value('<length>', '15vmin', length_ref('15vmin'));
+test_computed_value('<length>', 'calc(16px - 7em + 10vh)', length_ref('calc(10vh - 54px)'));
+
+test_computed_value('<length>', '1in', '96px');
+test_computed_value('<length>', '2.54cm', '96px');
+test_computed_value('<length>', '25.4mm', '96px');
+test_computed_value('<length>', '6pc', '96px');
+test_computed_value('<length>', '72pt', '96px');
+
+test_computed_value('<length>', '10lh', '200px');
+
+test_computed_value('<length-percentage>', '17em', '170px');
+test_computed_value('<length-percentage>', '18%', '18%');
+test_computed_value('<length-percentage>', 'calc(19em - 2%)', 'calc(-2% + 190px)');
+
+test_computed_value('<length>#', '10px, 3em', '10px, 30px');
+test_computed_value('<length>#', '4em ,9px', '40px, 9px');
+test_computed_value('<length>#', '8em', '80px');
+
+test_computed_value('<length-percentage>#', '3% , 10vmax , 22px', ['3%', length_ref('10vmax'), '22px'].join(', '));
+test_computed_value('<length-percentage>#', 'calc(50% + 1em), 4px', 'calc(50% + 10px), 4px');
+test_computed_value('<length-percentage>#', 'calc(13% + 37px)', 'calc(13% + 37px)');
+
+test_computed_value('<length>+', '10px 3em', '10px 30px');
+test_computed_value('<length>+', '4em 9px', '40px 9px');
+
+test_computed_value('<length-percentage>+', '3% 10vmax 22px', ['3%', length_ref('10vmax'), '22px'].join(' '));
+test_computed_value('<length-percentage>+', 'calc(50% + 1em) 4px', 'calc(50% + 10px) 4px');
+
+test_computed_value('<transform-function>', 'translateX(2px)', 'translateX(2px)');
+test_computed_value('<transform-function>', 'translateX(10em)', 'translateX(100px)');
+test_computed_value('<transform-function>', 'translateX(calc(11em + 10%))', 'translateX(calc(10% + 110px))');
+test_computed_value('<transform-function>+', 'translateX(10%) scale(2)', 'translateX(10%) scale(2)');
+
+test_computed_value('<integer>', '15', '15');
+test_computed_value('<integer>', 'calc(15 + 15)', '30');
+test_computed_value('<integer>', 'calc(2.4)', '2');
+test_computed_value('<integer>', 'calc(2.6)', '3');
+test_computed_value('<integer>', 'calc(2.6 + 3.1)', '6');
+
+test_computed_value('<integer>+', '15 calc(2.4) calc(2.6)', '15 2 3');
+
+test_computed_value('<number>', '15', '15');
+test_computed_value('<number>', 'calc(15 + 15)', '30');
+test_computed_value('<number>', 'calc(24 / 10)', '2.4');
+
+test_computed_value('<number>+', '15 calc(15 + 15) calc(24 / 10)', '15 30 2.4');
+
+test_computed_value('<color>', '#ff0000', 'rgb(255, 0, 0)');
+test_computed_value('<color>', '#000f00', 'rgb(0, 15, 0)');
+test_computed_value('<color>', '#00000a', 'rgb(0, 0, 10)');
+test_computed_value('<color>', '#badbee', 'rgb(186, 219, 238)');
+test_computed_value('<color>', '#badbee33', 'rgba(186, 219, 238, 0.2)');
+test_computed_value('<color>', 'tomato', 'rgb(255, 99, 71)');
+test_computed_value('<color>', 'plum', 'rgb(221, 160, 221)');
+test_computed_value('<color>', 'currentcolor', 'currentcolor');
+
+// Custom ident values that look like color keywords should not be converted.
+test_computed_value('*', 'tomato', 'tomato');
+test_computed_value('tomato | plum', 'plum', 'plum');
+test_computed_value('tomato | plum | <color>', 'plum', 'plum');
+
+test_computed_value('*', '-50grad', '-50grad');
+test_computed_value('<angle>', '180deg', '180deg');
+test_computed_value('<angle>', '400grad', '360deg');
+test_computed_value('<angle>', 'calc(360deg + 400grad)', '720deg');
+
+test_computed_value('*', '50s', '50s');
+test_computed_value('<time>', '1s', '1s');
+test_computed_value('<time>', '1000ms', '1s');
+test_computed_value('<time>', 'calc(1000ms + 1s)', '2s');
+
+test_computed_value('*', '50dpi', '50dpi');
+test_computed_value('<resolution>', '1dppx', '1dppx');
+test_computed_value('<resolution>', '96dpi', '1dppx');
+test_computed_value('<resolution>', 'calc(1dppx + 96dpi)', '2dppx');
+
+test_computed_value('*', 'url(why)', 'url(why)');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-crosstalk.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-crosstalk.html
new file mode 100644
index 0000000000..8108894369
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-crosstalk.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1" />
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1238686" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+
+ @property --x {
+ syntax: "<number>";
+ inherits: true;
+ initial-value: 0;
+ }
+
+ #a {
+ --y: 0;
+ }
+
+ #b {
+ --z: 0;
+ }
+
+ #c {
+ --x: 42;
+ }
+
+</style>
+
+<div id=a>
+ <div id=b>
+ <div id=c>
+ </div>
+ </div>
+</div>
+
+<script>
+
+test(function(){
+ assert_equals(getComputedStyle(a).getPropertyValue('--x'), '0');
+ assert_equals(getComputedStyle(b).getPropertyValue('--x'), '0');
+ assert_equals(getComputedStyle(c).getPropertyValue('--x'), '42');
+}, 'Only #c should be affected by --x:42');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-cssom.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-cssom.html
new file mode 100644
index 0000000000..6231e27e77
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-cssom.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#inner {
+ --length: 10px;
+ --color: red;
+}
+#outer {
+ --length: 77px;
+ --color: blue;
+}
+</style>
+
+<div id=outer>
+ <div id=inner></div>
+</div>
+
+<script>
+var computedStyle = getComputedStyle(inner);
+var inlineStyle = inner.style;
+var sheetStyle = document.styleSheets[0].cssRules[0].style;
+
+test(function() {
+ // Nothing registered yet, whatever you specify works
+ assert_equals(computedStyle.getPropertyValue('--length'), '10px');
+ assert_equals(computedStyle.getPropertyValue('--color'), 'red');
+}, "Initially unregistered values from stylesheet");
+
+test(function() {
+ inlineStyle.setProperty('--length', '5');
+ inlineStyle.setProperty('--color', 'hello');
+
+ assert_equals(inlineStyle.getPropertyValue('--length'), '5');
+ assert_equals(inlineStyle.getPropertyValue('--color'), 'hello');
+ assert_equals(computedStyle.getPropertyValue('--length'), '5');
+ assert_equals(computedStyle.getPropertyValue('--color'), 'hello');
+}, "CSSOM setters function as expected for unregistered properties");
+
+test(function() {
+ CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--color', syntax: '<color>', initialValue: 'white', inherits: true});
+}, "CSS.registerProperty");
+
+test(function() {
+ assert_equals(inlineStyle.getPropertyValue('--length'), '5');
+ assert_equals(inlineStyle.getPropertyValue('--color'), 'hello');
+ assert_equals(computedStyle.getPropertyValue('--length'), '0px');
+ assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(0, 0, 255)');
+}, "Formerly valid values are still readable from inline styles but are computed as the unset value");
+
+test(function() {
+ inlineStyle.setProperty('--length', 'hi');
+ inlineStyle.setProperty('--color', '20');
+ assert_equals(inlineStyle.getPropertyValue('--length'), 'hi');
+ assert_equals(inlineStyle.getPropertyValue('--color'), '20');
+}, "Values not matching the registered type can still be set");
+
+test(function() {
+ inlineStyle.removeProperty('--length');
+ inlineStyle.setProperty('--color', '');
+ assert_equals(inlineStyle.getPropertyValue('--length'), '');
+ assert_equals(inlineStyle.getPropertyValue('--color'), '');
+ assert_equals(computedStyle.getPropertyValue('--length'), '10px');
+ assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(255, 0, 0)');
+}, "Values can be removed from inline styles");
+
+test(function() {
+ // 'banana' is not a valid <length>, but still accepted.
+ sheetStyle.setProperty('--length', 'banana');
+ assert_equals(computedStyle.getPropertyValue('--length'), '0px');
+ sheetStyle.setProperty('--length', '20px');
+ assert_equals(computedStyle.getPropertyValue('--length'), '20px');
+ sheetStyle.setProperty('--length', 'initial');
+ assert_equals(computedStyle.getPropertyValue('--length'), '0px');
+}, "Stylesheets can be modified by CSSOM");
+
+test(function() {
+ inlineStyle.setProperty('--length', '30px');
+ inlineStyle.setProperty('--color', 'pink');
+ assert_equals(inlineStyle.getPropertyValue('--length'), '30px');
+ assert_equals(inlineStyle.getPropertyValue('--color'), 'pink');
+ assert_equals(computedStyle.getPropertyValue('--length'), '30px');
+ assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(255, 192, 203)');
+ inlineStyle.setProperty('--color', 'inherit');
+ assert_equals(inlineStyle.getPropertyValue('--color'), 'inherit');
+ assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(0, 0, 255)');
+}, "Valid values can be set on inline styles");
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-initial.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-initial.html
new file mode 100644
index 0000000000..2fa062f310
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-initial.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#dom-propertydescriptor-initialvalue" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#register-a-custom-property" />
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#substitution" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=target></div>
+<script>
+
+function test_initial_value(reg, expected) {
+ let suffix = reg.inherits === true ? ', inherits' : '';
+ test(function(){
+ let name = generate_property(reg);
+ let actual = getComputedStyle(target).getPropertyValue(name);
+ assert_equals(actual, expected);
+ }, `Initial value for ${reg.syntax} correctly computed [${reg.initialValue}${suffix}]`);
+}
+
+test_initial_value({ syntax: '<length>', initialValue: 'calc(10px + 15px)' }, '25px');
+test_initial_value({ syntax: '<length>', initialValue: '1in' }, '96px');
+test_initial_value({ syntax: '<length>', initialValue: '2.54cm' }, '96px');
+test_initial_value({ syntax: '<length>', initialValue: '25.4mm' }, '96px');
+test_initial_value({ syntax: '<length>', initialValue: '6pc' }, '96px');
+test_initial_value({ syntax: '<length>', initialValue: '72pt' }, '96px');
+test_initial_value({ syntax: '<percentage>', initialValue: 'calc(10% + 20%)' }, '30%');
+test_initial_value({ syntax: '<length-percentage>', initialValue: 'calc(1in + 10% + 4px)' }, 'calc(10% + 100px)');
+test_initial_value({ syntax: '<color>', initialValue: 'pink', inherits: true }, 'rgb(255, 192, 203)');
+test_initial_value({ syntax: '<color>', initialValue: 'purple' }, 'rgb(128, 0, 128)');
+test_initial_value({ syntax: '<transform-function>', initialValue: 'rotate(42deg)' }, 'rotate(42deg)');
+test_initial_value({ syntax: '<transform-list>', initialValue: 'scale(calc(2 + 2))' }, 'scale(4)');
+test_initial_value({ syntax: '<transform-list>', initialValue: 'scale(calc(2 + 1)) translateX(calc(3px + 1px))' }, 'scale(3) translateX(4px)');
+test_initial_value({ syntax: '<url>', initialValue: 'url(a)' },
+ `url("${new URL('a', document.baseURI)}")`);
+test_initial_value({ syntax: '<url>+', initialValue: 'url(a) url(a)' },
+ `url("${new URL('a', document.baseURI)}") url("${new URL('a', document.baseURI)}")`);
+
+// Test that the initial value of the custom property 'reg' is successfully
+// substituted into 'property'.
+function test_substituted_value(reg, property, expected) {
+ let inherits_text = reg.inherits === true ? 'inherited' : 'non-inherited';
+ test(function(){
+ try {
+ let name = generate_property(reg);
+ target.style = `${property}:var(${name});`;
+ assert_equals(getComputedStyle(target).getPropertyValue(property), expected);
+ } finally {
+ target.style = '';
+ }
+ }, `Initial ${inherits_text} value can be substituted [${reg.initialValue}, ${property}]`);
+}
+
+test_substituted_value({ syntax: '<color>', initialValue: 'purple', inherits: true }, 'color', 'rgb(128, 0, 128)');
+test_substituted_value({ syntax: '<color>', initialValue: 'pink' }, 'background-color', 'rgb(255, 192, 203)');
+
+// Registered properties shall substitute as a token sequence equivalent to
+// their computed value.
+test_substituted_value({ syntax: 'foo', initialValue: '\tfoo\t' }, '--x', 'foo');
+test_substituted_value({ syntax: '<angle>', initialValue: '\t1turn' }, '--x', '360deg');
+test_substituted_value({ syntax: '<color>', initialValue: ' pink ' }, '--x', 'rgb(255, 192, 203)');
+test_substituted_value({ syntax: '<custom-ident>', initialValue: '\ttest' }, '--x', 'test');
+test_substituted_value({ syntax: '<integer>', initialValue: 'calc(20 + 20 + 10)' }, '--x', '50');
+test_substituted_value({ syntax: '<length-percentage>', initialValue: '\tcalc(13% + 37px)' }, '--x', 'calc(13% + 37px)');
+test_substituted_value({ syntax: '<length>', initialValue: 'calc(10px + 15px)' }, '--x', '25px');
+test_substituted_value({ syntax: '<number>', initialValue: 'calc(13 + 37)' }, '--x', '50');
+test_substituted_value({ syntax: '<percentage>', initialValue: 'calc(13% + 37%)' }, '--x', '50%');
+test_substituted_value({ syntax: '<time>', initialValue: '2000ms' }, '--x', '2s');
+test_substituted_value({ syntax: '<transform-function>', initialValue: 'scale(calc(2 + 2))' }, '--x', 'scale(4)');
+test_substituted_value({ syntax: '<transform-list>', initialValue: 'scale(calc(2 + 2)) translateX(calc(3px + 1px))' }, '--x', 'scale(4) translateX(4px)');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/registered-property-revert.html b/testing/web-platform/tests/css/css-properties-values-api/registered-property-revert.html
new file mode 100644
index 0000000000..33c395d2c2
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/registered-property-revert.html
@@ -0,0 +1,94 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1" />
+<link rel="help" href="https://drafts.csswg.org/css-cascade/#default" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ #parent {
+ --inherited: 10px;
+ --non-inherited: 10px;
+ }
+ #child {
+ --inherited: 20px;
+ --non-inherited: 20px;
+ --inherited: revert;
+ --non-inherited: revert;
+ }
+
+ @keyframes revert_animation {
+ from {
+ --animated-inherited: revert;
+ --animated-non-inherited: revert;
+ }
+ to {
+ --animated-inherited: 100px;
+ --animated-non-inherited: 100px;
+ }
+ }
+
+ #animated_parent {
+ --animated-inherited: 0px;
+ }
+ #animated_child {
+ animation: revert_animation 10s -5s linear paused;
+ }
+</style>
+<div id=parent>
+ <div id=child>
+ </div>
+</div>
+<div id=animated_parent>
+ <div id=animated_child>
+ </div>
+</div>
+<script>
+
+CSS.registerProperty({
+ name: "--inherited",
+ syntax: "<length>",
+ initialValue: "0px",
+ inherits: true
+});
+
+CSS.registerProperty({
+ name: "--non-inherited",
+ syntax: "<length>",
+ initialValue: "0px",
+ inherits: false
+});
+
+CSS.registerProperty({
+ name: "--animated-non-inherited",
+ syntax: "<length>",
+ initialValue: "0px",
+ inherits: false
+});
+
+CSS.registerProperty({
+ name: "--animated-inherited",
+ syntax: "<length>",
+ initialValue: "10000px",
+ inherits: true
+});
+
+test(function(){
+ let cs = getComputedStyle(child);
+ assert_equals(cs.getPropertyValue('--inherited'), '10px');
+}, 'Inherited registered custom property can be reverted');
+
+test(function(){
+ let cs = getComputedStyle(child);
+ assert_equals(cs.getPropertyValue('--non-inherited'), '0px');
+}, 'Non-inherited registered custom property can be reverted');
+
+test(function(){
+ let cs = getComputedStyle(animated_child);
+ assert_equals(cs.getPropertyValue('--animated-non-inherited'), '50px');
+}, 'Non-inherited registered custom property can be reverted in animation');
+
+test(function(){
+ let cs = getComputedStyle(animated_child);
+ assert_equals(cs.getPropertyValue('--animated-inherited'), '50px');
+}, 'Inherited registered custom property can be reverted in animation');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js b/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js
new file mode 100644
index 0000000000..a952a4feed
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/resources/utils.js
@@ -0,0 +1,245 @@
+let next_property_id = 1;
+
+// Generate a unique property name on the form --prop-N.
+function generate_name() {
+ return `--prop-${next_property_id++}`;
+}
+
+// Produce a compatible initial value for the specified syntax.
+function any_initial_value(syntax) {
+ let components = syntax.split('|').map(x => x.trim())
+ let first_component = components[0];
+
+ if (first_component.endsWith('+') || first_component.endsWith('#'))
+ first_component = first_component.slice(0, -1);
+
+ switch (first_component) {
+ case '*':
+ case '<custom-ident>':
+ return 'NULL';
+ case '<angle>':
+ return '0deg';
+ case '<color>':
+ return 'rgb(0, 0, 0)';
+ case '<image>':
+ case '<url>':
+ return 'url(0)';
+ case '<integer>':
+ case '<length-percentage>':
+ case '<length>':
+ case '<number>':
+ return '0';
+ case '<percentage>':
+ return '0%';
+ case '<resolution>':
+ return '0dpi';
+ case '<time>':
+ return '0s';
+ case '<transform-function>':
+ case '<transform-list>':
+ return 'matrix(0, 0, 0, 0, 0, 0)';
+ default:
+ // We assume syntax is a specific custom ident.
+ return first_component;
+ }
+}
+
+// Registers a unique property on the form '--prop-N' and returns the name.
+// Any value except 'syntax' may be omitted, in which case the property will
+// not inherit, and some undefined (but compatible) initial value will be
+// generated. If a single string is used as the argument, it is assumed to be
+// the syntax.
+function generate_property(reg) {
+ // Verify that only valid keys are specified. This prevents the caller from
+ // accidentally supplying 'inherited' instead of 'inherits', for example.
+ if (typeof(reg) === 'object') {
+ const permitted = new Set(['name', 'syntax', 'initialValue', 'inherits']);
+ if (!Object.keys(reg).every(k => permitted.has(k)))
+ throw new Error('generate_property: invalid parameter');
+ }
+
+ let syntax = typeof(reg) === 'string' ? reg : reg.syntax;
+ let initial = typeof(reg.initialValue) === 'undefined' ? any_initial_value(syntax)
+ : reg.initialValue;
+ let inherits = typeof(reg.inherits) === 'undefined' ? false : reg.inherits;
+
+ let name = generate_name();
+ CSS.registerProperty({
+ name: name,
+ syntax: syntax,
+ initialValue: initial,
+ inherits: inherits
+ });
+ return name;
+}
+
+function all_syntaxes() {
+ return [
+ '*',
+ '<angle>',
+ '<color>',
+ '<custom-ident>',
+ '<image>',
+ '<integer>',
+ '<length-percentage>',
+ '<length>',
+ '<number>',
+ '<percentage>',
+ '<resolution>',
+ '<time>',
+ '<transform-function>',
+ '<transform-list>',
+ '<url>'
+ ]
+}
+
+function with_style_node(text, fn) {
+ let node = document.createElement('style');
+ node.textContent = text;
+ try {
+ document.body.append(node);
+ fn(node);
+ } finally {
+ node.remove();
+ }
+}
+
+function with_at_property(desc, fn) {
+ let name = typeof(desc.name) === 'undefined' ? generate_name() : desc.name;
+ let text = `@property ${name} {`;
+ if (typeof(desc.syntax) !== 'undefined')
+ text += `syntax:${desc.syntax};`;
+ if (typeof(desc.initialValue) !== 'undefined')
+ text += `initial-value:${desc.initialValue};`;
+ if (typeof(desc.inherits) !== 'undefined')
+ text += `inherits:${desc.inherits};`;
+ text += '}';
+ with_style_node(text, (node) => fn(name, node.sheet.rules[0]));
+}
+
+function test_with_at_property(desc, fn, description) {
+ test(() => with_at_property(desc, fn), description);
+}
+
+function test_with_style_node(text, fn, description) {
+ test(() => with_style_node(text, fn), description);
+}
+
+function animation_test(property, values, description) {
+ const name = generate_name();
+ property.name = name;
+ CSS.registerProperty(property);
+
+ test(() => {
+ const duration = 1000;
+ const keyframes = {};
+ keyframes[name] = values.keyframes;
+
+ const iterations = 3;
+ const composite = values.composite || "replace";
+ const iterationComposite = values.iterationComposite || "replace";
+ const animation = target.animate(keyframes, { composite, iterationComposite, iterations, duration });
+ animation.pause();
+ // We seek to the middle of the third iteration which will allow to test cases where
+ // iterationComposite is set to something other than "replace".
+ animation.currentTime = duration * 2.5;
+
+ const assert_equals_function = values.assert_function || assert_equals;
+ assert_equals_function(getComputedStyle(target).getPropertyValue(name), values.expected);
+ }, description);
+};
+
+function discrete_animation_test(syntax, fromValue, toValue, description) {
+ test(() => {
+ const name = generate_name();
+
+ CSS.registerProperty({
+ name,
+ syntax,
+ inherits: false,
+ initialValue: fromValue
+ });
+
+ const duration = 1000;
+ const keyframes = [];
+ keyframes[name] = toValue;
+ const animation = target.animate(keyframes, duration);
+ animation.pause();
+
+ const checkAtProgress = (progress, expected) => {
+ animation.currentTime = duration * 0.25;
+ assert_equals(getComputedStyle(target).getPropertyValue(name), fromValue, `The correct value is used at progress = ${progress}`);
+ };
+
+ checkAtProgress(0, fromValue);
+ checkAtProgress(0.25, fromValue);
+ checkAtProgress(0.49, fromValue);
+ checkAtProgress(0.5, toValue);
+ checkAtProgress(0.75, toValue);
+ checkAtProgress(1, toValue);
+ }, description || `Animating a custom property of type ${syntax} is discrete`);
+}
+
+function transition_test(options, description) {
+ promise_test(async () => {
+ const customProperty = generate_name();
+
+ options.transitionProperty ??= customProperty;
+
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: options.syntax,
+ inherits: false,
+ initialValue: options.from
+ });
+
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.from, "Element has the expected initial value");
+
+ const transitionEventPromise = new Promise(resolve => {
+ let listener = event => {
+ target.removeEventListener("transitionrun", listener);
+ assert_equals(event.propertyName, customProperty, "TransitionEvent has the expected property name");
+ resolve();
+ };
+ target.addEventListener("transitionrun", listener);
+ });
+
+ target.style.transition = `${options.transitionProperty} 1s -500ms linear`;
+ if (options.behavior) {
+ target.style.transitionBehavior = options.behavior;
+ }
+ target.style.setProperty(customProperty, options.to);
+
+ const animations = target.getAnimations();
+ assert_equals(animations.length, 1, "A single animation is running");
+
+ const transition = animations[0];
+ assert_class_string(transition, "CSSTransition", "A CSSTransition is running");
+
+ transition.pause();
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.expected, "Element has the expected animated value");
+
+ await transitionEventPromise;
+ }, description);
+}
+
+function no_transition_test(options, description) {
+ test(() => {
+ const customProperty = generate_name();
+
+ CSS.registerProperty({
+ name: customProperty,
+ syntax: options.syntax,
+ inherits: false,
+ initialValue: options.from
+ });
+
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.from, "Element has the expected initial value");
+
+ target.style.transition = `${customProperty} 1s -500ms linear`;
+ target.style.setProperty(customProperty, options.to);
+
+ assert_equals(target.getAnimations().length, 0, "No animation was created");
+ assert_equals(getComputedStyle(target).getPropertyValue(customProperty), options.to, "Element has the expected final value");
+ }, description);
+};
diff --git a/testing/web-platform/tests/css/css-properties-values-api/self-utils.html b/testing/web-platform/tests/css/css-properties-values-api/self-utils.html
new file mode 100644
index 0000000000..b770c86c38
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/self-utils.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Self-test for utils.js</title>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<div id=outer><div id=inner></div></div>
+<script>
+
+test(function(){
+ let syntaxes = all_syntaxes().concat([
+ 'foo',
+ 'bar | <length>',
+ '<angle> | <length>'
+ ]);
+ // Don't throw:
+ syntaxes.forEach(generate_property);
+}, 'Default initial values of generated properties are valid (self-test).');
+
+test(function(){
+ try {
+ let inherited = generate_property({ syntax: '<length>', inherits: true });
+ let non_inherited = generate_property({ syntax: '<length>', inherits: false, initialValue: '5px' });
+ outer.style = `${inherited}: 10px; ${non_inherited}: 11px;`;
+ assert_equals(getComputedStyle(outer).getPropertyValue(inherited), '10px');
+ assert_equals(getComputedStyle(outer).getPropertyValue(non_inherited), '11px');
+ assert_equals(getComputedStyle(inner).getPropertyValue(inherited), '10px');
+ assert_equals(getComputedStyle(inner).getPropertyValue(non_inherited), '5px');
+ } finally {
+ outer.style = '';
+ inner.style = '';
+ }
+}, 'Generated properties respect inherits flag');
+
+test(function(){
+ assert_throws_js(Error, () => generate_property({syntax: '<length>', foo: 1}));
+ assert_throws_js(Error, () => generate_property({syntax: '<length>', inherited: false}));
+ assert_throws_js(Error, () => generate_property({syntax: '<length>', initial: '10px'}));
+}, 'Can\'t generate property with unknown fields');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.css b/testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.css
new file mode 100644
index 0000000000..aeb6ad5abe
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.css
@@ -0,0 +1,4 @@
+#target {
+ --reg-alt-non-inherited-url: url(foo.jpg);
+ --reg-alt-non-inherited-func: url("foo.jpg");
+}
diff --git a/testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.js b/testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.js
new file mode 100644
index 0000000000..f25e18beeb
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/support/alt/alt.js
@@ -0,0 +1,11 @@
+function reg_url(name, inherits) {
+ CSS.registerProperty({
+ name: name,
+ syntax: '<url> | none',
+ inherits: inherits,
+ initialValue: 'none'
+ });
+}
+
+reg_url('--reg-alt-non-inherited-url', false);
+reg_url('--reg-alt-non-inherited-func', false);
diff --git a/testing/web-platform/tests/css/css-properties-values-api/support/main/main.css b/testing/web-platform/tests/css/css-properties-values-api/support/main/main.css
new file mode 100644
index 0000000000..6b81abdee7
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/support/main/main.css
@@ -0,0 +1,22 @@
+#target {
+ --unreg-url: url(foo.jpg);
+ --unreg-func: url("foo.jpg");
+
+ --reg-inherited-url: url(foo.jpg);
+ --reg-non-inherited-url: url(foo.jpg);
+
+ --reg-inherited-func: url("foo.jpg");
+ --reg-non-inherited-func: url("foo.jpg");
+
+ --reg-ref-to-unreg-url: var(--unreg-url);
+ --reg-ref-to-unreg-func: var(--unreg-func);
+
+ --reg-ref-to-reg-url: var(--reg-alt-non-inherited-url);
+ --reg-ref-to-reg-func: var(--reg-alt-non-inherited-func);
+
+ --unreg-ref-to-reg-url: var(--reg-alt-non-inherited-url);
+ --unreg-ref-to-reg-func: var(--reg-alt-non-inherited-func);
+
+ --unreg-multi-ref-to-reg-urls: var(--reg-non-inherited-url), var(--reg-alt-non-inherited-url);
+ --unreg-multi-ref-to-reg-funcs: var(--reg-non-inherited-func), var(--reg-alt-non-inherited-func);
+}
diff --git a/testing/web-platform/tests/css/css-properties-values-api/support/main/main.js b/testing/web-platform/tests/css/css-properties-values-api/support/main/main.js
new file mode 100644
index 0000000000..169ed7b53c
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/support/main/main.js
@@ -0,0 +1,25 @@
+function reg_url(name, inherits) {
+ CSS.registerProperty({
+ name: name,
+ syntax: '<url> | none',
+ inherits: inherits,
+ initialValue: 'none'
+ });
+}
+
+reg_url('--reg-non-inherited-url', false);
+reg_url('--reg-non-inherited-func', false);
+
+reg_url('--reg-inherited-url', true);
+reg_url('--reg-inherited-func', true);
+
+reg_url('--reg-ref-to-unreg-url', false);
+reg_url('--reg-ref-to-unreg-func', false);
+
+reg_url('--reg-ref-to-reg-url', false);
+reg_url('--reg-ref-to-reg-func', false);
+
+reg_url('--reg-merged-func', false);
+
+reg_url('--reg-utf16be-url', false);
+reg_url('--reg-utf16be-func', false);
diff --git a/testing/web-platform/tests/css/css-properties-values-api/support/main/main.utf16be.css b/testing/web-platform/tests/css/css-properties-values-api/support/main/main.utf16be.css
new file mode 100644
index 0000000000..26485da32b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/support/main/main.utf16be.css
Binary files differ
diff --git a/testing/web-platform/tests/css/css-properties-values-api/typedom.html b/testing/web-platform/tests/css/css-properties-values-api/typedom.html
new file mode 100644
index 0000000000..51940e298d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/typedom.html
@@ -0,0 +1,993 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#css-style-value-reification" />
+<meta name="assert" content="Verifies that registered custom properties interact correctly with CSS Typed OM" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<style id=style>
+ div {}
+</style>
+<div id=target></div>
+
+<script>
+
+// Cleans style rules used for testing between every test.
+add_result_callback(function(){
+ target.attributeStyleMap.clear();
+ // Clears 'div' rule in #style:
+ style.sheet.rules[0].styleMap.clear();
+});
+
+// In the following utility functions, the 'map' parameter (if present)
+// can be any StylePropertyMap. (Not StylePropertyMapReadOnly).
+
+// Verifies that get()/getAll() reifies the specified property to a
+// CSSUnparsedValue, with a string serialization equal to 'value'.
+function verify_map_get_unparsed(map, name, value) {
+ map.set(name, value);
+
+ let specifiedValue = map.get(name);
+ assert_true(specifiedValue instanceof CSSUnparsedValue);
+ assert_equals(specifiedValue.toString(), value);
+
+ let allSpecifiedValues = map.getAll(name);
+ assert_equals(allSpecifiedValues.length, 1);
+ assert_true(allSpecifiedValues[0] instanceof CSSUnparsedValue);
+ assert_equals(allSpecifiedValues[0].toString(), value);
+}
+
+// Verifies that the specified value is accepted by set().
+function verify_map_set(map, name, value) {
+ map.set(name, value);
+ assert_equals(map.get(name).toString(), value.toString());
+}
+
+// Verifies that the specified value is NOT accepted by set().
+function verify_map_not_set(map, name, value) {
+ assert_throws_js(TypeError, () => {
+ map.set(name, value);
+ });
+}
+
+// Verifies that the specified value is NOT accepted by append().
+function verify_map_no_append(map, name, value) {
+ assert_throws_js(TypeError, () => {
+ map.append(name, value);
+ });
+}
+
+// Verifies that the property 'name' shows up on iteration, that it's reified
+// as a CSSUnparsedValue, and that the string representation is equal to
+// 'value'.
+function verify_map_iteration_unparsed(map, name, value) {
+ map.set(name, value);
+ let result = Array.from(map).filter(e => e[0] == name)[0];
+ assert_equals(result.length, 2);
+ let iter_value = result[1];
+ assert_equals(iter_value.length, 1);
+ assert_true(iter_value[0] instanceof CSSUnparsedValue);
+ assert_equals(iter_value[0].toString(), value);
+}
+
+// Verifies that CSSStyleValue.parse/parseAll results in a CSSStyleValue with
+// the 'expected' type.
+function verify_parsed_type(prop, value, expected) {
+ let parse_value = CSSStyleValue.parse(prop, value);
+ let parse_all_value = CSSStyleValue.parseAll(prop, value);
+
+ assert_true(parse_value instanceof expected);
+ assert_true(parse_all_value.every(x => x instanceof expected))
+}
+
+// On the target element, verify that computed value of 'name' is an instance
+// of 'expected' and not an instance of CSSUnparsedValue.
+//
+// If 'value' is non-null, that value is first set using the style attribute
+// of the target element.
+function verify_computed_type(name, value, expected) {
+ if (expected == CSSUnparsedValue) {
+ throw 'CSSUnparsedValue may not be used as expected type';
+ }
+
+ try {
+ if (value != null) {
+ target.style = `${name}: ${value}`;
+ }
+
+ let computedValue = target.computedStyleMap().get(name);
+
+ assert_false(computedValue instanceof CSSUnparsedValue);
+ assert_true(computedValue instanceof expected);
+ } finally {
+ if (value != null) {
+ target.style = '';
+ }
+ }
+}
+
+// Verifies that the property 'name' shows up on iteration, that it's reified
+// to the specified type, and that the string representation is equal to 'value'.
+function verify_computed_iteration_type(name, value, type) {
+ target.attributeStyleMap.set(name, value);
+ let result = Array.from(target.computedStyleMap()).filter(e => e[0] == name)[0];
+ assert_equals(result.length, 2);
+ let iter_value = result[1];
+ assert_equals(iter_value.length, 1);
+ assert_true(iter_value[0] instanceof type);
+ assert_equals(iter_value[0].toString(), value);
+}
+
+// Run the same test twice: once for each StylePropertyMap.
+//
+// https://drafts.css-houdini.org/css-typed-om-1/#stylepropertymap
+function test_specified_maps(func, description) {
+ test(function(){
+ func(target.attributeStyleMap)
+ }, description + ' [attributeStyleMap]');
+
+ test(function(){
+ let rule = style.sheet.rules[0];
+ func(rule.styleMap)
+ }, description + ' [styleMap]');
+}
+
+// StylePropertyMapReadOnly.get
+
+test(function(){
+ let name = generate_property('*', 'if(){}');
+ assert_true(target.computedStyleMap().get(name) instanceof CSSUnparsedValue);
+
+ target.attributeStyleMap.set(name, 'as{}df');
+ assert_true(target.computedStyleMap().get(name) instanceof CSSUnparsedValue);
+ target.attributeStyleMap.delete(name);
+}, 'Computed * is reified as CSSUnparsedValue');
+
+test(function(){
+ verify_computed_type(generate_property('<angle>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <angle> '), '42deg', CSSUnitValue);
+}, 'Computed <angle> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<color>'), null, CSSStyleValue);
+ verify_computed_type(generate_property('fail | <color> '), null, CSSStyleValue);
+}, 'Computed <color> is reified as CSSStyleValue');
+
+test(function(){
+ verify_computed_type(generate_property('<custom-ident>'), null, CSSKeywordValue);
+ verify_computed_type(generate_property('<custom-ident> | <length>'), 'none', CSSKeywordValue);
+}, 'Computed <custom-ident> is reified as CSSKeywordValue');
+
+test(function(){
+ verify_computed_type(generate_property('<image>'), null, CSSImageValue);
+ verify_computed_type(generate_property('fail | <image> '), 'url(thing.png)', CSSImageValue);
+}, 'Computed <image> [url] is reified as CSSImageValue');
+
+test(function(){
+ verify_computed_type(generate_property('<integer>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <integer> '), '100', CSSUnitValue);
+}, 'Computed <integer> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<length-percentage>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <length-percentage> '), '10%', CSSUnitValue);
+}, 'Computed <length-percentage> [%] is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<length-percentage>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <length-percentage> '), '10px', CSSUnitValue);
+}, 'Computed <length-percentage> [px] is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property({syntax: '<length-percentage>', initialValue: 'calc(10% + 10px)'}), null, CSSMathSum);
+ verify_computed_type(generate_property('fail | <length-percentage> '), 'calc(10px + 10%)', CSSMathSum);
+}, 'Computed <length-percentage> [px + %] is reified as CSSMathSum');
+
+test(function(){
+ verify_computed_type(generate_property('<length>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <length> '), '10px', CSSUnitValue);
+}, 'Computed <length> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<number>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <number> '), '42', CSSUnitValue);
+}, 'Computed <number> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<percentage>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <percentage> '), '10%', CSSUnitValue);
+}, 'Computed <percentage> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<resolution>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <resolution> '), '300dpi', CSSUnitValue);
+}, 'Computed <resolution> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<time>'), null, CSSUnitValue);
+ verify_computed_type(generate_property('fail | <time> '), '42s', CSSUnitValue);
+}, 'Computed <time> is reified as CSSUnitValue');
+
+test(function(){
+ verify_computed_type(generate_property('<url>'), null, CSSStyleValue);
+ verify_computed_type(generate_property('fail | <url> '), 'url(a)', CSSStyleValue);
+}, 'Computed <url> is reified as CSSStyleValue');
+
+test(function(){
+ verify_computed_type(generate_property('thing1 | THING2'), null, CSSKeywordValue);
+ verify_computed_type(generate_property('thing1 | THING2 | <url>'), 'THING2', CSSKeywordValue);
+}, 'Computed ident is reified as CSSKeywordValue');
+
+test(function(){
+ verify_computed_type(generate_property('<length>+'), null, CSSUnitValue);
+ verify_computed_type(generate_property('<length>+'), '10px 20px', CSSUnitValue);
+}, 'First computed value correctly reified in space-separated list');
+
+test(function(){
+ verify_computed_type(generate_property('<length>#'), null, CSSUnitValue);
+ verify_computed_type(generate_property('<length>#'), '10px, 20px', CSSUnitValue);
+}, 'First computed value correctly reified in comma-separated list');
+
+// StylePropertyMapReadOnly.getAll
+
+test(function(){
+ let name = generate_property({syntax: '<length>+', initialValue: '10px 20px'});
+ assert_equals(target.computedStyleMap().getAll(name).length, 2);
+ assert_true(target.computedStyleMap().getAll(name).every(x => x instanceof CSSUnitValue));
+
+ target.style = `${name}: 10px 20px 30px`;
+ assert_equals(target.computedStyleMap().getAll(name).length, 3);
+ assert_true(target.computedStyleMap().getAll(name).every(x => x instanceof CSSUnitValue));
+}, 'All computed values correctly reified in space-separated list');
+
+test(function(){
+ let name = generate_property({syntax: '<length>#', initialValue: '10px, 20px'});
+ assert_equals(target.computedStyleMap().getAll(name).length, 2);
+ assert_true(target.computedStyleMap().getAll(name).every(x => x instanceof CSSUnitValue));
+
+ target.style = `${name}: 10px, 20px, 30px`;
+ assert_equals(target.computedStyleMap().getAll(name).length, 3);
+ assert_true(target.computedStyleMap().getAll(name).every(x => x instanceof CSSUnitValue));
+}, 'All computed values correctly reified in comma-separated list');
+
+// StylePropertyMap.get/All
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('*'), 'foo');
+}, 'Specified * is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('foo'), 'foo');
+}, 'Specified foo is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<angle>'), '10deg');
+}, 'Specified <angle> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<color>'), 'green');
+}, 'Specified <color> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<custom-ident>'), 'foo');
+}, 'Specified <custom-ident> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<image>'), 'url("a")');
+}, 'Specified <image> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<integer>'), '1');
+}, 'Specified <integer> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<length-percentage>'), 'calc(10% + 10px)');
+}, 'Specified <length-percentage> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<length>'), '10px');
+}, 'Specified <length> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<number>'), '1');
+}, 'Specified <number> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<percentage>'), '10%');
+}, 'Specified <percentage> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<resolution>'), '10dpi');
+}, 'Specified <resolution> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<time>'), '1s');
+}, 'Specified <time> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<transform-function>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Specified <transform-function> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<transform-list>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Specified <transform-list> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<url>'), 'url("a")');
+}, 'Specified <url> is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<length>+'), '10px 11px');
+}, 'Specified <length>+ is reified as CSSUnparsedValue from get/getAll');
+
+test_specified_maps(function(map){
+ verify_map_get_unparsed(map, generate_property('<length>#'), '10px, 11px');
+}, 'Specified <length># is reified as CSSUnparsedValue from get/getAll');
+
+// StylePropertyMap.set
+
+// The following strings are valid for the specified syntax, and should be
+// accepted by set().
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('*'), 'foo');
+}, 'Specified string "foo" accepted by set() for syntax *');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('foo'), 'foo');
+}, 'Specified string "foo" accepted by set() for syntax foo');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<angle>'), '10deg');
+}, 'Specified string "10deg" accepted by set() for syntax <angle>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<color>'), 'green');
+}, 'Specified string "green" accepted by set() for syntax <color>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<custom-ident>'), 'foo');
+}, 'Specified string "foo" accepted by set() for syntax <custom-ident>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<image>'), 'url("a")');
+}, 'Specified string "url("a")" accepted by set() for syntax <image>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<integer>'), '1');
+}, 'Specified string "1" accepted by set() for syntax <integer>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length-percentage>'), 'calc(10% + 10px)');
+}, 'Specified string "calc(10% + 10px)" accepted by set() for syntax <length-percentage>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>'), '10px');
+}, 'Specified string "10px" accepted by set() for syntax <length>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<number>'), '1');
+}, 'Specified string "1" accepted by set() for syntax <number>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<percentage>'), '10%');
+}, 'Specified string "10%" accepted by set() for syntax <percentage>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<resolution>'), '10dpi');
+}, 'Specified string "10dpi" accepted by set() for syntax <resolution>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<time>'), '1s');
+}, 'Specified string "1s" accepted by set() for syntax <time>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<transform-function>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Specified string "matrix(0, 0, 0, 0, 0, 0)" accepted by set() for syntax <transform-function>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<transform-list>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Specified string "matrix(0, 0, 0, 0, 0, 0)" accepted by set() for syntax <transform-list>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<url>'), 'url("a")');
+}, 'Specified string "url("a")" accepted by set() for syntax <url>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>+'), '10px 11px');
+}, 'Specified string "10px 11px" accepted by set() for syntax <length>+');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>#'), '10px, 11px');
+}, 'Specified string "10px, 11px" accepted by set() for syntax <length>#');
+
+// The following strings are invalid for the specified syntax, but should
+// should be accepted by set().
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('foo'), 'bar');
+}, 'Specified string "bar" accepted by set() for syntax foo');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<angle>'), '10px');
+}, 'Specified string "10px" accepted by set() for syntax <angle>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<color>'), '10px');
+}, 'Specified string "10px" accepted by set() for syntax <color>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<custom-ident>'), '10px');
+}, 'Specified string "10px" accepted by set() for syntax <custom-ident>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<image>'), 'a');
+}, 'Specified string "a" accepted by set() for syntax <image>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<integer>'), 'float');
+}, 'Specified string "float" accepted by set() for syntax <integer>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length-percentage>'), 'red');
+}, 'Specified string "red" accepted by set() for syntax <length-percentage>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>'), 'red');
+}, 'Specified string "red" accepted by set() for syntax <length>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<number>'), 'red');
+}, 'Specified string "red" accepted by set() for syntax <number>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<percentage>'), 'var(--x)');
+}, 'Specified string "var(--x)" accepted by set() for syntax <percentage>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<resolution>'), 'blue');
+}, 'Specified string "blue" accepted by set() for syntax <resolution>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<time>'), '1meter');
+}, 'Specified string "1meter" accepted by set() for syntax <time>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<transform-function>'), 'foo(0)');
+}, 'Specified string "foo(0)" accepted by set() for syntax <transform-function>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<transform-list>'), 'bar(1)');
+}, 'Specified string "bar(1)" accepted by set() for syntax <transform-list>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<url>'), 'a');
+}, 'Specified string "a" accepted by set() for syntax <url>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>+'), 'a b');
+}, 'Specified string "a b" accepted by set() for syntax <length>+');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>#'), 'a, b');
+}, 'Specified string "a, b" accepted by set() for syntax <length>#');
+
+// CSSUnparsedValue should always be accepted by any custom property,
+// regardless of registation status.
+
+const unparsed = CSSStyleValue.parse('--x', 'foo bar thing');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('*'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax *');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('foo'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax foo');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<angle>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <angle>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<color>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <color>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<custom-ident>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <custom-ident>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<image>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <image>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<integer>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <integer>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length-percentage>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <length-percentage>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <length>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<number>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <number>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<percentage>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <percentage>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<resolution>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <resolution>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<time>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <time>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<transform-function>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <transform-function>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<transform-list>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <transform-list>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<url>'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <url>');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>+'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <length>+');
+
+test_specified_maps(function(map){
+ verify_map_set(map, generate_property('<length>#'), unparsed);
+}, 'CSSUnparsedValue is accepted via set() for syntax <length>#');
+
+// CSSStyleValues which aren't CSSUnparsedValues aren't accepted by set(),
+// even if they're a value which is compatible with the syntax.
+//
+// https://drafts.css-houdini.org/css-properties-values-api-1/#cssom
+const zero_matrix = CSSStyleValue.parse('transform', 'matrix(0, 0, 0, 0, 0, 0)');
+const image_value = CSSStyleValue.parse('background-image', 'url(a)');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('*'), new CSSKeywordValue('foo'));
+}, 'CSSKeywordValue rejected by set() for syntax *');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('foo'), new CSSKeywordValue('foo'));
+}, 'CSSKeywordValue rejected by set() for syntax foo');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<angle>'), CSS.deg(10));
+}, 'CSSUnitValue rejected by set() for syntax <angle>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<custom-ident>'), new CSSKeywordValue('foo'));
+}, 'CSSKeywordValue rejected by set() for syntax <custom-ident>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<image>'), image_value);
+}, 'CSSImageValue rejected by set() for syntax <image>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<integer>'), CSS.number(1));
+}, 'CSSUnitValue rejected by set() for syntax <integer>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<length-percentage>'), CSS.px(10));
+}, 'CSSUnitValue rejected by set() for syntax <length-percentage>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<length>'), CSS.px(10));
+}, 'CSSUnitValue rejected by set() for syntax <length>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<number>'), CSS.number(10));
+}, 'CSSUnitValue rejected by set() for syntax <number>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<percentage>'), CSS.percent(10));
+}, 'CSSUnitValue rejected by set() for syntax <percentage>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<resolution>'), CSS.dpi(10));
+}, 'CSSUnitValue rejected by set() for syntax <resolution>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<time>'), CSS.s(10));
+}, 'CSSUnitValue rejected by set() for syntax <time>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<transform-list>'), zero_matrix);
+}, 'CSSTransformValue rejected by set() for syntax <transform-list>');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<length>+'), CSS.px(10), CSS.px(10));
+}, 'CSSUnitValue rejected by set() for syntax <length>+');
+
+test_specified_maps(function(map){
+ verify_map_not_set(map, generate_property('<length>#'), CSS.px(10), CSS.px(10));
+}, 'CSSUnitValue rejected by set() for syntax <length>#');
+
+// <color> has no CSSStyleValue subclass yet.
+// <url> has no CSSStyleValue subclass yet.
+// <transform-function> has no CSSStyleValue subclass yet.
+
+// StylePropertyMap.append
+
+// It is not allowed to append CSSStyleValues to custom properties, even
+// when the string matches the syntax of the custom property.
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('*'), 'a');
+}, 'Appending a string to * is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('foo+'), 'foo');
+}, 'Appending a string to foo+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<angle>+'), '10deg');
+}, 'Appending a string to <angle>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<color>+'), 'red');
+}, 'Appending a string to <color>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<custom-ident>+'), 'foo');
+}, 'Appending a string to <custom-ident>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<image>+'), 'url(a)');
+}, 'Appending a string to <image>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<integer>+'), 'a');
+}, 'Appending a string to <integer>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<length-percentage>+'), 'calc(10*% + 10px)');
+}, 'Appending a string to <length-percentage>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<length>+'), '10px');
+}, 'Appending a string to <length>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<number>+'), '1.3');
+}, 'Appending a string to <number>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<percentage>+'), '10%');
+}, 'Appending a string to <percentage>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<resolution>+'), '10dpi');
+}, 'Appending a string to <resolution>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<time>+'), '1s');
+}, 'Appending a string to <time>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<transform-function>+'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Appending a string to <transform-function>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<transform-list>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Appending a string to <transform-list> is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<url>+'), 'url(a)');
+}, 'Appending a string to <url>+ is not allowed');
+
+test_specified_maps(function(map){
+ verify_map_no_append(map, generate_property('<length>#'), '10px');
+}, 'Appending a string to <length># is not allowed');
+
+// It is not allowed to append CSSStyleValues to custom properties, even
+// when the CSSStyleValue matches the syntax of the custom property.
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('*'), new CSSKeywordValue('foo'));
+}, 'Appending a CSSKeywordValue to * is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('foo+'), new CSSKeywordValue('foo'));
+}, 'Appending a CSSKeywordValue to foo+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<angle>+'), CSS.deg(10));
+}, 'Appending a CSSUnitValue to <angle>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<custom-ident>+'), new CSSKeywordValue('foo'));
+}, 'Appending a CSSKeywordValue to <custom-ident>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<image>+'), image_value);
+}, 'Appending a CSSImageValue to <image>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<integer>+'), CSS.number(1));
+}, 'Appending a CSSUnitValue to <integer>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<length-percentage>+'), CSS.px(10));
+}, 'Appending a CSSUnitValue to <length-percentage>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<length>+'), CSS.px(10));
+}, 'Appending a CSSUnitValue to <length>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<number>+'), CSS.number(10));
+}, 'Appending a CSSUnitValue to <number>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<percentage>+'), CSS.percent(10));
+}, 'Appending a CSSUnitValue to <percentage>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<resolution>+'), CSS.dpi(10));
+}, 'Appending a CSSUnitValue to <resolution>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<time>+'), CSS.s(10));
+}, 'Appending a CSSUnitValue to <time>+ is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<transform-list>'), zero_matrix);
+}, 'Appending a CSSKeywordValue to <transform-list> is not allowed');
+
+test_specified_maps(function(map) {
+ verify_map_no_append(map, generate_property('<length>#'), CSS.px(10));
+}, 'Appending a CSSUnitValue to <length># is not allowed');
+
+// <color> has no CSSStyleValue subclass yet.
+// <url> has no CSSStyleValue subclass yet.
+// <transform-function> has no CSSStyleValue subclass yet.
+
+// CSSStyleValue.parse/parseAll
+
+test(function(){
+ verify_parsed_type(generate_property('*'), 'while(){}', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax *', CSSUnparsedValue);
+
+test(function(){
+ verify_parsed_type(generate_property('<angle>'), '42deg', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <angle>');
+
+test(function(){
+ verify_parsed_type(generate_property('<color>'), '#fefefe', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <color>');
+
+test(function(){
+ verify_parsed_type(generate_property('<custom-ident> | <length>'), 'none', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <custom-ident> | <length>');
+
+test(function(){
+ verify_parsed_type(generate_property('<image>'), 'url(thing.png)', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <image>');
+
+test(function(){
+ verify_parsed_type(generate_property('<integer>'), '100', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <integer>');
+
+test(function(){
+ verify_parsed_type(generate_property('<length-percentage>'), '10%', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <length-percentage> (10%)');
+
+test(function(){
+ verify_parsed_type(generate_property('<length-percentage>'), '10px', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <length-percentage> (10px)');
+
+test(function(){
+ verify_parsed_type(generate_property('<length-percentage>'), 'calc(10px + 10%)', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <length-percentage> (calc(10px + 10%))');
+
+test(function(){
+ verify_parsed_type(generate_property('<length>'), '10px', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <length>');
+
+test(function(){
+ verify_parsed_type(generate_property('<number>'), '42', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <number>');
+
+test(function(){
+ verify_parsed_type(generate_property('<percentage>'), '10%', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <percentage>');
+
+test(function(){
+ verify_parsed_type(generate_property('<resolution>'), '300dpi', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <resolution>');
+
+test(function(){
+ verify_parsed_type(generate_property('<time>'), '42s', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <time>');
+
+test(function(){
+ verify_parsed_type(generate_property('<transform-function>'), 'matrix(0, 0, 0, 0, 0, 0)', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <transform-function>');
+
+test(function(){
+ verify_parsed_type(generate_property('<transform-list>'), 'matrix(0, 0, 0, 0, 0, 0)', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <transform-list>');
+
+test(function(){
+ verify_parsed_type(generate_property('<url>'), 'url(a)', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <url>');
+
+test(function(){
+ verify_parsed_type(generate_property('thing1 | THING2 | <url>'), 'THING2', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax thing1 | THING2 | <url>');
+
+test(function(){
+ verify_parsed_type(generate_property('<length>+'), '10px 20px', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <length>+');
+
+test(function(){
+ verify_parsed_type(generate_property('<length>#'), '10px, 20px', CSSUnparsedValue);
+}, 'CSSStyleValue.parse[All] returns CSSUnparsedValue for syntax <length>#');
+
+// Direct CSSStyleValue objects:
+
+test_specified_maps(function(map){
+ for (let syntax of all_syntaxes()) {
+ let name = generate_property(syntax);
+
+ let initialValue = target.computedStyleMap().get(name);
+
+ // We only care about direct CSSStyleValue instances in this test.
+ // Ultimately, in some future version of CSS TypedOM, we may have no
+ // direct CSSStyleValue instances at all, which is fine.
+ if (initialValue.constructor !== CSSStyleValue) {
+ continue;
+ }
+
+ // Verify that direct CSSStyleValues are rejected by set(). Two things
+ // should prevent this: 1) direct CSSStyleValues are not
+ // CSSUnparsedValues, and 2) direct CSSStyleValues are only valid for
+ // the property they were reified from.
+ verify_map_not_set(map, generate_property(syntax), initialValue);
+ }
+}, 'Direct CSSStyleValue may not be set');
+
+// StylePropertyMap iteration
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('*'), 'foo');
+}, 'Specified * is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('foo'), 'foo');
+}, 'Specified foo is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<angle>'), '10deg');
+}, 'Specified <angle> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<color>'), 'green');
+}, 'Specified <color> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<custom-ident>'), 'foo');
+}, 'Specified <custom-ident> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<image>'), 'url("a")');
+}, 'Specified <image> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<integer>'), '1');
+}, 'Specified <integer> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<length-percentage>'), 'calc(10% + 10px)');
+}, 'Specified <length-percentage> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<length>'), '10px');
+}, 'Specified <length> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<number>'), '1');
+}, 'Specified <number> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<percentage>'), '10%');
+}, 'Specified <percentage> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<resolution>'), '10dpi');
+}, 'Specified <resolution> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<time>'), '1s');
+}, 'Specified <time> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<transform-function>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Specified <transform-function> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<transform-list>'), 'matrix(0, 0, 0, 0, 0, 0)');
+}, 'Specified <transform-list> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<url>'), 'url("a")');
+}, 'Specified <url> is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<length>+'), '10px 11px');
+}, 'Specified <length>+ is reified CSSUnparsedValue by iterator');
+
+test_specified_maps(function(map){
+ verify_map_iteration_unparsed(map, generate_property('<length>#'), '10px, 11px');
+}, 'Specified <length># is reified CSSUnparsedValue by iterator');
+
+// StylePropertyMapReadOnly iteration
+
+test(function(){
+ let name = generate_property({syntax: '<length>', initialValue: '10px'});
+ let result = Array.from(target.computedStyleMap()).filter(e => e[0] == name)[0];
+ assert_true(typeof(result) !== 'undefined');
+}, 'Registered property with initial value show up on iteration of computedStyleMap');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('*'), 'thing', CSSUnparsedValue);
+}, 'Computed * is reified as CSSUnparsedValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<angle>'), '42deg', CSSUnitValue);
+}, 'Computed <angle> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<custom-ident>'), 'thing', CSSKeywordValue);
+}, 'Computed <custom-ident> is reified as CSSKeywordValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<image>'), 'url(\"a\")', CSSImageValue);
+}, 'Computed <image> is reified as CSSImageValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<integer>'), '100', CSSUnitValue);
+}, 'Computed <integer> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<length>'), '10px', CSSUnitValue);
+}, 'Computed <length> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<number>'), '42', CSSUnitValue);
+}, 'Computed <number> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<percentage>'), '10%', CSSUnitValue);
+}, 'Computed <percentage> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<resolution>'), '300dppx', CSSUnitValue);
+}, 'Computed <resolution> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<time>'), '10s', CSSUnitValue);
+}, 'Computed <time> is reified as CSSUnitValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('none | thing | THING'), 'THING', CSSKeywordValue);
+}, 'Computed none | thing | THING is reified as CSSKeywordValue by iterator');
+
+test(function(){
+ verify_computed_iteration_type(generate_property('<angle> | <length>'), '10px', CSSUnitValue);
+}, 'Computed <angle> | <length> is reified as CSSUnitValue by iterator');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/unit-cycles.html b/testing/web-platform/tests/css/css-properties-values-api/unit-cycles.html
new file mode 100644
index 0000000000..744b0f9e4b
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/unit-cycles.html
@@ -0,0 +1,341 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#dependency-cycles-via-relative-units" />
+<meta name="assert" content="This test verifies that reference cycles via units are detected" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ function register_property(name, syntax, inherits) {
+ CSS.registerProperty({
+ name: name,
+ syntax: syntax,
+ initialValue: '0px',
+ inherits: inherits
+ });
+ }
+
+ function register_length(name, inherits=false) {
+ register_property(name, '<length>', inherits);
+ }
+
+ function register_universal(name) {
+ register_property(name, '*', false);
+ }
+
+ register_length('--font-size-em');
+ register_length('--font-size-rem');
+ register_length('--font-size-ex');
+ register_length('--font-size-ch');
+ register_length('--font-size-px');
+ register_length('--font-size-lh');
+ register_length('--font-size-em-via-var');
+ register_length('--font-size-rem-via-var');
+ register_length('--font-size-ex-via-var');
+ register_length('--font-size-ch-via-var');
+ register_length('--font-size-em-inherited', true);
+ register_length('--font-size-ex-inherited', true);
+ register_length('--font-size-ch-inherited', true);
+ register_length('--line-height-lh');
+ register_length('--line-height-lh-via-var');
+ register_length('--line-height-lh-inherited', true);
+ register_universal('--font-size-em-universal');
+ register_universal('--font-size-rem-universal');
+ register_universal('--font-size-ex-universal');
+ register_universal('--font-size-ch-universal');
+ register_universal('--font-size-px-universal');
+ register_universal('--font-size-lh-universal');
+</script>
+<style>
+ :root {
+ --unregistered-em: 10em;
+ --unregistered-rem: 10rem;
+ --unregistered-ex: 10ex;
+ --unregistered-ch: 10ch;
+ --unregistered-lh: 10lh;
+ --unregistered-em-env: calc(10em + env(test, 0px));
+ --unregistered-rem-env: calc(10rem + env(test, 0px));
+ --unregistered-ex-env: calc(10ex + env(test, 0px));
+ --unregistered-ch-env: calc(10ch + env(test, 0px));
+ --unregistered-lh-env: calc(10lh + env(test, 0px));
+ }
+
+ :root, #target {
+ --font-size-em: 2em;
+ --font-size-rem: 2rem;
+ --font-size-ex: 2ex;
+ --font-size-ch: 2ch;
+ --font-size-px: 42px;
+ --font-size-lh: 2lh;
+ --line-height-lh: 2lh;
+ --font-size-em-via-var: var(--unregistered-em);
+ --font-size-rem-via-var: var(--unregistered-rem);
+ --font-size-ex-via-var: var(--unregistered-ex);
+ --font-size-ch-via-var: var(--unregistered-ch);
+ --line-height-lh-via-var: var(--unregistered-lh);
+ --font-size-em-universal: 2em;
+ --font-size-rem-universal: 2rem;
+ --font-size-ex-universal: 2ex;
+ --font-size-ch-universal: 2ch;
+ --font-size-px-universal: 42px;
+ --font-size-lh-universal: 2lh;
+ }
+
+ #parent {
+ --font-size-em-inherited: 4em;
+ --font-size-ex-inherited: 4ex;
+ --font-size-ch-inherited: 4ch;
+ --line-height-lh-inherited: 4lh;
+ }
+
+ #target {
+ font-size: 11px;
+ line-height: 13px;
+ }
+</style>
+
+<div id=parent>
+ <div id=target></div>
+</div>
+<div id=ref></div>
+
+<script>
+
+ function unset_dimension(element = ref) {
+ element.style.fontSize = '';
+ element.style.marginBottom = '';
+ }
+
+ function compute_margin_bottom(dimension, element) {
+ try {
+ element.style.marginBottom = dimension;
+ return getComputedStyle(element).marginBottom;
+ } finally {
+ element.style.marginBottom = '';
+ }
+ }
+
+ // Compute a dimension (e.g. 1em) given a certain fontSize.
+ function compute_dimension(dimension, fontSize, element = ref) {
+ try {
+ element.style.fontSize = fontSize;
+ return compute_margin_bottom(dimension, element);
+ } finally {
+ element.style.fontSize = '';
+ }
+ }
+
+ function assert_property_equals(name, value, element = target) {
+ var computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue(name), value);
+ }
+
+ let unsetFontSize = compute_dimension('1em', 'unset');
+ const unsetLineHeight = "normal";
+
+ add_result_callback(function(){
+ unset_dimension(target);
+ unset_dimension(document.documentElement);
+ });
+
+ test(function() {
+ // Unregistered variables behave as if they're copy & paste, so
+ // follow the regular font-relative size resolution.
+ const unregistered_variables = {
+ 'unregistered-em': target.parentElement,
+ 'unregistered-ex': target.parentElement,
+ 'unregistered-ch': target.parentElement,
+ 'unregistered-lh': target.parentElement,
+ 'unregistered-rem': document.documentElement,
+ };
+
+ for (let variable in unregistered_variables) {
+ const e = unregistered_variables[variable];
+ const v = `var(--${variable})`;
+ target.style = `font-size: ${v};`;
+ assert_property_equals('font-size', compute_margin_bottom(v, e));
+ }
+ }, 'Font-dependent unregistered variables can be used in font-size');
+
+ test(function() {
+ const universal_variables = {
+ 'font-size-em-universal': (v) => compute_dimension(v, 'unset', target),
+ 'font-size-ex-universal': (v) => compute_dimension(v, 'unset', target),
+ 'font-size-ch-universal': (v) => compute_dimension(v, 'unset', target),
+ 'font-size-lh-universal': (v) => compute_margin_bottom('2lh', target.parentElement),
+ 'font-size-rem-universal': (v) => compute_dimension(v, 'unset', target),
+ };
+ for (let variable in universal_variables) {
+ const v = `var(--${variable})`;
+ const expected = universal_variables[variable](v);
+ target.style = `font-size: ${v};`;
+ assert_property_equals('font-size', expected);
+ }
+ }, 'Registered universal variables referencing font-dependent units can be used in font-size');
+
+
+ test(function() {
+ // Unregistered variables behave as if they're copy & paste, so
+ // follow the regular font-relative size resolution.
+ const unregistered_variables = {
+ 'unregistered-em-env': target.parentElement,
+ 'unregistered-ex-env': target.parentElement,
+ 'unregistered-ch-env': target.parentElement,
+ 'unregistered-lh-env': target.parentElement,
+ 'unregistered-rem-env': document.documentElement,
+ };
+
+ for (let variable in unregistered_variables) {
+ const e = unregistered_variables[variable];
+ const v = `var(--${variable})`;
+ target.style = `font-size: ${v};`;
+ assert_property_equals('font-size', compute_margin_bottom(v, e));
+ }
+ }, 'Font-dependent unregistered variables referencing env() can be used in font-size');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-px);';
+ assert_property_equals('font-size', '42px');
+ assert_property_equals('--font-size-px', '42px');
+ }, 'Non-font-dependent variables can be used in font-size');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-em);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-em', '0px');
+ }, 'Lengths with em units may not be referenced from font-size');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-ex);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-ex', '0px');
+ }, 'Lengths with ex units may not be referenced from font-size');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-ch);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-ch', '0px');
+ }, 'Lengths with ch units may not be referenced from font-size');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-lh);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-lh', '0px');
+ }, 'Lengths with lh units may not be referenced from font-size');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-rem);';
+ let expected = compute_dimension('2rem', 'unset', document.documentElement);
+ assert_property_equals('--font-size-rem', expected);
+ assert_property_equals('font-size', expected);
+ }, 'Lengths with rem units may be referenced from font-size on non-root element');
+
+ test(function() {
+ let root = document.documentElement;
+ root.style = 'font-size: var(--font-size-rem);';
+ assert_property_equals('font-size', unsetFontSize, root);
+ assert_property_equals('--font-size-rem', '0px', root);
+ }, 'Lengths with rem units may not be referenced from font-size on root element');
+
+ test(function() {
+ target.style = 'line-height: var(--line-height-lh);';
+ assert_property_equals('line-height', unsetLineHeight);
+ assert_property_equals('--line-height-lh', '0px');
+ }, 'Lengths with lh units may not be referenced from line-height');
+
+ test(function() {
+ target.style = 'font-size: var(--noexist, var(--font-size-em));';
+ assert_property_equals('font-size', unsetFontSize);
+ }, 'Fallback may not use font-relative units');
+
+ test(function() {
+ target.style = 'line-height: var(--noexist, var(--line-height-lh));';
+ assert_property_equals('line-height', unsetLineHeight);
+ }, 'Fallback may not use line-height-relative units');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-em, 42px);';
+ assert_property_equals('font-size', unsetFontSize);
+ }, 'Fallback not triggered while inside em unit cycle');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-ex, 42px);';
+ assert_property_equals('font-size', unsetFontSize);
+ }, 'Fallback not triggered while inside ex unit cycle');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-ch, 42px);';
+ assert_property_equals('font-size', unsetFontSize);
+ }, 'Fallback not triggered while inside ch unit cycle');
+
+ test(function() {
+ let root = document.documentElement;
+ root.style = 'font-size: var(--font-size-rem, 42px);';
+ assert_property_equals('font-size', unsetFontSize, root);
+ root.style = 'font-size: unset;';
+ }, 'Fallback not triggered while inside rem unit cycle on root element');
+
+ test(function() {
+ target.style = 'line-height: var(--line-height-lh, 42px);';
+ assert_property_equals('line-height', unsetLineHeight);
+ }, 'Fallback not triggered while inside lh unit cycle');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-em-via-var);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-em-via-var', '0px');
+ }, 'Lengths with em units are detected via var references');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-ex-via-var);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-ex-via-var', '0px');
+ }, 'Lengths with ex units are detected via var references');
+
+ test(function() {
+ target.style = 'font-size: var(--font-size-ch-via-var);';
+ assert_property_equals('font-size', unsetFontSize);
+ assert_property_equals('--font-size-ch-via-var', '0px');
+ }, 'Lengths with ch units are detected via var references');
+
+ test(function() {
+ let root = document.documentElement;
+ root.style = 'font-size: var(--font-size-rem-via-var);';
+ assert_property_equals('font-size', unsetFontSize, root);
+ assert_property_equals('--font-size-rem-via-var', '0px', root);
+ root.style = 'font-size: unset';
+ }, 'Lengths with rem units are detected via var references');
+
+ test(function() {
+ target.style = 'line-height: var(--line-height-lh-via-var);';
+ assert_property_equals('line-height', unsetLineHeight);
+ assert_property_equals('--line-height-lh-via-var', '0px');
+ }, 'Lengths with lh units are detected via var references');
+
+ test(function() {
+ let expected4em = compute_dimension('4em', 'unset');
+ target.style = 'font-size: var(--font-size-em-inherited);';
+ assert_property_equals('font-size', expected4em);
+ assert_property_equals('--font-size-em-inherited', expected4em);
+ }, 'Inherited lengths with em units may be used');
+
+ test(function() {
+ let expected4ex = compute_dimension('4ex', 'unset');
+ target.style = 'font-size: var(--font-size-ex-inherited);';
+ assert_property_equals('font-size', expected4ex);
+ assert_property_equals('--font-size-ex-inherited', expected4ex);
+ }, 'Inherited lengths with ex units may be used');
+
+ test(function() {
+ let expected4ch = compute_dimension('4ch', 'unset');
+ target.style = 'font-size: var(--font-size-ch-inherited);';
+ assert_property_equals('font-size', expected4ch);
+ assert_property_equals('--font-size-ch-inherited', expected4ch);
+ }, 'Inherited lengths with ch units may be used');
+
+ test(function() {
+ let expected4lh = compute_dimension('4lh', 'unset');
+ target.style = 'line-height: var(--line-height-lh-inherited);';
+ assert_property_equals('line-height', expected4lh);
+ assert_property_equals('--line-height-lh-inherited', expected4lh);
+ }, 'Inherited lengths with lh units may be used');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/url-resolution.html b/testing/web-platform/tests/css/css-properties-values-api/url-resolution.html
new file mode 100644
index 0000000000..d932789649
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/url-resolution.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api-1/#relative-urls" />
+<meta name="assert" content="This test verifies that relative URLs in registered properties resolve correctly" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script src="support/main/main.js"></script>
+<script src="support/alt/alt.js"></script>
+<link id="main" rel="stylesheet" type="text/css" href="support/main/main.css" />
+<link id="main_utf16be" rel="stylesheet" type="text/css" href="support/main/main.utf16be.css" />
+<link id="alt" rel="stylesheet" type="text/css" href="support/alt/alt.css" />
+<div id=target>
+ <div id=inner></div>
+</div>
+<script>
+function parse_url(urlstr) {
+ urlstr = urlstr.trim();
+ if (!urlstr.startsWith('url("') || !urlstr.endsWith('")'))
+ throw 'Unknown url format: ' + urlstr;
+ return urlstr.slice(5, -2);
+}
+
+function get_bg_url(element) {
+ return parse_url(getComputedStyle(element)['background-image']);
+}
+
+function get_bg_urls(element) {
+ return getComputedStyle(element)['background-image']
+ .split(',')
+ .map(x => x.trim())
+ .map(x => parse_url(x));
+}
+
+function assert_base_path_equal(actual, expected) {
+ let actual_base = new URL(actual).pathname.split('/').slice(0, -1);
+ let expected_base = new URL(expected).pathname.split('/').slice(0, -1);
+ assert_equals(actual_base.join('/'), expected_base.join('/'));
+}
+
+function assert_base_paths_equal(actual, expected) {
+ assert_equals(actual.length, expected.length);
+ for (let i = 0; i < actual.length; i++) {
+ assert_base_path_equal(actual[i], expected[i]);
+ }
+}
+
+test(function() {
+ target.style = 'background-image: var(--unreg-url);';
+ assert_base_path_equal(get_bg_url(target), document.baseURI);
+}, 'Unregistered property resolves against document (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--unreg-func);';
+ assert_base_path_equal(get_bg_url(target), document.baseURI);
+}, 'Unregistered property resolves against document (URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-non-inherited-url);';
+ assert_base_path_equal(get_bg_url(target), main.sheet.href);
+}, 'Registered non-inherited <url> resolves against sheet (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-non-inherited-func);';
+ assert_base_path_equal(get_bg_url(target), main.sheet.href);
+}, 'Registered non-inherited <url> resolves against sheet (URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-inherited-url);';
+ assert_base_path_equal(get_bg_url(target), main.sheet.href);
+}, 'Registered inherited <url> resolves against sheet (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-inherited-func);';
+ assert_base_path_equal(get_bg_url(target), main.sheet.href);
+}, 'Registered inherited <url> resolves against sheet (URL function)');
+
+test(function() {
+ inner.style = 'background-image: var(--reg-inherited-url);';
+ assert_base_path_equal(get_bg_url(inner), main.sheet.href);
+}, 'Registered inherited <url> resolves against sheet (Child node, URL token)');
+
+test(function() {
+ inner.style = 'background-image: var(--reg-inherited-func);';
+ assert_base_path_equal(get_bg_url(inner), main.sheet.href);
+}, 'Registered inherited <url> resolves against sheet (Child node, URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-ref-to-unreg-url);';
+ assert_base_path_equal(get_bg_url(target), main.sheet.href);
+}, 'Registered property with unregistered var reference resolves against sheet (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-ref-to-unreg-func);';
+ assert_base_path_equal(get_bg_url(target), main.sheet.href);
+}, 'Registered property with unregistered var reference resolves against sheet. (URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-ref-to-reg-url);';
+ assert_base_path_equal(get_bg_url(target), alt.sheet.href);
+}, 'Registered property with registered var reference resolves against sheet of referenced property (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-ref-to-reg-func);';
+ assert_base_path_equal(get_bg_url(target), alt.sheet.href);
+}, 'Registered property with registered var reference resolves against sheet of referenced property (URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--unreg-ref-to-reg-url);';
+ assert_base_path_equal(get_bg_url(target), alt.sheet.href);
+}, 'Unregistered property with registered var reference resolves against sheet of referenced property (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--unreg-ref-to-reg-func);';
+ assert_base_path_equal(get_bg_url(target), alt.sheet.href);
+}, 'Unregistered property with registered var reference resolves against sheet of referenced property (URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--unreg-multi-ref-to-reg-urls);';
+ assert_base_paths_equal(get_bg_urls(target), [main.sheet.href, alt.sheet.href]);
+}, 'Multiple (registered) var reference resolve against respective sheets (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--unreg-multi-ref-to-reg-funcs);';
+ assert_base_paths_equal(get_bg_urls(target), [main.sheet.href, alt.sheet.href]);
+}, 'Multiple (registered) var reference resolve against respective sheets (URL function)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-utf16be-url);';
+ assert_base_path_equal(get_bg_url(target), main_utf16be.sheet.href);
+}, 'Registered UTF16BE-encoded var reference resolve against sheet (URL token)');
+
+test(function() {
+ target.style = 'background-image: var(--reg-utf16be-func);';
+ assert_base_path_equal(get_bg_url(target), main_utf16be.sheet.href);
+}, 'Registered UTF16BE-encoded var reference resolve against sheet (URL function)');
+
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html b/testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html
new file mode 100644
index 0000000000..91792ef51d
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<style>
+#test1 {
+ --registered-1-a: var(--registered-1-b, 10px);
+ --registered-1-b: var(--registered-1-a, 20px);
+
+ --registered-1-c: var(--registered-1-b, 30px);
+ --registered-1-d: var(--registered-1-b);
+ --unregistered-1-a:var(--registered-1-a,40px);
+ --unregistered-1-a:var(--registered-1-a);
+ left: var(--registered-1-a, 50px);
+ top: var(--registered-1-b, 60px);
+}
+</style>
+<div id=test1></div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--registered-1-a', syntax: '<length>', initialValue: '1px', inherits: false});
+ CSS.registerProperty({name: '--registered-1-b', syntax: '<length>', initialValue: '2px', inherits: false});
+ CSS.registerProperty({name: '--registered-1-c', syntax: '<length>', initialValue: '3px', inherits: false});
+ CSS.registerProperty({name: '--registered-1-d', syntax: '<length>', initialValue: '4px', inherits: false});
+
+ computedStyle = getComputedStyle(test1);
+ assert_equals(computedStyle.getPropertyValue('--registered-1-a'), '1px');
+ assert_equals(computedStyle.getPropertyValue('--registered-1-b'), '2px');
+ assert_equals(computedStyle.getPropertyValue('--registered-1-c'), '2px');
+ assert_equals(computedStyle.getPropertyValue('--registered-1-d'), '2px');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-1-a'), '1px');
+ assert_equals(computedStyle.left, '1px');
+ assert_equals(computedStyle.top, '2px');
+}, "A var() cycle between two registered properties is handled correctly.");
+</script>
+
+<style>
+#test2 {
+ --registered-2-a: var(--unregistered-2-a, 10px);
+ --unregistered-2-a:var(--registered-2-a,20px);
+
+ --registered-2-b: var(--registered-2-a, 30px);
+ --registered-2-c: var(--registered-2-a);
+ --registered-2-d: var(--unregistered-2-a, 40px);
+ --registered-2-e: var(--unregistered-2-a);
+ --unregistered-2-b:var(--registered-2-a,50px);
+ --unregistered-2-c:var(--registered-2-a);
+ --unregistered-2-d:var(--unregistered-2-a,60px);
+ --unregistered-2-e:var(--unregistered-2-a);
+ left: var(--registered-2-a, 70px);
+ top: var(--unregistered-2-a, 80px);
+}
+</style>
+<div id=test2></div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--registered-2-a', syntax: '<length>', initialValue: '1px', inherits: false});
+ CSS.registerProperty({name: '--registered-2-b', syntax: '<length>', initialValue: '2px', inherits: false});
+ CSS.registerProperty({name: '--registered-2-c', syntax: '<length>', initialValue: '3px', inherits: false});
+ CSS.registerProperty({name: '--registered-2-d', syntax: '<length>', initialValue: '4px', inherits: false});
+ CSS.registerProperty({name: '--registered-2-e', syntax: '<length>', initialValue: '5px', inherits: false});
+
+ computedStyle = getComputedStyle(test2);
+ assert_equals(computedStyle.getPropertyValue('--registered-2-a'), '1px');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-2-a'), '');
+
+ assert_equals(computedStyle.getPropertyValue('--registered-2-b'), '1px');
+ assert_equals(computedStyle.getPropertyValue('--registered-2-c'), '1px');
+ assert_equals(computedStyle.getPropertyValue('--registered-2-d'), '40px');
+ assert_equals(computedStyle.getPropertyValue('--registered-2-e'), '5px');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-2-b'), '1px');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-2-c'), '1px');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-2-d'), '60px');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-2-e'), '');
+ assert_equals(computedStyle.left, '1px');
+ assert_equals(computedStyle.top, '80px');
+}, "A var() cycle between a registered properties and an unregistered property is handled correctly.");
+</script>
+
+<style>
+#test3 {
+ --unregistered-3-a:var(--unregistered-3-b,10px);
+ --unregistered-3-b:var(--unregistered-3-a,20px);
+
+ --registered-3-a: var(--unregistered-3-a, 30px);
+ --registered-3-b: var(--unregistered-3-a);
+ --registered-3-c: var(--unregistered-3-b, 40px);
+ --registered-3-d: var(--registered-3-c, 50px);
+ left: var(--registered-3-d, 60px);
+ top: var(--registered-3-b, 70px);
+}
+</style>
+<div id=test3></div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--registered-3-a', syntax: '<length>', initialValue: '1px', inherits: false});
+ CSS.registerProperty({name: '--registered-3-b', syntax: '<length>', initialValue: '2px', inherits: false});
+ CSS.registerProperty({name: '--registered-3-c', syntax: '<length>', initialValue: '3px', inherits: false});
+ CSS.registerProperty({name: '--registered-3-d', syntax: '<length>', initialValue: '4px', inherits: false});
+
+ computedStyle = getComputedStyle(test3);
+ assert_equals(computedStyle.getPropertyValue('--unregistered-3-a'), '');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-3-b'), '');
+
+ assert_equals(computedStyle.getPropertyValue('--registered-3-a'), '30px');
+ assert_equals(computedStyle.getPropertyValue('--registered-3-b'), '2px');
+ assert_equals(computedStyle.getPropertyValue('--registered-3-c'), '40px');
+ assert_equals(computedStyle.getPropertyValue('--registered-3-d'), '40px');
+ assert_equals(computedStyle.left, '40px');
+ assert_equals(computedStyle.top, '2px');
+}, "A var() cycle between a two unregistered properties is handled correctly.");
+</script>
+
+<style>
+#test4 {
+ --registered-4-a:var(--unregistered-4-a,hello);
+ --unregistered-4-a:var(--registered-4-a,world);
+
+ --registered-4-b:var(--unregistered-4-a,meow);
+ --registered-4-c:var(--unregistered-4-a);
+ --unregistered-4-b:var(--unregistered-4-a,woof);
+ --unregistered-4-c:var(--unregistered-4-a);
+ transition-property: var(--registered-4-a, water);
+}
+</style>
+<div id=test4></div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--registered-4-a', syntax: '*', inherits: false});
+ CSS.registerProperty({name: '--registered-4-b', syntax: '*', initialValue: 'moo', inherits: false});
+ CSS.registerProperty({name: '--registered-4-c', syntax: '*', initialValue: 'circle', inherits: false});
+
+ computedStyle = getComputedStyle(test4);
+ assert_equals(computedStyle.getPropertyValue('--registered-4-a'), '');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-4-a'), '');
+
+ assert_equals(computedStyle.getPropertyValue('--registered-4-b'), 'meow');
+ assert_equals(computedStyle.getPropertyValue('--registered-4-c'), '');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-4-b'), 'woof');
+ assert_equals(computedStyle.getPropertyValue('--unregistered-4-c'), '');
+ assert_equals(computedStyle.transitionProperty, 'water');
+}, "A var() cycle between a syntax:'*' property and an unregistered property is handled correctly.");
+</script>
+
+<style>
+#test5_parent {
+ --registered-5-c:foo;
+ --registered-5-d:bar;
+ --registered-5-e:baz;
+ color: green;
+}
+#test5 {
+ --registered-5-a:var(--registered-5-b,hello);
+ --registered-5-b:var(--registered-5-a,world);
+
+ --registered-5-c:var(--registered-5-a);
+ --registered-5-d:var(--registered-5-b);
+ --registered-5-e:var(--unknown);
+ color: var(--registered-5-e);
+}
+</style>
+<div id=test5_parent>
+ <div id=test5></div>
+</div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--registered-5-a', syntax: '*', inherits: true});
+ CSS.registerProperty({name: '--registered-5-b', syntax: '*', inherits: true});
+ CSS.registerProperty({name: '--registered-5-c', syntax: '*', inherits: true});
+ CSS.registerProperty({name: '--registered-5-d', syntax: '*', inherits: true});
+ CSS.registerProperty({name: '--registered-5-e', syntax: '*', inherits: true});
+
+ let computedStyle = getComputedStyle(test5);
+ assert_equals(computedStyle.getPropertyValue('--registered-5-a'), '');
+ assert_equals(computedStyle.getPropertyValue('--registered-5-b'), '');
+ assert_equals(computedStyle.getPropertyValue('--registered-5-c'), '');
+ assert_equals(computedStyle.getPropertyValue('--registered-5-d'), '');
+ assert_equals(computedStyle.getPropertyValue('--registered-5-e'), '');
+ assert_equals(computedStyle.getPropertyValue('color'), 'rgb(0, 128, 0)');
+}, "Custom properties with universal syntax become guaranteed-invalid when " +
+ "invalid at computed-value time");
+</script>
diff --git a/testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties.html b/testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties.html
new file mode 100644
index 0000000000..f8f094c9bc
--- /dev/null
+++ b/testing/web-platform/tests/css/css-properties-values-api/var-reference-registered-properties.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-css-registerproperty" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/utils.js"></script>
+<style>
+div {
+ --registered-length-1: 10px;
+ --registered-length-2: var(--registered-length-1);
+ --registered-length-3: var(--length-1);
+ --registered-length-4: calc(var(--length-1) + 40px);
+ --registered-length-5: var(--invalid, 70px);
+ --registered-length-6: calc(var(--registered-length-3)*4);
+ --registered-length-7: var(--123px, 6px);
+
+ --length-1: 20px;
+ --length-2: var(--registered-length-1);
+ --length-3: calc(var(--123px, 6px) + var(--123px));
+
+ --percentage: 10%;
+ --registered-length-invalid: var(--percentage);
+
+ --registered-token-stream-1:var(--invalid);
+ --registered-token-stream-2:var(--invalid,fallback);
+ --token-stream-1:var(--registered-token-stream-1,moo);
+
+ --registered-length-list-1: 1px, var(--registered-length-1), 2px;
+ --registered-length-list-2: 1px, var(--length-1), var(--registered-length-1), 2px;
+ --registered-length-list-3: var(--registered-length-list-1), var(--registered-length-list-2);
+}
+</style>
+<div id=element></div>
+<script>
+test(function() {
+ CSS.registerProperty({name: '--123px', syntax: '<length>', initialValue: '123px', inherits: false});
+
+ CSS.registerProperty({name: '--registered-length-1', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-2', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-3', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-4', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-5', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-6', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-7', syntax: '<length>', initialValue: '0px', inherits: false});
+ CSS.registerProperty({name: '--registered-length-invalid', syntax: '<length>', initialValue: '15px', inherits: false});
+
+ CSS.registerProperty({name: '--registered-token-stream-1', syntax: '*', inherits: false});
+ CSS.registerProperty({name: '--registered-token-stream-2', syntax: '*', inherits: false});
+
+ computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--registered-length-1'), '10px');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-2'), '10px');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-3'), '20px');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-4'), '60px');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-5'), '70px');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-6'), '80px');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-7'), '123px');
+ assert_equals(computedStyle.getPropertyValue('--length-1'), '20px');
+ assert_equals(computedStyle.getPropertyValue('--length-2'), '10px');
+ assert_equals(computedStyle.getPropertyValue('--length-3'), 'calc(123px + 123px)');
+ assert_equals(computedStyle.getPropertyValue('--registered-length-invalid'), '15px');
+
+ assert_equals(computedStyle.getPropertyValue('--registered-token-stream-1'), '');
+ assert_equals(computedStyle.getPropertyValue('--registered-token-stream-2'), 'fallback');
+ assert_equals(computedStyle.getPropertyValue('--token-stream-1'), 'moo');
+}, "var() references work with registered properties");
+
+test(function(){
+ CSS.registerProperty({
+ name: '--registered-length-list-1',
+ syntax: '<length>#',
+ initialValue: '0px',
+ inherits: false
+ });
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--registered-length-list-1'), '1px, 10px, 2px');
+}, 'References to registered var()-properties work in registered lists');
+
+test(function(){
+ CSS.registerProperty({
+ name: '--registered-length-list-2',
+ syntax: '<length>#',
+ initialValue: '0px',
+ inherits: false
+ });
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--registered-length-list-2'), '1px, 20px, 10px, 2px');
+}, 'References to mixed registered and unregistered var()-properties work in registered lists');
+
+test(function(){
+ CSS.registerProperty({
+ name: '--registered-length-list-3',
+ syntax: '<length>#',
+ initialValue: '0px',
+ inherits: false
+ });
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--registered-length-list-3'), '1px, 10px, 2px, 1px, 20px, 10px, 2px');
+}, 'Registered lists may be concatenated');
+
+test(function(){
+ CSS.registerProperty({
+ name: '--length-em',
+ syntax: '<length>',
+ initialValue: '0px',
+ inherits: false
+ });
+ element.style = 'font-size: 11px; --length-em: 10em; --unregistered:var(--length-em);';
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--unregistered'), '110px');
+ element.style = '';
+}, 'Font-relative units are absolutized when substituting');
+
+test(function(){
+ CSS.registerProperty({
+ name: '--length-calc',
+ syntax: '<length>',
+ initialValue: '0px',
+ inherits: false
+ });
+ element.style = 'font-size: 11px; --length-calc: calc(10em + 10px); --unregistered:var(--length-calc);';
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--unregistered'), '120px');
+ element.style = '';
+}, 'Calc expressions are resolved when substituting');
+
+test(function(){
+ CSS.registerProperty({
+ name: '--length-calc-list',
+ syntax: '<length>#',
+ initialValue: '0px',
+ inherits: false
+ });
+ element.style = 'font-size: 11px; --length-calc-list: 10em, calc(10em + 10px); --unregistered:var(--length-calc-list);';
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--unregistered'), '110px, 120px');
+ element.style = '';
+}, 'Lists with relative units are absolutized when substituting');
+
+test(function(){
+ let length = generate_property('none | <length>');
+ let universal = generate_property('*');
+ element.style = `font-size: 10px; ${length}: 10em; ${universal}: var(${length})`;
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue(universal), '100px');
+ element.style = '';
+}, 'Values are absolutized when substituting into properties with universal syntax');
+
+test(function(){
+ let name = generate_property('<length>');
+ let unregistered = '--unregistered'
+ element.style = `${name}: red; ${unregistered}: var(${name})`
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue(unregistered), '');
+}, 'Invalid values for registered properties are serialized as the empty string')
+
+function test_valid_fallback(syntax, value, fallback) {
+ test(function(){
+ let name = generate_property(syntax);
+ try {
+ element.style = `${name}: ${value}; --x:var(${name},${fallback})`;
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--x'), value);
+ } finally {
+ element.style = '';
+ }
+ }, `Valid fallback does not invalidate var()-reference [${syntax}, ${fallback}]`);
+}
+
+function test_invalid_fallback(syntax, value, fallback) {
+ test(function(){
+ let name = generate_property(syntax);
+ try {
+ element.style = `${name}: ${value}; --x:var(${name},${fallback})`;
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('--x'), '');
+ } finally {
+ element.style = '';
+ }
+ }, `Invalid fallback invalidates var()-reference [${syntax}, ${fallback}]`);
+}
+
+test_valid_fallback('<length>', '40px', '10px');
+test_valid_fallback('<length> | <color>', '40px', 'red');
+test_valid_fallback('<length> | none', '40px', 'none');
+
+test_invalid_fallback('<length>', '40px', 'red');
+test_invalid_fallback('<length> | none', '40px', 'nolength');
+test_invalid_fallback('<length>', '40px', 'var(--novar)');
+
+test(function(t){
+ CSS.registerProperty({
+ name: '--registered-universal-no-initial',
+ syntax: '*',
+ inherits: false
+ });
+ t.add_cleanup(() => {
+ element.style = '';
+ });
+ element.style = [
+ '--registered-universal-no-initial:;',
+ 'background-color: var(--registered-universal-no-initial) green',
+ ].join(';');
+ let computedStyle = getComputedStyle(element);
+ assert_equals(computedStyle.getPropertyValue('background-color'), 'rgb(0, 128, 0)');
+}, 'Empty universal custom property can be substituted with var()');
+
+</script>