Update CSS Properties and Values API to use new cycle fallback behaviour
authorjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Dec 2018 03:21:15 +0000 (03:21 +0000)
committerjustin_michaud@apple.com <justin_michaud@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Dec 2018 03:21:15 +0000 (03:21 +0000)
https://bugs.webkit.org/show_bug.cgi?id=192800

Reviewed by Antti Koivisto.

LayoutTests/imported/w3c:

Re-import tests and adjust expected results. Some of the tests go from pass to fail because
this patch adds some extra dependency checking to property registrations to fix a crash, but
now unsupported syntaxes like <length-percentage> do not register properly.

* web-platform-tests/css/css-properties-values-api/register-property-expected.txt:
* web-platform-tests/css/css-properties-values-api/register-property-syntax-parsing-expected.txt:
* web-platform-tests/css/css-properties-values-api/register-property-syntax-parsing.html:
* web-platform-tests/css/css-properties-values-api/register-property.html:
* web-platform-tests/css/css-properties-values-api/registered-properties-inheritance-expected.txt:
* web-platform-tests/css/css-properties-values-api/registered-properties-inheritance.html:
* web-platform-tests/css/css-properties-values-api/registered-property-computation-expected.txt:
* web-platform-tests/css/css-properties-values-api/registered-property-computation.html:
* web-platform-tests/css/css-properties-values-api/registered-property-cssom-expected.txt:
* web-platform-tests/css/css-properties-values-api/registered-property-cssom.html:
* web-platform-tests/css/css-properties-values-api/registered-property-initial-expected.txt:
* web-platform-tests/css/css-properties-values-api/registered-property-initial.html:
* web-platform-tests/css/css-properties-values-api/resources/utils.js: Added.
(generate_name):
(any_initial_value):
(generate_property):
(all_syntaxes):
* web-platform-tests/css/css-properties-values-api/resources/w3c-import.log: Added.
* web-platform-tests/css/css-properties-values-api/self-utils-expected.txt: Added.
* web-platform-tests/css/css-properties-values-api/self-utils.html: Added.
* web-platform-tests/css/css-properties-values-api/typedom.tentative-expected.txt:
* web-platform-tests/css/css-properties-values-api/typedom.tentative.html:
* web-platform-tests/css/css-properties-values-api/unit-cycles-expected.txt:
* web-platform-tests/css/css-properties-values-api/unit-cycles.html:
* web-platform-tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html:
* web-platform-tests/css/css-properties-values-api/var-reference-registered-properties-expected.txt:
* web-platform-tests/css/css-properties-values-api/var-reference-registered-properties.html:
* web-platform-tests/css/css-properties-values-api/w3c-import.log:

Source/WebCore:

Make CSS variables that are registered and involved in a cycle be treated as invalid. This also fixes a crash in the
wpt tests where relative units and calc() in a registered property's initial value would break things instead of failing.

* css/CSSCustomPropertyValue.h:
* css/CSSVariableReferenceValue.cpp:
(WebCore::resolveVariableReference):
* css/DOMCSSRegisterCustomProperty.cpp:
(WebCore::DOMCSSRegisterCustomProperty::registerProperty):
* css/StyleResolver.cpp:
(WebCore::StyleResolver::applyCascadedCustomProperty):
* css/parser/CSSPropertyParser.cpp:
(WebCore::CSSPropertyParser::parseTypedCustomPropertyValue):

LayoutTests:

* css-custom-properties-api/crash.html:
* css-custom-properties-api/inherits-expected.txt:
* css-custom-properties-api/inherits.html:
* css-custom-properties-api/registerProperty-expected.txt:
* css-custom-properties-api/registerProperty.html:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@239365 268f45cc-cd09-0410-ab3c-d52691b4dbfc

