[css-properties-values-api] Multi-value StylePropertyMap.set.

This adds support for setting multiple values with TypedOM for list-valued
custom properties. For example, for a property --x with syntax <length>+,
it will now be possible to do:

  element.attributeStyleMap.set('--x', CSS.px(1), CSS.px(2));

This is done by producing a CSSVariableReferenceValue holding tokens
equivalent to the incoming CSSStyleValues, provided that those values
match the grammar of the custom property.

R=chrishtr@chromium.org

Bug: 641877
Change-Id: Ic28497edbbea894a9c09b840dcb6c8fb825d99bb
Reviewed-on: https://chromium-review.googlesource.com/c/1270963
Commit-Queue: Anders Ruud <andruud@chromium.org>
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#599164}
diff --git a/css/css-properties-values-api/typedom.tentative.html b/css/css-properties-values-api/typedom.tentative.html
index 6d0623b..51e45e8 100644
--- a/css/css-properties-values-api/typedom.tentative.html
+++ b/css/css-properties-values-api/typedom.tentative.html
@@ -325,11 +325,13 @@
         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}`);
 }
@@ -360,77 +362,77 @@
     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'],
+    shouldReject: [unparsed('42'), CSS.px(100), '50px', [CSS.number(42), '42']],
 });
 
 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({
@@ -444,14 +446,107 @@
     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'],
+    shouldReject: [[CSS.number(42), keyword('noint')], '42 noint'],
+});
+
+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']],
 });
 
 // CSSStyleValue.parse/parseAll