37 files changed:
LayoutTests/ChangeLog
LayoutTests/css-custom-properties-api/crash.html
LayoutTests/css-custom-properties-api/inherits-expected.txt
LayoutTests/css-custom-properties-api/inherits.html
LayoutTests/css-custom-properties-api/registerProperty-expected.txt
LayoutTests/css-custom-properties-api/registerProperty.html
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/register-property-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/register-property-syntax-parsing-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/register-property-syntax-parsing.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/register-property.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-properties-inheritance-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-properties-inheritance.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-computation-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-computation.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-cssom-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-cssom.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-initial-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-initial.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/utils.js [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/w3c-import.log [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/typedom.tentative-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/typedom.tentative.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/unit-cycles-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/unit-cycles.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/var-reference-registered-properties-expected.txt
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/var-reference-registered-properties.html
LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/w3c-import.log
Source/WebCore/ChangeLog
Source/WebCore/css/CSSCustomPropertyValue.h
Source/WebCore/css/CSSVariableReferenceValue.cpp
Source/WebCore/css/DOMCSSRegisterCustomProperty.cpp
Source/WebCore/css/StyleResolver.cpp
Source/WebCore/css/parser/CSSPropertyParser.cpp

index a5ec06a..821c757 100644 (file)
@@ -1,3 +1,16 @@
+2018-12-18  Justin Michaud  <justin_michaud@apple.com>
+
+        Update CSS Properties and Values API to use new cycle fallback behaviour
+        https://bugs.webkit.org/show_bug.cgi?id=192800
+
+        Reviewed by Antti Koivisto.
+
+        * css-custom-properties-api/crash.html:
+        * css-custom-properties-api/inherits-expected.txt:
+        * css-custom-properties-api/inherits.html:
+        * css-custom-properties-api/registerProperty-expected.txt:
+        * css-custom-properties-api/registerProperty.html:
+
 2018-12-18  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Thick overlines and line-throughs grow in the wrong direction
index 7f2cd70..6205688 100644 (file)
@@ -48,15 +48,15 @@ test(function() {
 test(function() {
   inlineStyle.setProperty('--baz', '   40px');
   assert_equals(computedStyle.getPropertyValue('--baz'), '40px');
-  assert_equals(computedStyle.getPropertyValue('--foo'), '200px');
-  assert_equals(computedStyle.getPropertyValue('--bar'), '200px');
+  assert_equals(computedStyle.getPropertyValue('--foo'), '');
+  assert_equals(computedStyle.getPropertyValue('--bar'), '');
   assert_equals(computedStyle.getPropertyValue('--baz'), '40px');
-  assert_equals(computedStyle.getPropertyValue('font-size'), '200px');
+  assert_equals(computedStyle.getPropertyValue('font-size'), '30px');
   inlineStyle.removeProperty('--baz');
   assert_equals(computedStyle.getPropertyValue('--baz'), '200px');
-  assert_equals(computedStyle.getPropertyValue('--foo'), '200px');
-  assert_equals(computedStyle.getPropertyValue('--bar'), '200px');
-  assert_equals(computedStyle.getPropertyValue('font-size'), '200px');
+  assert_equals(computedStyle.getPropertyValue('--foo'), '');
+  assert_equals(computedStyle.getPropertyValue('--bar'), '');
+  assert_equals(computedStyle.getPropertyValue('font-size'), '30px');
   assert_equals(computedStyle.getPropertyValue('--baz'), '200px');
 }, "Setting the inline style is handled correctly when registered");
 
index 09e206c..5af1db3 100644 (file)
@@ -1,62 +1,62 @@
 Specified in parent, inherits=true
 
-100px green
+test
 
 Specified in parent, inherits=false
 
-200px green
+test
 
 Specified in parent, not registered
 
-100px green
+test
 
 Initial
 
-200px green
+test
 
 Unset, inherits=true
 
-100px green
+test
 
 Unset, inherits=false
 
-200px green
+test
 
 A cycle between an inherits=true and inherits=false property
 
-200px green
+test
 
 A cycle between an inherits=true and inherits=false property
 
-200px green
+test
 
 A cycle between an inherits=true and inherits=false property with fallback
 
-200px green
+test
 
 Inheritance should not create a cycle
 
-110px green
+test
 
 A cycle between two unregistered properties
 
-300px green
+test
 
 Revert, inherits=true
 
-190px purple
+test
 
 Revert, inherits=false
 
-200px purple
+test
 
 Revert, unregistered
 
-purple
+test
 
 Inherit, unregistered
 
-purple
+test
 
 Test that inherited properties do variable substitution before being inherited - registered
 
@@ -68,15 +68,15 @@ test
 
 No initial value in registered property should act like unregistered
 
-200px green
+test
 
 (unregistered)
 
-500px green
+test
 
 Inherit should be substituted for unregistered property
 
-500px green
+test
 
 
 PASS Registration is successful 
index 8ee4fcf..7fa53d5 100644 (file)
 </style>
 <div>
   <p> Specified in parent, inherits=true</p>
-  <div id="parent1"><div id="child1"><p>100px green</p></div> </div>
+  <div id="parent1"><div id="child1"><p>test</p></div> </div>
   <p> Specified in parent, inherits=false </p>
-  <div id="parent2"><div id="child2"><p>200px green</p></div> </div>
+  <div id="parent2"><div id="child2"><p>test</p></div> </div>
   <p> Specified in parent, not registered </p>
-  <div id="parent3"><div id="child3"><p>100px green</p></div> </div>
+  <div id="parent3"><div id="child3"><p>test</p></div> </div>
   <p> Initial </p>
-  <div id="parent4"><div id="child4"><p>200px green</p></div> </div>
+  <div id="parent4"><div id="child4"><p>test</p></div> </div>
   <p> Unset, inherits=true </p>
-  <div id="parent5"><div id="child5"><p>100px green</p></div> </div>
+  <div id="parent5"><div id="child5"><p>test</p></div> </div>
   <p> Unset, inherits=false </p>
-  <div id="parent6"><div id="child6"><p>200px green</p></div> </div>
+  <div id="parent6"><div id="child6"><p>test</p></div> </div>
   <p> A cycle between an inherits=true and inherits=false property </p>
-  <div id="parent7"><div id="child7"><p>200px green</p></div> </div>
+  <div id="parent7"><div id="child7"><p>test</p></div> </div>
   <p> A cycle between an inherits=true and inherits=false property </p>
-  <div id="parent8"><div id="child8"><p>200px green</p></div> </div>
+  <div id="parent8"><div id="child8"><p>test</p></div> </div>
   <p> A cycle between an inherits=true and inherits=false property with fallback </p>
-  <div id="parent9"><div id="child9"><p>200px green</p></div> </div>
+  <div id="parent9"><div id="child9"><p>test</p></div> </div>
   <p> Inheritance should not create a cycle </p>
-  <div id="parent10"><div id="child10"><p>110px green</p></div></div>
+  <div id="parent10"><div id="child10"><p>test</p></div></div>
   <p> A cycle between two unregistered properties </p>
-  <div id="parent11"><div id="child11"><p>300px green</p></div> </div>
+  <div id="parent11"><div id="child11"><p>test</p></div> </div>
 
   <p> Revert, inherits=true </p>
-  <div id="parent12"><div id="child12"><div id="childchild12"><p>190px purple</p></div></div></div>
+  <div id="parent12"><div id="child12"><div id="childchild12"><p>test</p></div></div></div>
   <p> Revert, inherits=false </p>
-  <div id="parent13"><div id="child13"><div id="childchild13"><p>200px purple</p></div></div> </div>
+  <div id="parent13"><div id="child13"><div id="childchild13"><p>test</p></div></div> </div>
   <p> Revert, unregistered </p>
-  <div id="parent14"><div id="child14"><div id="childchild14"><p>purple</p></div></div> </div>
+  <div id="parent14"><div id="child14"><div id="childchild14"><p>test</p></div></div> </div>
   <p> Inherit, unregistered </p>
-  <div id="parent14-1"><div id="child14-1"><div id="childchild14-1"><p>purple</p></div></div> </div>
+  <div id="parent14-1"><div id="child14-1"><div id="childchild14-1"><p>test</p></div></div> </div>
 
   <p> Test that inherited properties do variable substitution before being inherited - registered</p>
   <div id="parent15"><div id="child15"><p>test</p></div> </div>
   <div id="parent16"><div id="child16"><p>test</p></div> </div>
 
   <p> No initial value in registered property should act like unregistered</p>
-  <div id="parent17"><div id="child17"><p>200px green</p></div> </div>
+  <div id="parent17"><div id="child17"><p>test</p></div> </div>
   <p> (unregistered) </p>
-  <div id="parent18"><div id="child18"><p>500px green</p></div> </div>
+  <div id="parent18"><div id="child18"><p>test</p></div> </div>
 
   <p>Inherit should be substituted for unregistered property</p>
-  <div id="parent19"><div id="child19"><p>500px green</p></div> </div>
+  <div id="parent19"><div id="child19"><p>test</p></div> </div>
 </div>
 <script>
 
@@ -346,19 +346,19 @@ test(function() {
   test_prop('child6', '--my-custom-prop2', '200px');
 }, "JS Attributes are valid for element 6");
 test(function() {
-  test_prop('child7', 'width', '200px');
-  test_prop('child7', '--my-custom-prop', '100px');
-  test_prop('child7', '--my-custom-prop2', '200px');
+  test_prop('child7', 'width', '300px');
+  test_prop('child7', '--my-custom-prop', '');
+  test_prop('child7', '--my-custom-prop2', '');
 }, "JS Attributes are valid for element 7");
 test(function() {
-  test_prop('child8', 'width', '200px');
-  test_prop('child8', '--my-custom-prop', '200px');
-  test_prop('child8', '--my-custom-prop2', '200px');
+  test_prop('child8', 'width', '300px');
+  test_prop('child8', '--my-custom-prop', '');
+  test_prop('child8', '--my-custom-prop2', '');
 }, "JS Attributes are valid for element 8");
 test(function() {
-  test_prop('child9', 'width', '200px');
-  test_prop('child9', '--my-custom-prop', '100px');
-  test_prop('child9', '--my-custom-prop2', '200px');
+  test_prop('child9', 'width', '300px');
+  test_prop('child9', '--my-custom-prop', '');
+  test_prop('child9', '--my-custom-prop2', '');
 }, "JS Attributes are valid for element 9");
 test(function() {
   test_prop('child10', 'width', '110px');
index b3082e1..44d215a 100644 (file)
@@ -3,4 +3,5 @@ PASS registerProperty requires a Dictionary type
 PASS registerProperty requires a name matching <custom-property-name> 
 PASS registerProperty always allows omitting initialValue and syntax, requires name and inherits 
 PASS registerProperty requires inherits and name 
+PASS registerProperty requires initialValue to be computationally independent 
 
index 1f57666..3dbcb0c 100644 (file)
@@ -2,6 +2,7 @@
 <!-- https://chromium.googlesource.com/chromium/src/+/01ce431409e3a019858677626a983c55168da6dc/third_party/WebKit/LayoutTests/custom-properties/register-property.html -->
 <script src="../resources/testharness.js"></script>
 <script src="../resources/testharnessreport.js"></script>
+<div id="el"></div>
 <script>
 // Tests for error checking during property registration
 test(function() {
@@ -18,9 +19,9 @@ test(function() {
     CSS.registerProperty({name: '--name2, no need for escapes', inherits: false});
     CSS.registerProperty({name: ['--name', 3], inherits: false});
     // Invalid property names
-    //assert_throws(new SyntaxError(), () => CSS.registerProperty({name: 'no-leading-dash', inherits: false}));
-    //assert_throws(new SyntaxError(), () => CSS.registerProperty({name: '', inherits: false}));
-    //assert_throws(new SyntaxError(), () => CSS.registerProperty({name: '\\--name', inherits: false}));
+    assert_throws(new SyntaxError(), () => CSS.registerProperty({name: 'no-leading-dash', inherits: false}));
+    assert_throws(new SyntaxError(), () => CSS.registerProperty({name: '', inherits: false}));
+    assert_throws(new SyntaxError(), () => CSS.registerProperty({name: '\\--name', inherits: false}));
 }, "registerProperty requires a name matching <custom-property-name>");
 test(function() {
     CSS.registerProperty({name: '--syntax-test-1', inherits: false, syntax: '*'});
@@ -37,4 +38,15 @@ test(function() {
     CSS.registerProperty({name: '--syntax-test-4', inherits: false, syntax: '*'});
     CSS.registerProperty({name: '--syntax-test-5', inherits: false, syntax: ' * '});
 }, "registerProperty requires inherits and name");
+test(function() {
+  CSS.registerProperty({name: '--initialvalue-test-0', inherits: false, syntax: '<length>', initialValue: 'calc(10px + 10in)'});
+  assert_equals(window.getComputedStyle(el).getPropertyValue('--initialvalue-test-0').toString(), '970px');
+
+  assert_throws(new SyntaxError(),
+    () => CSS.registerProperty({name: '--initialvalue-test-1', inherits: false, syntax: '<length>', initialValue: '10em'}));
+  assert_throws(new SyntaxError(),
+    () => CSS.registerProperty({name: '--initialvalue-test-2', inherits: false, syntax: '<length>', initialValue: 'calc(10px + 10em)'}));
+  assert_throws(new SyntaxError(),
+    () => CSS.registerProperty({name: '--initialvalue-test-3', inherits: false, syntax: '<length>', initialValue: 'calc(10px + 10%)'}));
+}, "registerProperty requires initialValue to be computationally independent");
 </script>
index 582f6d0..2fb9a32 100644 (file)
@@ -1,5 +1,45 @@
 2018-12-18  Justin Michaud  <justin_michaud@apple.com>
 
+        Update CSS Properties and Values API to use new cycle fallback behaviour
+        https://bugs.webkit.org/show_bug.cgi?id=192800
+
+        Reviewed by Antti Koivisto.
+
+        Re-import tests and adjust expected results. Some of the tests go from pass to fail because
+        this patch adds some extra dependency checking to property registrations to fix a crash, but
+        now unsupported syntaxes like <length-percentage> do not register properly.
+
+        * web-platform-tests/css/css-properties-values-api/register-property-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/register-property-syntax-parsing-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/register-property-syntax-parsing.html:
+        * web-platform-tests/css/css-properties-values-api/register-property.html:
+        * web-platform-tests/css/css-properties-values-api/registered-properties-inheritance-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/registered-properties-inheritance.html:
+        * web-platform-tests/css/css-properties-values-api/registered-property-computation-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/registered-property-computation.html:
+        * web-platform-tests/css/css-properties-values-api/registered-property-cssom-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/registered-property-cssom.html:
+        * web-platform-tests/css/css-properties-values-api/registered-property-initial-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/registered-property-initial.html:
+        * web-platform-tests/css/css-properties-values-api/resources/utils.js: Added.
+        (generate_name):
+        (any_initial_value):
+        (generate_property):
+        (all_syntaxes):
+        * web-platform-tests/css/css-properties-values-api/resources/w3c-import.log: Added.
+        * web-platform-tests/css/css-properties-values-api/self-utils-expected.txt: Added.
+        * web-platform-tests/css/css-properties-values-api/self-utils.html: Added.
+        * web-platform-tests/css/css-properties-values-api/typedom.tentative-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/typedom.tentative.html:
+        * web-platform-tests/css/css-properties-values-api/unit-cycles-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/unit-cycles.html:
+        * web-platform-tests/css/css-properties-values-api/var-reference-registered-properties-cycles.html:
+        * web-platform-tests/css/css-properties-values-api/var-reference-registered-properties-expected.txt:
+        * web-platform-tests/css/css-properties-values-api/var-reference-registered-properties.html:
+        * web-platform-tests/css/css-properties-values-api/w3c-import.log:
+
+2018-12-18  Justin Michaud  <justin_michaud@apple.com>
+
         CSS Typed OM should expose attributeStyleMap
         https://bugs.webkit.org/show_bug.cgi?id=192671
 
index 567db31..cf9c8d4 100644 (file)
@@ -1,7 +1,8 @@
 
 PASS registerProperty requires a Dictionary type 
-FAIL registerProperty requires a name matching <custom-property-name> assert_throws: function "() => CSS.registerProperty({name: 'no-leading-dash', inherits: false})" did not throw
+PASS registerProperty requires a name matching <custom-property-name> 
 FAIL registerProperty only allows omitting initialValue if syntax is '*' assert_throws: function "() => CSS.registerProperty({name: '--syntax-test-3', syntax: 'length', inherits: false})" did not throw
-PASS registerProperty fails for an already registered property 
+FAIL registerProperty fails for an already registered property assert_throws: function "() => CSS.registerProperty({name: '--re-register', syntax: '<percentage>', initialValue: '0%', inherits: false})" threw object "SyntaxError: The given initial value does not parse for the given syntax." ("SyntaxError") expected object "[object Object]" ("InvalidModificationError")
 PASS registerProperty requires inherits 
+FAIL Registering a property should not cause a transition The given initial value does not parse for the given syntax.
 
index 2ea78e2..376e48b 100644 (file)
@@ -3,7 +3,7 @@ PASS syntax:'*', initialValue:'a' is valid
 FAIL syntax:' * ', initialValue:'b' is valid The given initial value does not parse for the given syntax.
 PASS syntax:'<length>', initialValue:'2px' is valid 
 FAIL syntax:' <number>', initialValue:'5' is valid The given initial value does not parse for the given syntax.
-PASS syntax:'<percentage> ', initialValue:'10%' is valid 
+FAIL syntax:'<percentage> ', initialValue:'10%' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<color>+', initialValue:'red' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:' <length>+ | <percentage>', initialValue:'2px 8px' is valid The given initial value does not parse for the given syntax.
 PASS syntax:'<length>|<percentage>|<length-percentage>', initialValue:'2px' is valid 
@@ -21,14 +21,19 @@ PASS syntax:'<length>', initialValue:'calc(2px*4 + 10px)' is valid
 PASS syntax:'<length>', initialValue:'7.1e-4cm' is valid 
 PASS syntax:'<length>', initialValue:'calc(7in - 12px)' is valid 
 FAIL syntax:'<length>+', initialValue:'2px 7px calc(8px)' is valid The given initial value does not parse for the given syntax.
+FAIL syntax:'<length>#', initialValue:'2px, 7px, calc(8px)' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<percentage>', initialValue:'-9.3e3%' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<length-percentage>', initialValue:'-54%' is valid The given initial value does not parse for the given syntax.
 PASS syntax:'<length-percentage>', initialValue:'0' is valid 
-PASS syntax:'<length-percentage>', initialValue:'calc(-11px + 10.4%)' is valid 
+FAIL syntax:'<length-percentage>', initialValue:'calc(-11px + 10.4%)' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<number>', initialValue:'-109' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<number>', initialValue:'2.3e4' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<integer>', initialValue:'-109' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<integer>', initialValue:'19' is valid The given initial value does not parse for the given syntax.
+FAIL syntax:'<integer>', initialValue:'calc(1)' is valid The given initial value does not parse for the given syntax.
+FAIL syntax:'<integer>', initialValue:'calc(1 + 2)' is valid The given initial value does not parse for the given syntax.
+FAIL syntax:'<integer>', initialValue:'calc(3.1415)' is valid The given initial value does not parse for the given syntax.
+FAIL syntax:'<integer>', initialValue:'calc(3.1415 + 3.1415)' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<angle>', initialValue:'10deg' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<angle>', initialValue:'20.5rad' is valid The given initial value does not parse for the given syntax.
 FAIL syntax:'<angle>', initialValue:'calc(50grad + 3.14159rad)' is valid The given initial value does not parse for the given syntax.
@@ -84,7 +89,7 @@ PASS syntax:'inherit', initialValue:'inherit' is invalid
 PASS syntax:'unset', initialValue:'unset' is invalid 
 FAIL syntax:'<length>|initial', initialValue:'10px' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
 FAIL syntax:'<length>|INHERIT', initialValue:'10px' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
-FAIL syntax:'<percentage>|unsEt', initialValue:'2%' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+PASS syntax:'<percentage>|unsEt', initialValue:'2%' is invalid 
 FAIL syntax:'*', initialValue:'initial' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
 FAIL syntax:'*', initialValue:'inherit' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
 FAIL syntax:'*', initialValue:'unset' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
@@ -103,24 +108,26 @@ FAIL syntax:'*', initialValue:'var(--foo)' is invalid assert_throws: function "(
 PASS syntax:'banana', initialValue:'bAnAnA' is invalid 
 PASS syntax:'<length>', initialValue:'var(--moo)' is invalid 
 PASS syntax:'<length>', initialValue:'10' is invalid 
-FAIL syntax:'<length>', initialValue:'10%' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
-FAIL syntax:'<length>', initialValue:'calc(5px + 10%)' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+PASS syntax:'<length>', initialValue:'10%' is invalid 
+PASS syntax:'<length>', initialValue:'calc(5px + 10%)' is invalid 
 PASS syntax:'<length>', initialValue:'calc(5px * 3px / 6px)' is invalid 
-FAIL syntax:'<length>', initialValue:'10em' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+PASS syntax:'<length>', initialValue:'10em' is invalid 
 FAIL syntax:'<length>', initialValue:'10vmin' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
-FAIL syntax:'<length>', initialValue:'calc(4px + 3em)' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
-FAIL syntax:'<length>', initialValue:'calc(4px + calc(8 * 2em))' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
-FAIL syntax:'<length>+', initialValue:'calc(2ex + 16px)' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+PASS syntax:'<length>', initialValue:'calc(4px + 3em)' is invalid 
+PASS syntax:'<length>', initialValue:'calc(4px + calc(8 * 2em))' is invalid 
+PASS syntax:'<length>+', initialValue:'calc(2ex + 16px)' is invalid 
 PASS syntax:'<length>+', initialValue:'10px calc(20px + 4rem)' is invalid 
+FAIL syntax:'<length>+', initialValue:'' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+FAIL syntax:'<length>#', initialValue:'' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
 PASS syntax:'<percentage> | <length>+', initialValue:'calc(100vh - 10px) 30px' is invalid 
 PASS syntax:'<length>', initialValue:'10px;' is invalid 
-FAIL syntax:'<length-percentage>', initialValue:'calc(2px + 10% + 7ex)' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+PASS syntax:'<length-percentage>', initialValue:'calc(2px + 10% + 7ex)' is invalid 
 FAIL syntax:'<percentage>', initialValue:'0' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
 PASS syntax:'<integer>', initialValue:'1.0' is invalid 
 PASS syntax:'<integer>', initialValue:'1e0' is invalid 
 PASS syntax:'<number>|foo', initialValue:'foo var(--foo, bla)' is invalid 
 FAIL syntax:'<angle>', initialValue:'0' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
-FAIL syntax:'<angle>', initialValue:'10%' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
+PASS syntax:'<angle>', initialValue:'10%' is invalid 
 FAIL syntax:'<time>', initialValue:'2px' is invalid assert_throws: function "() => CSS.registerProperty({name: name, syntax: syntax, initialValue: initialValue, inherits: false})" did not throw
 PASS syntax:'<resolution>', initialValue:'10' is invalid 
 PASS syntax:'<transform-function>', initialValue:'scale()' is invalid 
index 15a4ab6..4ce514f 100644 (file)
@@ -46,6 +46,7 @@ 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");
@@ -55,6 +56,10 @@ assert_valid("<number>", "-109");
 assert_valid("<number>", "2.3e4");
 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");
@@ -147,6 +152,8 @@ 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("<percentage> | <length>+", "calc(100vh - 10px) 30px");
 assert_invalid("<length>", "10px;");
 assert_invalid("<length-percentage>", "calc(2px + 10% + 7ex)");
index 62ad236..5842b83 100644 (file)
@@ -2,6 +2,8 @@
 <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
 
@@ -45,4 +47,29 @@ test(function(){
     CSS.registerProperty({name: '--inherit-test-2', syntax: '<length>', initialValue: '0px', inherits: false});
     assert_throws(new 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>
index 5654e82..d2234f0 100644 (file)
@@ -4,4 +4,6 @@ PASS Explicitly inheriting from a parent with an invalid value results in initia
 PASS Explicitly inheriting from a parent with no value results in initial value. 
 PASS Reference to undefined variable results in inherited value 
 PASS Reference to syntax-incompatible variable results in inherited value 
+PASS Font-relative units are absolutized before before inheritance 
+PASS Calc expressions are resolved before inheritance 
 
index 8f9eafe..614a72a 100644 (file)
@@ -72,4 +72,18 @@ test(function(){
     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>
index 426e96e..1bf7eb6 100644 (file)
@@ -1,17 +1,57 @@
 
-FAIL CSS.registerProperty The given initial value does not parse for the given syntax.
-PASS <length> values are computed correctly for divWithFontSizeSet 
-FAIL <length-percentage> values are computed correctly for divWithFontSizeSet assert_equals: expected "calc(190px + -2%)" but got "calc(190px - 2%)"
-FAIL <length># values are computed correctly for divWithFontSizeSet assert_equals: expected "10px, 30px" but got "0px"
-FAIL <length-percentage># values are computed correctly for divWithFontSizeSet assert_equals: expected "3%, 80px, 22px" but got "0px"
-FAIL <length>+ values are computed correctly for divWithFontSizeSet assert_equals: expected "10px 30px" but got "0px"
-FAIL <length-percentage>+ values are computed correctly for divWithFontSizeSet assert_equals: expected "3% 80px 22px" but got "0px"
-FAIL <transform-function> values are computed correctly for divWithFontSizeSet assert_equals: expected "translateX(2px)" but got " translateX(2px)"
-PASS <length> values are computed correctly for divWithFontSizeInherited 
-FAIL <length-percentage> values are computed correctly for divWithFontSizeInherited assert_equals: expected "calc(190px + -2%)" but got "calc(190px - 2%)"
-FAIL <length># values are computed correctly for divWithFontSizeInherited assert_equals: expected "10px, 30px" but got "0px"
-FAIL <length-percentage># values are computed correctly for divWithFontSizeInherited assert_equals: expected "3%, 80px, 22px" but got "0px"
-FAIL <length>+ values are computed correctly for divWithFontSizeInherited assert_equals: expected "10px 30px" but got "0px"
-FAIL <length-percentage>+ values are computed correctly for divWithFontSizeInherited assert_equals: expected "3% 80px 22px" but got "0px"
-FAIL <transform-function> values are computed correctly for divWithFontSizeInherited assert_equals: expected "translateX(2px)" but got " translateX(2px)"
+PASS <length> values computed are correctly via var()-reference 
+PASS <length> values computed are correctly via var()-reference when font-size is inherited 
+PASS <length> values are computed correctly when font-size is inherited [14em] 
+PASS <length> values are computed correctly when font-size is inherited [calc(14em + 10px)] 
+PASS <length> values are computed correctly [12px] 
+PASS <length> values are computed correctly [13vw] 
+PASS <length> values are computed correctly [14em] 
+PASS <length> values are computed correctly [15vmin] 
+PASS <length> values are computed correctly [calc(16px - 7em + 10vh)] 
+PASS <length-percentage> values are computed correctly [17em] 
+FAIL <length-percentage> values are computed correctly [18%] assert_equals: expected "18%" but got "0px"
+FAIL <length-percentage> values are computed correctly [calc(19em - 2%)] assert_equals: expected "calc(-2% + 190px)" but got "0px"
+FAIL <length># values are computed correctly [10px, 3em] assert_equals: expected "10px, 30px" but got "0px"
+FAIL <length># values are computed correctly [4em ,9px] assert_equals: expected "40px, 9px" but got "0px"
+PASS <length># values are computed correctly [8em] 
+FAIL <length-percentage># values are computed correctly [3% , 10vmax  , 22px] assert_equals: expected "3%, 80px, 22px" but got "0px"
+FAIL <length-percentage># values are computed correctly [calc(50% + 1em), 4px] assert_equals: expected "calc(50% + 10px), 4px" but got "0px"
+FAIL <length-percentage># values are computed correctly [calc(13% + 37px)] assert_equals: expected "calc(13% + 37px)" but got "0px"
+FAIL <length>+ values are computed correctly [10px 3em] assert_equals: expected "10px 30px" but got "0px"
+FAIL <length>+ values are computed correctly [4em 9px] assert_equals: expected "40px 9px" but got "0px"
+FAIL <length-percentage>+ values are computed correctly [3% 10vmax 22px] assert_equals: expected "3% 80px 22px" but got "0px"
+FAIL <length-percentage>+ values are computed correctly [calc(50% + 1em) 4px] assert_equals: expected "calc(50% + 10px) 4px" but got "0px"
+FAIL <transform-function> values are computed correctly [translateX(2px)] The given initial value does not parse for the given syntax.
+FAIL <transform-function> values are computed correctly [translateX(10em)] The given initial value does not parse for the given syntax.
+FAIL <transform-function> values are computed correctly [translateX(calc(11em + 10%))] The given initial value does not parse for the given syntax.
+FAIL <transform-function>+ values are computed correctly [translateX(10%) scale(2)] The given initial value does not parse for the given syntax.
+FAIL <integer> values are computed correctly [15] assert_equals: expected "15" but got "0px"
+FAIL <integer> values are computed correctly [calc(15 + 15)] assert_equals: expected "30" but got "0px"
+FAIL <integer> values are computed correctly [calc(2.4)] assert_equals: expected "2" but got "0px"
+FAIL <integer> values are computed correctly [calc(2.6)] assert_equals: expected "3" but got "0px"
+FAIL <integer> values are computed correctly [calc(2.6 + 3.1)] assert_equals: expected "6" but got "0px"
+FAIL <integer>+ values are computed correctly [15 calc(2.4) calc(2.6)] assert_equals: expected "15 2 3" but got "0px"
+FAIL <color> values are computed correctly [#ff0000] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [#000f00] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [#00000a] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [#badbee] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [#badbee33] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [tomato] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [plum] The given initial value does not parse for the given syntax.
+FAIL <color> values are computed correctly [currentcolor] The given initial value does not parse for the given syntax.
+PASS * values are computed correctly [tomato] 
+FAIL tomato | plum values are computed correctly [plum] The given initial value does not parse for the given syntax.
+FAIL tomato | plum | <color> values are computed correctly [plum] The given initial value does not parse for the given syntax.
+PASS * values are computed correctly [-50grad] 
+FAIL <angle> values are computed correctly [180deg] The given initial value does not parse for the given syntax.
+FAIL <angle> values are computed correctly [400grad] The given initial value does not parse for the given syntax.
+FAIL <angle> values are computed correctly [calc(360deg + 400grad)] The given initial value does not parse for the given syntax.
+PASS * values are computed correctly [50s] 
+FAIL <time> values are computed correctly [1s] The given initial value does not parse for the given syntax.
+FAIL <time> values are computed correctly [1000ms] The given initial value does not parse for the given syntax.
+FAIL <time> values are computed correctly [calc(1000ms + 1s)] The given initial value does not parse for the given syntax.
+PASS * values are computed correctly [50dpi] 
+FAIL <resolution> values are computed correctly [1dppx] The given initial value does not parse for the given syntax.
+FAIL <resolution> values are computed correctly [96dpi] The given initial value does not parse for the given syntax.
+FAIL <resolution> values are computed correctly [calc(1dppx + 96dpi)] The given initial value does not parse for the given syntax.
 
index 30d6b4b..b1e5d23 100644 (file)
-<!DOCTYPE HTML>
+<!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;
 }
-#divWithFontSizeSet, #divWithFontSizeInherited {
-    --length-1: 12px;
-    --length-2: 13vw;
-    --length-3: 14em;
-    --length-4: 15vmin;
-    --length-5: calc(16px - 7em + 10vh);
-    --length-6: var(--length-3);
-    --length-percentage-1: 17em;
-    --length-percentage-2: 18%;
-    --length-percentage-3: calc(19em - 2%);
-    --csv-1: 10px, 3em;
-    --csv-2: 4em ,9px;
-    --csv-3: 8em;
-    --csv-4: 3% , 10vmax  , 22px;
-    --csv-5: calc(50% + 1em), 4px;
-    --csv-6: calc(13% + 37px);
-    --list-1: 10px 3em;
-    --list-2: 4em 9px;
-    --list-3: 3% 10vmax 22px;
-    --list-4: calc(50% + 1em) 4px;
-    --transform-function-1: translateX(2px);
-    --transform-function-2: translateX(10em);
-    --transform-function-3: translateX(calc(11em + 10%));
-    --transform-function-4: translateX(10%) scale(2);
-}
 </style>
 
 <div id=divWithFontSizeSet></div>
 <div id=parentDiv>
     <div id=divWithFontSizeInherited></div>
 </div>
+<div id="ref"></div>
 
 <script>
-test(() => {
-    CSS.registerProperty({name: '--length-1', syntax: '<length>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-2', syntax: '<length>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-3', syntax: '<length>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-4', syntax: '<length>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-5', syntax: '<length>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-6', syntax: '<length>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-percentage-1', syntax: '<length-percentage>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-percentage-2', syntax: '<length-percentage>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--length-percentage-3', syntax: '<length-percentage>', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--csv-1', syntax: '<length>#', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--csv-2', syntax: '<length>#', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--csv-3', syntax: '<length>#', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--csv-4', syntax: '<length-percentage>#', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--csv-5', syntax: '<length-percentage>#', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--csv-6', syntax: '<length-percentage>#', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--list-1', syntax: '<length>+', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--list-2', syntax: '<length>+', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--list-3', syntax: '<length-percentage>+', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--list-4', syntax: '<length-percentage>+', initialValue: '0px', inherits: false});
-    CSS.registerProperty({name: '--transform-function-1', syntax: '<transform-function>', initialValue: 'translateX(0px)', inherits: false});
-    CSS.registerProperty({name: '--transform-function-2', syntax: '<transform-function>', initialValue: 'translateX(0px)', inherits: false});
-    CSS.registerProperty({name: '--transform-function-3', syntax: '<transform-function>', initialValue: 'translateX(0px)', inherits: false});
-    CSS.registerProperty({name: '--transform-function-4', syntax: '<transform-function>+', initialValue: 'translateX(0px)', inherits: false});
-}, "CSS.registerProperty");
-
-for (var element of [divWithFontSizeSet, divWithFontSizeInherited]) {
-    var id = element.id;
-    var computedStyle = getComputedStyle(element);
 
-    test(function() {
-        assert_equals(computedStyle.getPropertyValue('--length-1'), '12px');
-        assert_equals(computedStyle.getPropertyValue('--length-2'), '104px');
-        assert_equals(computedStyle.getPropertyValue('--length-3'), '140px');
-        assert_equals(computedStyle.getPropertyValue('--length-4'), '90px');
-        assert_equals(computedStyle.getPropertyValue('--length-5'), '6px');
-        assert_equals(computedStyle.getPropertyValue('--length-6'), '140px');
-    }, "<length> values are computed correctly for " + id);
+// 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';
 
-    test(function() {
-        assert_equals(computedStyle.getPropertyValue('--length-percentage-1'), '170px');
-        assert_equals(computedStyle.getPropertyValue('--length-percentage-2'), '18%');
-        assert_equals(computedStyle.getPropertyValue('--length-percentage-3'), 'calc(190px + -2%)');
-    }, "<length-percentage> values are computed correctly for " + id);
+    let name = generate_property(reg);
 
-    test(function() {
-        assert_equals(computedStyle.getPropertyValue('--csv-1'), '10px, 30px');
-        assert_equals(computedStyle.getPropertyValue('--csv-2'), '40px, 9px');
-        assert_equals(computedStyle.getPropertyValue('--csv-3'), '80px');
-    }, "<length># values are computed correctly for " + id);
+    // 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);
 
-    test(function() {
-        assert_equals(computedStyle.getPropertyValue('--csv-4'), '3%, 80px, 22px');
-        assert_equals(computedStyle.getPropertyValue('--csv-5'), 'calc(10px + 50%), 4px');
-        assert_equals(computedStyle.getPropertyValue('--csv-6'), 'calc(37px + 13%)');
-    }, "<length-percentage># values are computed correctly for " + id);
+    try {
+        fn(name);
+    } finally {
+        node.remove();
+    }
+}
 
-    test(function() {
-        assert_equals(computedStyle.getPropertyValue('--list-1'), '10px 30px');
-        assert_equals(computedStyle.getPropertyValue('--list-2'), '40px 9px');
-    }, "<length>+ values are computed correctly for " + id);
+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);
+    });
+}
 
-    test(function() {
-        assert_equals(computedStyle.getPropertyValue('--list-3'), '3% 80px 22px');
-        assert_equals(computedStyle.getPropertyValue('--list-4'), 'calc(10px + 50%) 4px');
-    }, "<length-percentage>+ values are computed correctly for " + id);
+// 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_equals(computedStyle.getPropertyValue('--transform-function-1'), 'translateX(2px)');
-        assert_equals(computedStyle.getPropertyValue('--transform-function-2'), 'translateX(100px)');
-        assert_equals(computedStyle.getPropertyValue('--transform-function-3'), 'translateX(calc(110px + 10%))');
-        assert_equals(computedStyle.getPropertyValue('--transform-function-4'), 'translateX(10%) scale(2)');
-    }, "<transform-function> values are computed correctly for " + id);
+        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-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('<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');
+
 </script>
index efcc5c2..1dfea79 100644 (file)
@@ -1,9 +1,9 @@
 
 PASS CSSOM setters function as expected for unregistered properties 
 FAIL CSS.registerProperty The given initial value does not parse for the given syntax.
-FAIL Formerly valid values are still readable from inline styles but are computed as the unset value assert_equals: expected "blue" but got "hello"
+FAIL Formerly valid values are still readable from inline styles but are computed as the unset value assert_equals: expected "rgb(0, 0, 255)" but got "hello"
 FAIL Values not matching the registered type can't be set assert_equals: expected "hello" but got "20"
-FAIL Values can be removed from inline styles assert_equals: expected "red" but got " red"
+FAIL Values can be removed from inline styles assert_equals: expected "rgb(255, 0, 0)" but got " red"
 PASS Stylesheets can be modified by CSSOM 
-FAIL Valid values can be set on inline styles assert_equals: expected "blue" but got " blue"
+FAIL Valid values can be set on inline styles assert_equals: expected "rgb(255, 192, 203)" but got "pink"
 
index 019778e..59443b3 100644 (file)
@@ -46,7 +46,7 @@ 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'), 'blue');
+  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() {
@@ -62,7 +62,7 @@ test(function() {
   assert_equals(inlineStyle.getPropertyValue('--length'), '');
   assert_equals(inlineStyle.getPropertyValue('--color'), '');
   assert_equals(computedStyle.getPropertyValue('--length'), '10px');
-  assert_equals(computedStyle.getPropertyValue('--color'), 'red');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(255, 0, 0)');
 }, "Values can be removed from inline styles");
 
 test(function() {
@@ -80,9 +80,9 @@ test(function() {
   assert_equals(inlineStyle.getPropertyValue('--length'), '30px');
   assert_equals(inlineStyle.getPropertyValue('--color'), 'pink');
   assert_equals(computedStyle.getPropertyValue('--length'), '30px');
-  assert_equals(computedStyle.getPropertyValue('--color'), 'pink');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(255, 192, 203)');
   inlineStyle.setProperty('--color', 'inherit');
   assert_equals(inlineStyle.getPropertyValue('--color'), 'inherit');
-  assert_equals(computedStyle.getPropertyValue('--color'), 'blue');
+  assert_equals(computedStyle.getPropertyValue('--color'), 'rgb(0, 0, 255)');
 }, "Valid values can be set on inline styles");
 </script>
index 86c5a11..a3b762a 100644 (file)
@@ -1,3 +1,11 @@
 
-FAIL Initial values of registered properties can be referenced when no custom properties are explicitly set. The given initial value does not parse for the given syntax.
+PASS Initial value for <length> correctly computed [calc(10px + 15px)] 
+FAIL Initial value for <length-percentage> correctly computed [calc(1in + 10% + 4px)] The given initial value does not parse for the given syntax.
+FAIL Initial value for <color> correctly computed [pink, inherits] The given initial value does not parse for the given syntax.
+FAIL Initial value for <color> correctly computed [purple] The given initial value does not parse for the given syntax.
+FAIL Initial value for <transform-function> correctly computed [rotate(42deg)] The given initial value does not parse for the given syntax.
+FAIL Initial value for <transform-list> correctly computed [scale(calc(2 + 2))] The given initial value does not parse for the given syntax.
+FAIL Initial value for <transform-list> correctly computed [scale(calc(2 + 1)) translateX(calc(3px + 1px))] The given initial value does not parse for the given syntax.
+FAIL Initial inherited value can be substituted [purple, color] The given initial value does not parse for the given syntax.
+FAIL Initial non-inherited value can be substituted [pink, background-color] The given initial value does not parse for the given syntax.
 
index d655af6..82a012e 100644 (file)
@@ -1,35 +1,45 @@
-<!DOCTYPE HTML>
+<!DOCTYPE html>
 <link rel="help" href="https://drafts.css-houdini.org/css-properties-values-api/#dom-propertydescriptor-initialvalue" />
 <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>
-#target {
-  background: var(--inherited-color);
-  color: var(--non-inherited-color);
-}
-</style>
+<script src="./resources/utils.js"></script>
 <div id=target></div>
 <script>
-test(function() {
-    CSS.registerProperty({name: '--length', syntax: '<length>', initialValue: 'calc(10px + 15px)', inherits: false});
-    CSS.registerProperty({name: '--length-percentage', syntax: '<length-percentage>', initialValue: 'calc(1in + 10% + 4px)', inherits: false});
-    CSS.registerProperty({name: '--inherited-color', syntax: '<color>', initialValue: 'pink', inherits: true});
-    CSS.registerProperty({name: '--non-inherited-color', syntax: '<color>', initialValue: 'purple', inherits: false});
-    CSS.registerProperty({name: '--transform-function', syntax: '<transform-function>', initialValue: 'rotate(42deg)', inherits: false});
-    CSS.registerProperty({name: '--single-transform-list', syntax: '<transform-list>', initialValue: 'scale(calc(2 + 2))', inherits: false});
-    CSS.registerProperty({name: '--multiple-transform-list', syntax: '<transform-list>', initialValue: 'scale(calc(2 + 1)) translateX(calc(3px + 1px))', inherits: false});
 
-    computedStyle = getComputedStyle(target);
-    assert_equals(computedStyle.getPropertyValue('--length'), '25px');
-    assert_equals(computedStyle.getPropertyValue('--length-percentage'), 'calc(100px + 10%)');
-    assert_equals(computedStyle.getPropertyValue('--inherited-color'), 'pink');
-    assert_equals(computedStyle.getPropertyValue('--non-inherited-color'), 'purple');
-    assert_equals(computedStyle.getPropertyValue('--transform-function'), 'rotate(42deg)');
-    assert_equals(computedStyle.getPropertyValue('--single-transform-list'), 'scale(4)');
-    assert_equals(computedStyle.getPropertyValue('--multiple-transform-list'), 'scale(3) translateX(4px)');
+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-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 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)');
 
-    assert_equals(computedStyle.backgroundColor, 'rgb(255, 192, 203)');
-    assert_equals(computedStyle.color, 'rgb(128, 0, 128)');
-}, "Initial values of registered properties can be referenced when no custom properties are explicitly set.");
 </script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/utils.js b/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/utils.js
new file mode 100644 (file)
index 0000000..bef5956
--- /dev/null
@@ -0,0 +1,94 @@
+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>'
+  ]
+}
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/w3c-import.log b/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/w3c-import.log
new file mode 100644 (file)
index 0000000..e3e8558
--- /dev/null
@@ -0,0 +1,17 @@
+The tests in this directory were imported from the W3C repository.
+Do NOT modify these tests directly in WebKit.
+Instead, create a pull request on the WPT github:
+       https://github.com/web-platform-tests/wpt
+
+Then run the Tools/Scripts/import-w3c-tests in WebKit to reimport
+
+Do NOT modify or remove this file.
+
+------------------------------------------------------------------------
+Properties requiring vendor prefixes:
+None
+Property values requiring vendor prefixes:
+None
+------------------------------------------------------------------------
+List of files:
+/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/resources/utils.js
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils-expected.txt
new file mode 100644 (file)
index 0000000..d3cec37
--- /dev/null
@@ -0,0 +1,5 @@
+
+FAIL Default initial values of generated properties are valid (self-test). The given initial value does not parse for the given syntax.
+PASS Generated properties respect inherits flag 
+PASS Can't generate property with unknown fields 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils.html
new file mode 100644 (file)
index 0000000..05aa4b2
--- /dev/null
@@ -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(new Error(), () => generate_property({syntax: '<length>', foo: 1}));
+  assert_throws(new Error(), () => generate_property({syntax: '<length>', inherited: false}));
+  assert_throws(new Error(), () => generate_property({syntax: '<length>', initial: '10px'}));
+}, 'Can\'t generate property with unknown fields');
+
+</script>
index fc9d495..b551c9e 100644 (file)
@@ -1,69 +1,6 @@
-CONSOLE MESSAGE: line 349: TypeError: CSS.px is not a function. (In 'CSS.px(15)', 'CSS.px' is undefined)
+CONSOLE MESSAGE: line 38: TypeError: target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
 
-Harness Error (FAIL), message = TypeError: CSS.px is not a function. (In 'CSS.px(15)', 'CSS.px' is undefined)
+Harness Error (FAIL), message = TypeError: target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
 
 FAIL Computed * is reified as CSSUnparsedValue target.computedStyleMap is not a function. (In 'target.computedStyleMap()', 'target.computedStyleMap' is undefined)
-FAIL Computed <angle> is reified as CSSUnitValue The given initial value does not parse for the given syntax.
-FAIL Computed <color> is reified as CSSStyleValue The given initial value does not parse for the given syntax.
-FAIL Computed <custom-ident> is reified as CSSKeywordValue The given initial value does not parse for the given syntax.
-FAIL Computed <image> [url] is reified as CSSImageValue The given initial value does not parse for the given syntax.
-FAIL Computed <integer> is reified as CSSUnitValue The given initial value does not parse for the given syntax.
-FAIL Computed <length-percentage> [%] is reified as CSSUnitValue target.computedStyleMap is not a function. (In 'target.computedStyleMap()', 'target.computedStyleMap' is undefined)
-FAIL Computed <length-percentage> [px] is reified as CSSUnitValue target.computedStyleMap is not a function. (In 'target.computedStyleMap()', 'target.computedStyleMap' is undefined)
-FAIL Computed <length-percentage> [px + %] is reified as CSSMathSum Can't find variable: CSSMathSum
-FAIL Computed <length> is reified as CSSUnitValue target.computedStyleMap is not a function. (In 'target.computedStyleMap()', 'target.computedStyleMap' is undefined)
-FAIL Computed <number> is reified as CSSUnitValue The given initial value does not parse for the given syntax.
-FAIL Computed <percentage> is reified as CSSUnitValue target.computedStyleMap is not a function. (In 'target.computedStyleMap()', 'target.computedStyleMap' is undefined)
-FAIL Computed <resolution> is reified as CSSUnitValue The given initial value does not parse for the given syntax.
-FAIL Computed <time> is reified as CSSUnitValue The given initial value does not parse for the given syntax.
-FAIL Computed <url> is reified as CSSStyleValue The given initial value does not parse for the given syntax.
-FAIL Computed ident is reified as CSSKeywordValue The given initial value does not parse for the given syntax.
-FAIL First computed value correctly reified in space-separated list The given initial value does not parse for the given syntax.
-FAIL First computed value correctly reified in comma-separated list The given initial value does not parse for the given syntax.
-FAIL All computed values correctly reified in space-separated list The given initial value does not parse for the given syntax.
-FAIL All computed values correctly reified in comma-separated list The given initial value does not parse for the given syntax.
-FAIL attributeStyleMap.get returns CSSUnparsedValue for value with var references target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnparsedValue for value with var references undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnparsedValue for value with var references in list target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnparsedValue for value with var references in list undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnparsedValue for * target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnparsedValue for * undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <angle> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <angle> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSStyleValue for <color> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSStyleValue for <color> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSKeywordValue for <custom-ident> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSKeywordValue for <custom-ident> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSImageValue for <image> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSImageValue for <image> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <integer> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <integer> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <length-percentage> [10%] target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <length-percentage> [10%] undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <length-percentage> [10px] target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <length-percentage> [10px] undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSMathSum for <length-percentage> [calc(10px + 10%)] target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSMathSum for <length-percentage> [calc(10px + 10%)] undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <length> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <length> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <number> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <number> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <percentage> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <percentage> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <resolution> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <resolution> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <time> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <time> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSStyleValue for <url> target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSStyleValue for <url> undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSKeywordValue for thing1 | THING2 target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSKeywordValue for thing1 | THING2 undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <length>+ target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <length>+ undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.get returns CSSUnitValue for <length># target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.get returns CSSUnitValue for <length># undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.getAll returns a list of CSSUnitValues for <length>+ target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.getAll returns a list of CSSUnitValues for <length>+ undefined is not an object (evaluating 'rule.styleMap.clear')
-FAIL attributeStyleMap.getAll returns a list of CSSUnitValues for <length># target.attributeStyleMap.clear is not a function. (In 'target.attributeStyleMap.clear()', 'target.attributeStyleMap.clear' is undefined)
-FAIL styleMap.getAll returns a list of CSSUnitValues for <length># undefined is not an object (evaluating 'rule.styleMap.clear')
 
index 4e5eaa7..14a6204 100644 (file)
@@ -33,6 +33,13 @@ function gen_prop(syntax, initialValue) {
     return name;
 }
 
+// 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();
+});
+
 // On the target element, verify that computed value of 'name' is an instance
 // of 'expected' and not an instance of CSSUnparsedValue.
 //
@@ -214,86 +221,86 @@ test_style_property_map_get(function(styleDecl, propertyMap){
     let name2 = gen_prop('<length>', '0px');
     styleDecl.setProperty(name2, `var(${name1})`);
     assert_true(propertyMap.get(name2) instanceof CSSUnparsedValue);
-}, name => `${name}.get returns CSSUnparsedValue for value with var references`);
+}, name => `StylePropertyMap.get returns CSSUnparsedValue for value with var references (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     let name1 = gen_prop('<length>', '100px');
     let name2 = gen_prop('<length>#', '0px');
     styleDecl.setProperty(name2, `1px, var(${name1}), 3px`);
     assert_true(propertyMap.get(name2) instanceof CSSUnparsedValue);
-}, name => `${name}.get returns CSSUnparsedValue for value with var references in list`);
+}, name => `StylePropertyMap.get returns CSSUnparsedValue for value with var references in list (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '*', 'if(){}', CSSUnparsedValue);
-}, name => `${name}.get returns CSSUnparsedValue for *`);
+}, name => `StylePropertyMap.get returns CSSUnparsedValue for * (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<angle>', '42deg', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <angle>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <angle> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<color>', '#fefefe', CSSStyleValue);
-}, name => `${name}.get returns CSSStyleValue for <color>`);
+}, name => `StylePropertyMap.get returns CSSStyleValue for <color> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<custom-ident>', 'none', CSSKeywordValue);
-}, name => `${name}.get returns CSSKeywordValue for <custom-ident>`);
+}, name => `StylePropertyMap.get returns CSSKeywordValue for <custom-ident> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<image>', 'url(thing.png)', CSSImageValue);
-}, name => `${name}.get returns CSSImageValue for <image>`);
+}, name => `StylePropertyMap.get returns CSSImageValue for <image> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<integer>', '100', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <integer>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <integer> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<length-percentage>', '10%', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <length-percentage> [10%]`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <length-percentage> [10%] (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<length-percentage>', '10px', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <length-percentage> [10px]`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <length-percentage> [10px] (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<length-percentage>', 'calc(10px + 10%)', CSSMathSum);
-}, name => `${name}.get returns CSSMathSum for <length-percentage> [calc(10px + 10%)]`);
+}, name => `StylePropertyMap.get returns CSSMathSum for <length-percentage> [calc(10px + 10%)] (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<length>', '10px', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <length>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <length> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<number>', '42', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <number>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <number> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<percentage>', '10%', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <percentage>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <percentage> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<resolution>', '300dpi', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <resolution>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <resolution> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<time>', '42s', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <time>`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <time> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<url>', 'url(a)', CSSStyleValue);
-}, name => `${name}.get returns CSSStyleValue for <url>`);
+}, name => `StylePropertyMap.get returns CSSStyleValue for <url> (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, 'thing1 | THING2', 'thing1', CSSKeywordValue);
-}, name => `${name}.get returns CSSKeywordValue for thing1 | THING2`);
+}, name => `StylePropertyMap.get returns CSSKeywordValue for thing1 | THING2 (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<length>+', '10px 20px', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <length>+`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <length>+ (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     assert_attribute_get_type(styleDecl, propertyMap, '<length>#', '10px 20px', CSSUnitValue);
-}, name => `${name}.get returns CSSUnitValue for <length>#`);
+}, name => `StylePropertyMap.get returns CSSUnitValue for <length># (${name})`);
 
 // attributeStyleMap.getAll
 
@@ -302,14 +309,14 @@ test_style_property_map_get(function(styleDecl, propertyMap){
     styleDecl.setProperty(name, '10px 20px 30px');
     assert_equals(propertyMap.getAll(name).length, 3);
     assert_true(propertyMap.getAll(name).every(x => x instanceof CSSUnitValue));
-}, name => `${name}.getAll returns a list of CSSUnitValues for <length>+`);
+}, name => `StylePropertyMap.getAll returns a list of CSSUnitValues for <length>+ (${name})`);
 
 test_style_property_map_get(function(styleDecl, propertyMap){
     let name = gen_prop('<length>#', '0px');
     styleDecl.setProperty(name, '10px, 20px, 30px');
     assert_equals(propertyMap.getAll(name).length, 3);
     assert_true(propertyMap.getAll(name).every(x => x instanceof CSSUnitValue));
-}, name => `${name}.getAll returns a list of CSSUnitValues for <length>#`);
+}, name => `StylePropertyMap.getAll returns a list of CSSUnitValues for <length># (${name})`);
 
 // StylePropertyMap.set
 
@@ -318,13 +325,15 @@ function test_style_property_map_set_using_property_map(propertyMapName, propert
         let name = gen_prop(options.syntax, options.initialValue);
         propertyMap.clear();
 
+        let ensureArray = v => v.constructor === Array ? v : [v];
+
         for (let value of options.shouldAccept)
-            propertyMap.set(name, value);
+            propertyMap.set(name, ...ensureArray(value));
 
         for (let value of options.shouldReject) {
-            assert_throws(new TypeError(), () => propertyMap.set(name, value));
+            assert_throws(new TypeError(), () => propertyMap.set(name, ...ensureArray(value)));
         }
-    }, `${propertyMapName}.set accepts correct CSSUnitValues for ${options.syntax}`);
+    }, `StylePropertyMap.set accepts correct CSSStyleValues for ${options.syntax} (${propertyMapName})`);
 }
 
 // Verify that the correct CSSStyleValues are accepted/rejected for a registered
@@ -353,77 +362,77 @@ test_style_property_map_set({
     syntax: '<angle>',
     initialValue: '0deg',
     shouldAccept: [CSS.deg(42), CSS.turn(2), '42deg'],
-    shouldReject: [unparsed('42deg'), CSS.px(15), '50px'],
+    shouldReject: [unparsed('42deg'), CSS.px(15), '50px', [CSS.deg(15), '10deg']],
 });
 
 test_style_property_map_set({
     syntax: '<custom-ident>',
     initialValue: 'none',
     shouldAccept: [keyword('foo'), 'foo'],
-    shouldReject: [unparsed('foo'), CSS.px(15), '15px'],
+    shouldReject: [unparsed('foo'), CSS.px(15), '15px', [keyword('foo'), 'foo']],
 });
 
 test_style_property_map_set({
     syntax: '<image>',
     initialValue: 'url(a)',
     shouldAccept: [url_image('url(b)'), 'url(b)'],
-    shouldReject: [unparsed('url(b)'), CSS.px(100), '50px'],
+    shouldReject: [unparsed('url(b)'), CSS.px(100), '50px', [url_image('url(1)'), 'url(2)']],
 });
 
 test_style_property_map_set({
     syntax: '<integer>',
     initialValue: '0',
-    shouldAccept: [CSS.number(1), CSS.number(-42), '1', '-42'],
-    shouldReject: [unparsed('42'), CSS.px(100), '50px'],
+    shouldAccept: [CSS.number(1), CSS.number(-42), '1', '-42', 'calc(2.4)'],
+    shouldReject: [unparsed('42'), CSS.px(100), '50px', [CSS.number(42), '42'], 'calc(2px + 1px)'],
 });
 
 test_style_property_map_set({
     syntax: '<length-percentage>',
     initialValue: '0px',
     shouldAccept: [CSS.percent(10), CSS.px(1), CSS.em(1), '10px', '10%'],
-    shouldReject: [unparsed('10%'), unparsed('10px'), CSS.dpi(1), 'url(b)'],
+    shouldReject: [unparsed('10%'), unparsed('10px'), CSS.dpi(1), 'url(b)', [CSS.percent(10), '10%']],
 });
 
 test_style_property_map_set({
     syntax: '<length>',
     initialValue: '0px',
     shouldAccept: [CSS.px(10), CSS.em(10), CSS.vh(200), sum(CSS.px(10), CSS.em(20)), '10em', 'calc(10px + 10em)'],
-    shouldReject: [unparsed('10px'), CSS.percent(1), 'url(b)'],
+    shouldReject: [unparsed('10px'), CSS.percent(1), 'url(b)', [CSS.em(10), '10px']],
 });
 
 test_style_property_map_set({
     syntax: '<number>',
     initialValue: '0',
     shouldAccept: [CSS.number(1337), CSS.number(-42.5), '1337', '-42.5'],
-    shouldReject: [unparsed('42'), CSS.px(15), '#fef'],
+    shouldReject: [unparsed('42'), CSS.px(15), '#fef', [CSS.number(-42.5), '42.5']],
 });
 
 test_style_property_map_set({
     syntax: '<percentage>',
     initialValue: '0%',
     shouldAccept: [CSS.percent(10), '10%'],
-    shouldReject: [unparsed('10%'), CSS.px(1), '#fef'],
+    shouldReject: [unparsed('10%'), CSS.px(1), '#fef', [CSS.percent(10), '1%']],
 });
 
 test_style_property_map_set({
     syntax: '<resolution>',
     initialValue: '0dpi',
     shouldAccept: [CSS.dpi(100), CSS.dpcm(10), CSS.dppx(50), '100dpi'],
-    shouldReject: [unparsed('42'), CSS.px(15), '#fef'],
+    shouldReject: [unparsed('42'), CSS.px(15), '#fef', [CSS.dpi(1), '2dpi']],
 });
 
 test_style_property_map_set({
     syntax: '<time>',
     initialValue: '0s',
     shouldAccept: [CSS.s(42), CSS.ms(16), '16ms'],
-    shouldReject: [unparsed('42s'), CSS.px(15), '#fef'],
+    shouldReject: [unparsed('42s'), CSS.px(15), '#fef', [CSS.s(5), '6s']],
 });
 
 test_style_property_map_set({
     syntax: '<url>',
     initialValue: 'url(a)',
     shouldAccept: [url_image('url(b)')],
-    shouldReject: [unparsed('url(b)'), CSS.px(100), '#fef'],
+    shouldReject: [unparsed('url(b)'), CSS.px(100), '#fef', [url_image('url(1)'), 'url(2)']],
 });
 
 test_style_property_map_set({
@@ -437,14 +446,308 @@ test_style_property_map_set({
     syntax: 'none | thing | THING',
     initialValue: 'none',
     shouldAccept: [keyword('thing'), keyword('THING'), 'thing'],
-    shouldReject: [unparsed('thing'), CSS.px(15), keyword('notathing'), 'notathing'],
+    shouldReject: [unparsed('thing'), CSS.px(15), keyword('notathing'), 'notathing', [keyword('thing'), keyword('thing')]],
 });
 
 test_style_property_map_set({
     syntax: '<angle> | <length>',
     initialValue: '0deg',
     shouldAccept: [CSS.deg(42), CSS.turn(2), CSS.px(10), CSS.em(10), '10deg', '10px'],
-    shouldReject: [unparsed('42deg'), unparsed('20px'), CSS.s(1), '#fef'],
+    shouldReject: [unparsed('42deg'), unparsed('20px'), CSS.s(1), '#fef', [CSS.deg(42), '21deg']],
+});
+
+// StylePropertyMap.set for list-valued properties:
+
+test_style_property_map_set({
+    syntax: '<angle>+',
+    initialValue: '0deg',
+    shouldAccept: [CSS.deg(15), [CSS.deg(15), '10deg'], '15deg 10deg'],
+    shouldReject: [[CSS.deg(15), CSS.px(10)], '15deg 10px'],
+});
+
+test_style_property_map_set({
+    syntax: '<custom-ident>+',
+    initialValue: 'none',
+    shouldAccept: [keyword('foo'), [keyword('foo'), 'bar'], 'foo bar'],
+    shouldReject: [[keyword('foo'), CSS.px(10)], 'foo 10px'],
+});
+
+test_style_property_map_set({
+    syntax: '<image>+',
+    initialValue: 'url(a)',
+    shouldAccept: [url_image('url(1)'), [url_image('url(1)'), 'url(2)'], 'url(1) url(2)'],
+    shouldReject: [[url_image('url(1)'), CSS.px(10)], 'url(1) 10px'],
+});
+
+test_style_property_map_set({
+    syntax: '<integer>+',
+    initialValue: '0',
+    shouldAccept: [CSS.number(42), [CSS.number(42), '42'], '42 42', 'calc(2.4) calc(2.6)'],
+    shouldReject: [[CSS.number(42), keyword('noint')], '42 noint', 'calc(2px + 2px)'],
+});
+
+test_style_property_map_set({
+    syntax: '<length-percentage>+',
+    initialValue: '0px',
+    shouldAccept: [CSS.percent(10), [CSS.percent(10), '10%']],
+    shouldReject: [[CSS.percent(10), keyword('nolength')], '10% nolength'],
+});
+
+test_style_property_map_set({
+    syntax: '<length>+',
+    initialValue: '0px',
+    shouldAccept: [CSS.em(10), [CSS.em(10), '10px']],
+    shouldReject: [[CSS.em(10), keyword('nolength'), '10em nolength']],
+});
+
+test_style_property_map_set({
+    syntax: '<number>+',
+    initialValue: '0',
+    shouldAccept: [CSS.number(-42.5), [CSS.number(-42.5), '42.5'], '-42.5 42.5'],
+    shouldReject: [[CSS.number(-42.5), CSS.px(10)], '-42.5 10px'],
+});
+
+test_style_property_map_set({
+    syntax: '<percentage>+',
+    initialValue: '0%',
+    shouldAccept: [CSS.percent(10), [CSS.percent(10), '1%'], '10% 1%'],
+    shouldReject: [[CSS.percent(10), keyword('foo')], '10% foo'],
+});
+
+test_style_property_map_set({
+    syntax: '<resolution>+',
+    initialValue: '0dpi',
+    shouldAccept: [CSS.dpi(1), [CSS.dpi(1), '2dpi'], '1dpi 2dpi'],
+    shouldReject: [[CSS.dpi(1), keyword('foo')], '1dpi foo'],
+});
+
+test_style_property_map_set({
+    syntax: '<time>+',
+    initialValue: '0s',
+    shouldAccept: [CSS.s(5), [CSS.s(5), '6s'], '5s 6s'],
+    shouldReject: [[CSS.s(5), keyword('foo')], '5s foo'],
+});
+
+test_style_property_map_set({
+    syntax: '<url>+',
+    initialValue: 'url(a)',
+    shouldAccept: [url_image('url(1)'), [url_image('url(1)'), 'url(2)'], 'url(1) url(2)'],
+    shouldReject: [[url_image('url(1)'), CSS.px(10)], 'url(1) 10px'],
+});
+
+test_style_property_map_set({
+    syntax: 'thing+',
+    initialValue: 'thing',
+    shouldAccept: [keyword('thing'), [keyword('thing'), 'thing'], 'thing thing'],
+    shouldReject: [[keyword('thing'), CSS.px(10)], 'thing 10px'],
+});
+
+test_style_property_map_set({
+    syntax: '<length>#',
+    initialValue: '0px',
+    shouldAccept: [CSS.em(10), [CSS.em(10), '10px']],
+    shouldReject: [[CSS.em(10), keyword('nolength'), '10em nolength']],
+});
+
+function test_append_for_property_map(propertyMapName, propertyMap, options) {
+    test(function(){
+        let name = gen_prop(options.syntax, options.initialValue);
+
+        let ensureArray = v => v.constructor === Array ? v : [v];
+
+        for (let value of options.values) {
+            propertyMap.clear();
+
+            if (value.base !== null)
+                propertyMap.set(name, ...ensureArray(value.base));
+
+            // If 'null' is expected, it means we expect the append to fail.
+            if (value.expect !== null) {
+                propertyMap.append(name, ...ensureArray(value.append));
+                let actual = Array.from(propertyMap.getAll(name)).join(' ');
+                assert_equals(actual, value.expect);
+            } else {
+                assert_throws(new TypeError(), () => propertyMap.append(name, ...ensureArray(value.append)));
+            }
+        }
+    }, `StylePropertyMap.append accepts correct CSSStyleValues for ${options.syntax} (${propertyMapName})`);
+}
+
+// Verify that the correct CSSStyleValues are accepted/rejected when
+// appending values to list-valued properties.
+//
+// The same test is performed twice: once for attributeStyleMap, and once
+// for styleMap.
+function test_append(options) {
+    test_append_for_property_map('attributeStyleMap', target.attributeStyleMap, options);
+    test_append_for_property_map('styleMap', style.sheet.rules[0].styleMap, options);
+}
+
+test_append({
+    syntax: '<angle>+',
+    initialValue: '0deg',
+    values: [
+        { base: [CSS.deg(1)], append: [CSS.px(1)], expect: null },
+        { base: [CSS.deg(1)], append: [CSS.deg(2), CSS.px(1)], expect: null },
+        { base: [CSS.deg(1)], append: [CSS.deg(2), '1px'], expect: null },
+        { base: [CSS.deg(1)], append: [CSS.turn(2), CSS.deg(3)], expect: '1deg 2turn 3deg' },
+        { base: [CSS.deg(1), CSS.deg(2)], append: [CSS.deg(3)], expect: '1deg 2deg 3deg' },
+        { base: [CSS.deg(1)], append: [CSS.deg(2), '3deg'], expect: '1deg 2deg 3deg' },
+        { base: [CSS.deg(1)], append: [CSS.deg(2), '3turn 4deg'], expect: '1deg 2deg 3turn 4deg' },
+        { base: null, append: [CSS.deg(1), '2deg'], expect: '1deg 2deg' },
+    ],
+});
+
+test_append({
+    syntax: '<custom-ident>+',
+    initialValue: 'none',
+    values: [
+        { base: [keyword('foo')], append: [CSS.px(1)], expect: null },
+        { base: [keyword('foo')], append: [keyword('bar'), CSS.px(1)], expect: null },
+        { base: [keyword('foo')], append: [keyword('bar'), '1px'], expect: null },
+        { base: [keyword('foo')], append: [keyword('bar'), keyword('baz')], expect: 'foo bar baz' },
+        { base: [keyword('foo'), keyword('bar')], append: [keyword('baz')], expect: 'foo bar baz' },
+        { base: [keyword('foo')], append: [keyword('bar'), 'baz'], expect: 'foo bar baz' },
+        { base: [keyword('foo')], append: [keyword('bar'), 'baz zim'], expect: 'foo bar baz zim' },
+        { base: null, append: [keyword('foo'), 'bar'], expect: 'foo bar' },
+    ],
+});
+
+['<image>+', '<url>+'].forEach((syntax) => {
+    test_append({
+        syntax: syntax,
+        initialValue: 'url(0)',
+        values: [
+            { base: [url_image('url("1")')], append: [CSS.px(1)], expect: null },
+            { base: [url_image('url("1")')], append: [url_image('url("2")'), CSS.px(1)], expect: null },
+            { base: [url_image('url("1")')], append: [url_image('url("2")'), '1px'], expect: null },
+            { base: [url_image('url("1")')], append: [url_image('url("2")'), url_image('url("3")')], expect: 'url("1") url("2") url("3")' },
+            { base: [url_image('url("1")'), url_image('url("2")')], append: [url_image('url("3")')], expect: 'url("1") url("2") url("3")' },
+            { base: [url_image('url("1")')], append: [url_image('url("2")'), 'url("3")'], expect: 'url("1") url("2") url("3")' },
+            { base: [url_image('url("1")')], append: [url_image('url("2")'), 'url("3") url("4")'], expect: 'url("1") url("2") url("3") url("4")' },
+            { base: null, append: [url_image('url("1")'), 'url("2")'], expect: 'url("1") url("2")' },
+        ],
+    });
+});
+
+test_append({
+    syntax: '<integer>+',
+    initialValue: '0',
+    values: [
+        { base: [CSS.number(1)], append: [CSS.px(1)], expect: null },
+        { base: [CSS.number(1)], append: [CSS.number(2), CSS.px(1)], expect: null },
+        { base: [CSS.number(1)], append: [CSS.number(2), 'noint'], expect: null },
+        { base: [CSS.number(1)], append: [CSS.number(2), CSS.number(3)], expect: '1 2 3' },
+        { base: [CSS.number(1), CSS.number(2)], append: [CSS.number(3)], expect: '1 2 3' },
+        { base: [CSS.number(1)], append: [CSS.number(2), '3'], expect: '1 2 3' },
+        { base: [CSS.number(1)], append: [CSS.number(2), '3 4'], expect: '1 2 3 4' },
+        { base: null, append: [CSS.number(1), '2'], expect: '1 2' },
+    ],
+});
+
+test_append({
+    syntax: '<length-percentage>+',
+    initialValue: '0px',
+    values: [
+        { base: [CSS.px(1)], append: [keyword('nolength')], expect: null },
+        { base: [CSS.px(1)], append: [CSS.px(2), keyword('nolength')], expect: null },
+        { base: [CSS.px(1)], append: [CSS.px(2), 'nolength'], expect: null },
+        { base: [CSS.px(1)], append: [CSS.px(2), CSS.percent(3)], expect: '1px 2px 3%' },
+        { base: [CSS.px(1), CSS.px(2)], append: [CSS.percent(3)], expect: '1px 2px 3%' },
+        { base: [CSS.px(1)], append: [CSS.percent(2), '3px'], expect: '1px 2% 3px' },
+        { base: [CSS.px(1)], append: [CSS.px(2), '3% 4px'], expect: '1px 2px 3% 4px' },
+        { base: null, append: [CSS.px(1), '2%'], expect: '1px 2%' },
+    ],
+});
+
+test_append({
+    syntax: '<length>+',
+    initialValue: '0',
+    values: [
+        { base: [CSS.px(1)], append: [keyword('nolength')], expect: null },
+        { base: [CSS.px(1)], append: [CSS.px(2), keyword('nolength')], expect: null },
+        { base: [CSS.px(1)], append: [CSS.px(2), 'nolength'], expect: null },
+        { base: [CSS.px(1)], append: [CSS.em(2), CSS.px(3)], expect: '1px 2em 3px' },
+        { base: [CSS.px(1), CSS.em(2)], append: [CSS.vh(3)], expect: '1px 2em 3vh' },
+        { base: [CSS.px(1)], append: [CSS.em(2), '3px'], expect: '1px 2em 3px' },
+        { base: [CSS.px(1)], append: [CSS.px(2), '3em 4cm'], expect: '1px 2px 3em 4cm' },
+        { base: null, append: [CSS.vh(1), '2px'], expect: '1vh 2px' },
+    ],
+});
+
+test_append({
+    syntax: '<number>+',
+    initialValue: '0',
+    values: [
+        { base: [CSS.number(-1)], append: [keyword('NaN')], expect: null },
+        { base: [CSS.number(-1)], append: [CSS.number(2.5), keyword('NaN')], expect: null },
+        { base: [CSS.number(-1)], append: [CSS.number(2.5), '1px'], expect: null },
+        { base: [CSS.number(-1)], append: [CSS.number(2.5), CSS.number(3.2)], expect: '-1 2.5 3.2' },
+        { base: [CSS.number(-1), CSS.number(2.5)], append: [CSS.number(3.2)], expect: '-1 2.5 3.2' },
+        { base: [CSS.number(-1)], append: [CSS.number(2.5), '3.2'], expect: '-1 2.5 3.2' },
+        { base: [CSS.number(-1)], append: [CSS.number(2.5), '3.2 4'], expect: '-1 2.5 3.2 4' },
+        { base: null, append: [CSS.number(-1), '2.5'], expect: '-1 2.5' },
+    ],
+});
+
+test_append({
+    syntax: '<percentage>+',
+    initialValue: '0%',
+    values: [
+        { base: [CSS.percent(1)], append: [CSS.px(1)], expect: null },
+        { base: [CSS.percent(1)], append: [CSS.percent(2), CSS.px(1)], expect: null },
+        { base: [CSS.percent(1)], append: [CSS.percent(2), '1px'], expect: null },
+        { base: [CSS.percent(1)], append: [CSS.percent(2), CSS.percent(3)], expect: '1% 2% 3%' },
+        { base: [CSS.percent(1), CSS.percent(2)], append: [CSS.percent(3)], expect: '1% 2% 3%' },
+        { base: [CSS.percent(1)], append: [CSS.percent(2), '3%'], expect: '1% 2% 3%' },
+        { base: [CSS.percent(1)], append: [CSS.percent(2), '3% 4%'], expect: '1% 2% 3% 4%' },
+        { base: null, append: [CSS.percent(1), '2%'], expect: '1% 2%' },
+    ],
+});
+
+test_append({
+    syntax: '<resolution>+',
+    initialValue: '0dpi',
+    values: [
+        { base: [CSS.dpi(1)], append: [CSS.px(1)], expect: null },
+        { base: [CSS.dpi(1)], append: [CSS.dpi(2), CSS.px(1)], expect: null },
+        { base: [CSS.dpi(1)], append: [CSS.dpi(2), '1px'], expect: null },
+        { base: [CSS.dpi(1)], append: [CSS.dpi(2), CSS.dpi(3)], expect: '1dpi 2dpi 3dpi' },
+        { base: [CSS.dpi(1), CSS.dpi(2)], append: [CSS.dpi(3)], expect: '1dpi 2dpi 3dpi' },
+        { base: [CSS.dpi(1)], append: [CSS.dpi(2), '3dpi'], expect: '1dpi 2dpi 3dpi' },
+        { base: [CSS.dpi(1)], append: [CSS.dpi(2), '3dpi 4dpi'], expect: '1dpi 2dpi 3dpi 4dpi' },
+        { base: null, append: [CSS.dpi(1), '2dpi'], expect: '1dpi 2dpi' },
+    ],
+});
+
+test_append({
+    syntax: '<time>+',
+    initialValue: '0s',
+    values: [
+        { base: [CSS.s(1)], append: [CSS.px(1)], expect: null },
+        { base: [CSS.s(1)], append: [CSS.s(2), CSS.px(1)], expect: null },
+        { base: [CSS.s(1)], append: [CSS.ms(2), '1px'], expect: null },
+        { base: [CSS.s(1)], append: [CSS.ms(2), CSS.s(3)], expect: '1s 2ms 3s' },
+        { base: [CSS.s(1), CSS.s(2)], append: [CSS.s(3)], expect: '1s 2s 3s' },
+        { base: [CSS.s(1)], append: [CSS.s(2), '3s'], expect: '1s 2s 3s' },
+        { base: [CSS.s(1)], append: [CSS.s(2), '3ms 4s'], expect: '1s 2s 3ms 4s' },
+        { base: null, append: [CSS.s(1), '2s'], expect: '1s 2s' },
+    ],
+});
+
+test_append({
+    syntax: 'foo+',
+    initialValue: 'foo',
+    values: [
+        { base: [keyword('foo')], append: [CSS.px(1)], expect: null },
+        { base: [keyword('foo')], append: [keyword('foo'), CSS.px(1)], expect: null },
+        { base: [keyword('foo')], append: [keyword('foo'), '1px'], expect: null },
+        { base: [keyword('foo')], append: [keyword('foo'), keyword('foo')], expect: 'foo foo foo' },
+        { base: [keyword('foo'), keyword('foo')], append: [keyword('foo')], expect: 'foo foo foo' },
+        { base: [keyword('foo')], append: [keyword('foo'), 'foo'], expect: 'foo foo foo' },
+        { base: [keyword('foo')], append: [keyword('foo'), 'foo foo'], expect: 'foo foo foo foo' },
+        { base: null, append: [keyword('foo'), keyword('foo')], expect: 'foo foo' },
+    ],
 });
 
 // CSSStyleValue.parse/parseAll
@@ -537,4 +840,181 @@ test(function(){
     assert_parsed_type(gen_prop('<length># | fail', 'fail'), '10px, 20px', CSSUnitValue);
 }, 'CSSStyleValue.parse[All] returns list of CSSUnitValues for <length>#');
 
+// Direct CSSStyleValue objects:
+
+function gen_all_props() {
+    return [
+        gen_prop('*', 'foo'),
+        gen_prop('foo', 'foo'),
+        gen_prop('<angle>', '0deg'),
+        gen_prop('<color>', 'rgb(1, 2, 3)'),
+        gen_prop('<custom-ident>', 'thing'),
+        gen_prop('<image>', 'url(a)'),
+        gen_prop('<integer>', '0'),
+        gen_prop('<length-percentage>', 'calc(10px + 10%)'),
+        gen_prop('<length>', '0px'),
+        gen_prop('<number>', '0.5'),
+        gen_prop('<percentage>', '0%'),
+        gen_prop('<resolution>', '0dpi'),
+        gen_prop('<time>', '0s'),
+        gen_prop('<transform-function>', 'rotateX(0deg)'),
+        gen_prop('<transform-list>', 'rotateX(0deg)'),
+        gen_prop('<url>', 'url(a)')
+    ];
+}
+
+test(function(){
+    let props0 = gen_all_props();
+    let props1 = gen_all_props();
+
+    for (let i = 0; i < props0.length; i++) {
+        let prop0 = props0[i];
+        let prop1 = props1[i];
+
+        // Abuse computedStyleMap to get the initialValue (just to get some
+        // value that will parse for prop0/1's syntax).
+        let initialValue = target.computedStyleMap().get(prop0);
+
+        // 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;
+        }
+
+        let value = CSSStyleValue.parse(prop0, initialValue.toString());
+
+        // A value parsed for prop0 must be assignable to prop0.
+        target.attributeStyleMap.clear();
+        target.attributeStyleMap.set(prop0, value); // Don't throw.
+
+        // A value parsed for prop0 must not be assignable to prop1, even if
+        // the properties have compatible syntaxes.
+        assert_throws(new TypeError(), () => {
+            target.attributeStyleMap.clear();
+            target.attributeStyleMap.set(prop1, value);
+        });
+    }
+}, 'Direct CSSStyleValue instances are tied to their associated property');
+
+// StylePropertyMapReadOnly iteration
+
+test(function(){
+    let name = gen_prop('<length>', '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');
+
+// Verifies that iterating a StylePropertyMap[ReadOnly] yields correctly
+// typed objects for a given syntax/value.
+function test_iteration_type_for_property_map(propertyMapName, propertyMap, options) {
+    test(function(){
+        let name = gen_prop(options.syntax, options.initialValue);
+        if (propertyMap instanceof StylePropertyMap) {
+            // Only set the value if the propertyMap is mutable.
+            propertyMap.set(name, options.value);
+        }
+        let result = Array.from(propertyMap).filter(e => e[0] == name)[0];
+        let value = result[1];
+        assert_true(options.expect(value));
+    }, `Iteration on ${propertyMapName} produces correct type for ${options.syntax}`);
+}
+
+function test_iteration_type(options) {
+    test_iteration_type_for_property_map('computedStyleMap', target.computedStyleMap(), options);
+    test_iteration_type_for_property_map('attributeStyleMap', target.attributeStyleMap, options);
+    test_iteration_type_for_property_map('styleMap', style.sheet.rules[0].styleMap, options);
+}
+
+test_iteration_type({
+    syntax: '*',
+    initialValue: 'none',
+    value: 'thing',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnparsedValue,
+});
+
+test_iteration_type({
+    syntax: '<angle>',
+    initialValue: '0deg',
+    value: '42deg',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<custom-ident>',
+    initialValue: 'none',
+    value: 'thing',
+    expect: v => v.length == 1 && v[0] instanceof CSSKeywordValue,
+});
+
+test_iteration_type({
+    syntax: '<image>',
+    initialValue: 'url(a)',
+    value: 'url(b)',
+    expect: v => v.length == 1 && v[0] instanceof CSSImageValue,
+});
+
+test_iteration_type({
+    syntax: '<integer>',
+    initialValue: '0',
+    value: '100',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<length>',
+    initialValue: '0px',
+    value: '10px',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<number>',
+    initialValue: '0',
+    value: '42',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<percentage>',
+    initialValue: '0%',
+    value: '10%',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<resolution>',
+    initialValue: '0dpi',
+    value: '300dpi',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<time>',
+    initialValue: '0s',
+    value: '10s',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
+test_iteration_type({
+    syntax: '<url>',
+    initialValue: 'url(a)',
+    value: 'url(b)',
+    expect: v => v.length == 1 && v[0].constructor === CSSStyleValue,
+});
+
+test_iteration_type({
+    syntax: 'none | thing | THING',
+    initialValue: 'none',
+    value: 'THING',
+    expect: v => v.length == 1 && v[0] instanceof CSSKeywordValue,
+});
+
+test_iteration_type({
+    syntax: '<angle> | <length>',
+    initialValue: '0deg',
+    value: '10px',
+    expect: v => v.length == 1 && v[0] instanceof CSSUnitValue,
+});
+
 </script>
index b720865..9979860 100644 (file)
@@ -1,4 +1,4 @@
-CONSOLE MESSAGE: line 63: TypeError: element.attributeStyleMap.clear is not a function. (In 'element.attributeStyleMap.clear()', 'element.attributeStyleMap.clear' is undefined)
+CONSOLE MESSAGE: line 74: TypeError: element.attributeStyleMap.clear is not a function. (In 'element.attributeStyleMap.clear()', 'element.attributeStyleMap.clear' is undefined)
 
 FAIL Untitled TypeError: element.attributeStyleMap.clear is not a function. (In 'element.attributeStyleMap.clear()', 'element.attributeStyleMap.clear' is undefined)
 
index d653485..c26e1cd 100644 (file)
@@ -4,12 +4,12 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script>
-    function register_length(name) {
+    function register_length(name, inherits=true) {
         CSS.registerProperty({
             name: name,
             syntax: '<length>',
             initialValue: '0px',
-            inherits: false
+            inherits: inherits
         });
     }
 
@@ -22,6 +22,9 @@
     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);
 </script>
 <style>
     :root {
         --font-size-ch-via-var: var(--unregistered-ch);
     }
 
+    #parent {
+        --font-size-em-inherited: 4em;
+        --font-size-ex-inherited: 4ex;
+        --font-size-ch-inherited: 4ch;
+    }
+
     #target {
         font-size: 11px;
     }
 </style>
 
-<div id=target></div>
+<div id=parent>
+    <div id=target></div>
+</div>
 <div id=ref></div>
 
 <script>
         assert_property_equals('--font-size-rem-via-var', expected10rem, root);
     }, 'Lengths with rem 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');
+
 </script>
index 58d6c84..65d1169 100644 (file)
@@ -25,14 +25,13 @@ test(function() {
     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');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-a'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-b'), '');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-c'), '30px');
+    assert_equals(computedStyle.getPropertyValue('--registered-1-d'), '4px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-1-a'), '');
+    assert_equals(computedStyle.left, '50px');
+    assert_equals(computedStyle.top, '60px');
 }, "A var() cycle between two registered properties is handled correctly.");
 </script>
 
@@ -63,18 +62,18 @@ test(function() {
     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('--registered-2-a'), '');
     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-b'), '30px');
+    assert_equals(computedStyle.getPropertyValue('--registered-2-c'), '3px');
     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-b'), '50px');
+    assert_equals(computedStyle.getPropertyValue('--unregistered-2-c'), '');
     assert_equals(computedStyle.getPropertyValue('--unregistered-2-d'), '60px');
     assert_equals(computedStyle.getPropertyValue('--unregistered-2-e'), '');
-    assert_equals(computedStyle.left, '1px');
+    assert_equals(computedStyle.left, '70px');
     assert_equals(computedStyle.top, '80px');
 }, "A var() cycle between a registered properties and an unregistered property is handled correctly.");
 </script>
index 39e675d..4285a41 100644 (file)
@@ -1,6 +1,15 @@
 
-FAIL var() references work with registered properties assert_equals: expected "  10px" but got " 10px"
+PASS var() references work with registered properties 
 FAIL References to registered var()-properties work in registered lists assert_equals: expected "1px, 10px, 2px" but got "0px"
 FAIL References to mixed registered and unregistered var()-properties work in registered lists assert_equals: expected "1px, 20px, 10px, 2px" but got "0px"
 FAIL Registered lists may be concatenated assert_equals: expected "1px, 10px, 2px, 1px, 20px, 10px, 2px" but got "0px"
+PASS Font-relative units are absolutized when substituting 
+PASS Calc expressions are resolved when substituting 
+FAIL Lists with relative units are absolutized when substituting assert_equals: expected "110px, 120px" but got "0px"
+PASS Valid fallback does not invalidate var()-reference [<length>, 10px] 
+PASS Valid fallback does not invalidate var()-reference [<length> | <color>, red] 
+PASS Valid fallback does not invalidate var()-reference [<length> | none, none] 
+FAIL Invalid fallback invalidates var()-reference [<length>, red] assert_equals: expected "" but got "40px"
+FAIL Invalid fallback invalidates var()-reference [<length> | none, nolength] assert_equals: expected "" but got "40px"
+FAIL Invalid fallback invalidates var()-reference [<length>, var(--novar)] assert_equals: expected "" but got "40px"
 
index d8a8315..1667545 100644 (file)
@@ -2,6 +2,7 @@
 <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;
@@ -54,7 +55,7 @@ test(function() {
     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-2'), ' 10px');
     assert_equals(computedStyle.getPropertyValue('--length-3'), ' calc(123px + 123px)');
     assert_equals(computedStyle.getPropertyValue('--registered-length-invalid'), '15px');
 
@@ -96,5 +97,77 @@ test(function(){
     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');
+
+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)');
 
 </script>
index 86cd8dd..6307bcd 100644 (file)
@@ -23,6 +23,7 @@ List of files:
 /LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-computation.html
 /LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-cssom.html
 /LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/registered-property-initial.html
+/LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/self-utils.html
 /LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/typedom.tentative.html
 /LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/unit-cycles.html
 /LayoutTests/imported/w3c/web-platform-tests/css/css-properties-values-api/url-resolution.html
index ea10be5..ff90760 100644 (file)
@@ -1,3 +1,23 @@
+2018-12-18  Justin Michaud  <justin_michaud@apple.com>
+
+        Update CSS Properties and Values API to use new cycle fallback behaviour
+        https://bugs.webkit.org/show_bug.cgi?id=192800
+
+        Reviewed by Antti Koivisto.
+
+        Make CSS variables that are registered and involved in a cycle be treated as invalid. This also fixes a crash in the
+        wpt tests where relative units and calc() in a registered property's initial value would break things instead of failing. 
+
+        * css/CSSCustomPropertyValue.h:
+        * css/CSSVariableReferenceValue.cpp:
+        (WebCore::resolveVariableReference):
+        * css/DOMCSSRegisterCustomProperty.cpp:
+        (WebCore::DOMCSSRegisterCustomProperty::registerProperty):
+        * css/StyleResolver.cpp:
+        (WebCore::StyleResolver::applyCascadedCustomProperty):
+        * css/parser/CSSPropertyParser.cpp:
+        (WebCore::CSSPropertyParser::parseTypedCustomPropertyValue):
+
 2018-12-18  Daniel Bates  <dabates@apple.com>
 
         Wrong value for key property in keydown and keyup events generated holding Control key
index fe85025..bdaa74d 100644 (file)
@@ -67,6 +67,8 @@ public:
     
     static Ref<CSSCustomPropertyValue> createSyntaxLength(const AtomicString& name, Length value)
     {
+        ASSERT(!value.isUndefined());
+        ASSERT(!value.isCalculated());
         return adoptRef(*new CSSCustomPropertyValue(name, { WTFMove(value) }));
     }
 
index 77a523e..94d63a3 100644 (file)
@@ -70,16 +70,20 @@ static bool resolveVariableReference(CSSParserTokenRange range, Vector<CSSParser
 
     // Apply fallback to detect cycles
     Vector<CSSParserToken> fallbackResult;
-    resolveVariableFallback(CSSParserTokenRange(range), fallbackResult, state);
+    bool fallbackReturn = resolveVariableFallback(CSSParserTokenRange(range), fallbackResult, state);
 
     auto* property = style.getCustomProperty(variableName);
 
-    if (!property || property->isUnset() || property->isInvalid()) {
+    if (!property || property->isUnset()) {
         auto* registered = registeredProperties.get(variableName);
         if (registered && registered->initialValue())
             property = registered->initialValue();
-        else
-            return resolveVariableFallback(range, result, state);
+    }
+
+    if (!property || property->isInvalid()) {
+        if (fallbackReturn)
+            result.appendVector(fallbackResult);
+        return fallbackReturn;
     }
 
     ASSERT(property->isResolved());
index a890669..7ecd93d 100644 (file)
@@ -40,6 +40,9 @@ namespace WebCore {
 
 ExceptionOr<void> DOMCSSRegisterCustomProperty::registerProperty(Document& document, const DOMCSSCustomPropertyDescriptor& descriptor)
 {
+    if (!isCustomPropertyName(descriptor.name))
+        return Exception { SyntaxError, "The name of this property is not a custom property name." };
+
     RefPtr<CSSCustomPropertyValue> initialValue;
     if (!descriptor.initialValue.isEmpty()) {
         CSSTokenizer tokenizer(descriptor.initialValue);
@@ -50,12 +53,17 @@ ExceptionOr<void> DOMCSSRegisterCustomProperty::registerProperty(Document& docum
         styleResolver.applyPropertyToStyle(CSSPropertyInvalid, nullptr, styleResolver.defaultStyleForElement());
         styleResolver.updateFont();
 
+        HashSet<CSSPropertyID> dependencies;
+        CSSPropertyParser::collectParsedCustomPropertyValueDependencies(descriptor.syntax, false, dependencies, tokenizer.tokenRange(), strictCSSParserContext());
+
+        if (!dependencies.isEmpty())
+            return Exception { SyntaxError, "The given initial value must be computationally independent." };
+
         initialValue = CSSPropertyParser::parseTypedCustomPropertyValue(descriptor.name, descriptor.syntax, tokenizer.tokenRange(), styleResolver, strictCSSParserContext());
 
         if (!initialValue || !initialValue->isResolved())
             return Exception { SyntaxError, "The given initial value does not parse for the given syntax." };
 
-        HashSet<CSSPropertyID> dependencies;
         initialValue->collectDirectComputationalDependencies(dependencies);
         initialValue->collectDirectRootComputationalDependencies(dependencies);
 
index 74fa3f5..d5ee934 100644 (file)
@@ -2319,6 +2319,7 @@ void StyleResolver::applyCascadedCustomProperty(const String& name, ApplyCascade
         return;
 
     auto property = state.cascade->customProperties().get(name);
+    bool inCycle = state.inProgressPropertiesCustom.contains(name);
 
     for (auto index : { SelectorChecker::MatchDefault, SelectorChecker::MatchLink, SelectorChecker::MatchVisited }) {
         if (!property.cssValue[index])
@@ -2328,13 +2329,9 @@ void StyleResolver::applyCascadedCustomProperty(const String& name, ApplyCascade
 
         Ref<CSSCustomPropertyValue> valueToApply = CSSCustomPropertyValue::create(downcast<CSSCustomPropertyValue>(*property.cssValue[index]));
 
-        if (state.inProgressPropertiesCustom.contains(name)) {
-            // We are in a cycle, so reset the value.
-            state.appliedCustomProperties.add(name);
-            // Resolve this value so that we reset its dependencies
-            if (WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value()))
-                resolvedVariableValue(CSSPropertyCustom, valueToApply.get(), state);
-            valueToApply = CSSCustomPropertyValue::createWithID(name, CSSValueUnset);
+        if (inCycle) {
+            state.appliedCustomProperties.add(name); // Make sure we do not try to apply this property again while resolving it.
+            valueToApply = CSSCustomPropertyValue::createWithID(name, CSSValueInvalid);
         }
 
         state.inProgressPropertiesCustom.add(name);
@@ -2342,6 +2339,9 @@ void StyleResolver::applyCascadedCustomProperty(const String& name, ApplyCascade
         if (WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value())) {
             RefPtr<CSSValue> parsedValue = resolvedVariableValue(CSSPropertyCustom, valueToApply.get(), state);
 
+            if (state.appliedCustomProperties.contains(name))
+                return; // There was a cycle and the value was reset, so bail.
+
             if (!parsedValue)
                 parsedValue = CSSCustomPropertyValue::createWithID(name, CSSValueUnset);
 
@@ -2365,8 +2365,23 @@ void StyleResolver::applyCascadedCustomProperty(const String& name, ApplyCascade
             }
             applyProperty(CSSPropertyCustom, valueToApply.ptr(), state, index);
         }
-        state.inProgressPropertiesCustom.remove(name);
-        state.appliedCustomProperties.add(name);
+    }
+
+    state.inProgressPropertiesCustom.remove(name);
+    state.appliedCustomProperties.add(name);
+
+    for (auto index : { SelectorChecker::MatchDefault, SelectorChecker::MatchLink, SelectorChecker::MatchVisited }) {
+        if (!property.cssValue[index])
+            continue;
+        if (index != SelectorChecker::MatchDefault && this->state().style()->insideLink() == InsideLink::NotInside)
+            continue;
+
+        Ref<CSSCustomPropertyValue> valueToApply = CSSCustomPropertyValue::create(downcast<CSSCustomPropertyValue>(*property.cssValue[index]));
+
+        if (inCycle && WTF::holds_alternative<Ref<CSSVariableReferenceValue>>(valueToApply->value())) {
+            // Resolve this value so that we reset its dependencies.
+            resolvedVariableValue(CSSPropertyCustom, valueToApply.get(), state);
+        }
     }
 }
 
index 5cb8945..98df418 100644 (file)
@@ -4393,8 +4393,11 @@ RefPtr<CSSCustomPropertyValue> CSSPropertyParser::parseTypedCustomPropertyValue(
     if (syntax != "*") {
         m_range.consumeWhitespace();
         auto primitiveVal = consumeWidthOrHeight(m_range, m_context);
-        if (primitiveVal && primitiveVal->isPrimitiveValue())
-            return CSSCustomPropertyValue::createSyntaxLength(name, StyleBuilderConverter::convertLength(styleResolver, *primitiveVal));
+        if (primitiveVal && primitiveVal->isPrimitiveValue() && downcast<CSSPrimitiveValue>(*primitiveVal).isLength()) {
+            auto length = StyleBuilderConverter::convertLength(styleResolver, *primitiveVal);
+            if (!length.isCalculated() && !length.isUndefined())
+                return CSSCustomPropertyValue::createSyntaxLength(name, WTFMove(length));
+        }
     } else {
         auto propertyValue = CSSCustomPropertyValue::createSyntaxAll(name, CSSVariableData::create(m_range));
         while (!m_range.atEnd())