Bug 1064937 - Part 2: Add tests. r=birtles draft
authorDaisuke Akatsuka <daisuke@mozilla-japan.org>
Fri, 11 Nov 2016 16:40:53 +0900
changeset 437645 469985791bd727a37e83538b78ea7c03dae216b6
parent 437644 e5a1d5492a84fc15af10a774ac094a48fcd523ff
child 536688 3d02eb894b5a6ff61160010b428cf26a5fbb7e66
push id35470
push userbmo:daisuke@mozilla-japan.org
push dateFri, 11 Nov 2016 07:48:14 +0000
reviewersbirtles
bugs1064937
milestone52.0a1
Bug 1064937 - Part 2: Add tests. r=birtles MozReview-Commit-ID: GnFktARb4Z7
dom/animation/test/mochitest.ini
dom/animation/test/mozilla/file_discrete-animations.html
dom/animation/test/mozilla/test_discrete-animations.html
testing/web-platform/meta/web-animations/animation-model/animation-types/type-per-property.html.ini
testing/web-platform/tests/web-animations/animation-model/animation-types/type-per-property.html
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -36,16 +36,17 @@ support-files =
   css-transitions/file_keyframeeffect-getkeyframes.html
   css-transitions/file_pseudoElement-get-animations.html
   css-transitions/file_setting-effect.html
   document-timeline/file_document-timeline.html
   mozilla/file_cubic_bezier_limits.html
   mozilla/file_deferred_start.html
   mozilla/file_disabled_properties.html
   mozilla/file_disable_animations_api_core.html
+  mozilla/file_discrete-animations.html
   mozilla/file_document-timeline-origin-time-range.html
   mozilla/file_hide_and_show.html
   mozilla/file_partial_keyframes.html
   mozilla/file_spacing_property_order.html
   mozilla/file_spacing_transform.html
   mozilla/file_transform_limits.html
   mozilla/file_transition_finish_on_compositor.html
   mozilla/file_underlying-discrete-value.html
@@ -90,16 +91,17 @@ support-files =
 [css-transitions/test_pseudoElement-get-animations.html]
 [css-transitions/test_setting-effect.html]
 [document-timeline/test_document-timeline.html]
 [document-timeline/test_request_animation_frame.html]
 [mozilla/test_cubic_bezier_limits.html]
 [mozilla/test_deferred_start.html]
 [mozilla/test_disable_animations_api_core.html]
 [mozilla/test_disabled_properties.html]
+[mozilla/test_discrete-animations.html]
 [mozilla/test_document-timeline-origin-time-range.html]
 [mozilla/test_hide_and_show.html]
 [mozilla/test_partial_keyframes.html]
 [mozilla/test_set-easing.html]
 [mozilla/test_spacing_property_order.html]
 [mozilla/test_spacing_transform.html]
 [mozilla/test_transform_limits.html]
 [mozilla/test_transition_finish_on_compositor.html]
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/mozilla/file_discrete-animations.html
@@ -0,0 +1,170 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Test Mozilla-specific discrete animatable properties</title>
+<script type="application/javascript" src="../testcommon.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+const gMozillaSpecificProperties = {
+  "-moz-appearance": {
+    // https://drafts.csswg.org/css-align/#propdef-align-content
+    from: "button",
+    to: "none"
+  },
+  "-moz-border-bottom-colors": {
+    from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
+    to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
+  },
+  "-moz-border-left-colors": {
+    from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
+    to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
+  },
+  "-moz-border-right-colors": {
+    from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
+    to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
+  },
+  "-moz-border-top-colors": {
+    from: "rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0) rgb(255, 0, 0)",
+    to: "rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0) rgb(0, 255, 0)"
+  },
+  "-moz-box-align": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/box-align
+    from: "center",
+    to: "stretch"
+  },
+  "-moz-box-direction": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/box-direction
+    from: "reverse",
+    to: "normal"
+  },
+  "-moz-box-ordinal-group": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/box-ordinal-group
+    from: "1",
+    to: "5"
+  },
+  "-moz-box-orient": {
+    // https://www.w3.org/TR/css-flexbox-1/
+    from: "horizontal",
+    to: "vertical"
+  },
+  "-moz-box-pack": {
+    // https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#propdef-box-pack
+    from: "center",
+    to: "end"
+  },
+  "-moz-float-edge": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/-moz-float-edge
+    from: "margin-box",
+    to: "content-box"
+  },
+  "-moz-force-broken-image-icon": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/-moz-force-broken-image-icon
+    from: "1",
+    to: "5"
+  },
+  "image-rendering": {
+    // https://drafts.csswg.org/css-images-3/#propdef-image-rendering
+    from: "-moz-crisp-edges",
+    to: "auto"
+  },
+  "-moz-stack-sizing": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/-moz-stack-sizing
+    from: "ignore",
+    to: "stretch-to-fit"
+  },
+  "-moz-tab-size": {
+    // https://drafts.csswg.org/css-text-3/#propdef-tab-size
+    from: "1",
+    to: "5"
+  },
+  "-moz-text-size-adjust": {
+    // https://drafts.csswg.org/css-size-adjust/#propdef-text-size-adjust
+    from: "none",
+    to: "auto"
+  },
+  "-webkit-text-stroke-width": {
+    // https://compat.spec.whatwg.org/#propdef--webkit-text-stroke-width
+    from: "10px",
+    to: "50px"
+  }
+}
+
+for (let property in gMozillaSpecificProperties) {
+  const testData = gMozillaSpecificProperties[property];
+  const from = testData.from;
+  const to = testData.to;
+  const idlName = propertyToIDL(property);
+  const keyframes = {};
+  keyframes[idlName] = [from, to];
+
+  test(t => {
+    const div = addDiv(t);
+    const animation = div.animate(keyframes,
+                                  { duration: 1000, fill: "both" });
+    testAnimationSamples(animation, idlName,
+                         [{ time: 0,    expected: from.toLowerCase() },
+                          { time: 499,  expected: from.toLowerCase() },
+                          { time: 500,  expected: to.toLowerCase() },
+                          { time: 1000, expected: to.toLowerCase() }]);
+  }, property + " should animate between '"
+   + from + "' and '" + to + "' with linear easing");
+
+  test(function(t) {
+    // Easing: http://cubic-bezier.com/#.68,0,1,.01
+    // With this curve, we don't reach the 50% point until about 95% of
+    // the time has expired.
+    const div = addDiv(t);
+    const animation = div.animate(keyframes,
+                                  { duration: 1000, fill: "both",
+                                    easing: "cubic-bezier(0.68,0,1,0.01)" });
+    testAnimationSamples(animation, idlName,
+                         [{ time: 0,    expected: from.toLowerCase() },
+                          { time: 940,  expected: from.toLowerCase() },
+                          { time: 960,  expected: to.toLowerCase() }]);
+  }, property + " should animate between '"
+   + from + "' and '" + to + "' with effect easing");
+
+  test(function(t) {
+    // Easing: http://cubic-bezier.com/#.68,0,1,.01
+    // With this curve, we don't reach the 50% point until about 95% of
+    // the time has expired.
+    keyframes.easing = "cubic-bezier(0.68,0,1,0.01)";
+    const div = addDiv(t);
+    const animation = div.animate(keyframes,
+                                  { duration: 1000, fill: "both" });
+    testAnimationSamples(animation, idlName,
+                         [{ time: 0,    expected: from.toLowerCase() },
+                          { time: 940,  expected: from.toLowerCase() },
+                          { time: 960,  expected: to.toLowerCase() }]);
+  }, property + " should animate between '"
+   + from + "' and '" + to + "' with keyframe easing");
+}
+
+function propertyToIDL(property) {
+  var prefixMatch = property.match(/^-(\w+)-/);
+  if (prefixMatch) {
+    var prefix = prefixMatch[1] === "moz" ? "Moz" : prefixMatch[1];
+    property = prefix + property.substring(prefixMatch[0].length - 1);
+  }
+  // https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
+  return property.replace(/-([a-z])/gi, function(str, group) {
+    return group.toUpperCase();
+  });
+}
+
+function testAnimationSamples(animation, idlName, testSamples) {
+  const target = animation.effect.target;
+  testSamples.forEach(testSample => {
+    animation.currentTime = testSample.time;
+    assert_equals(getComputedStyle(target)[idlName], testSample.expected,
+                  "The value should be " + testSample.expected +
+                  " at " + testSample.time + "ms");
+  });
+}
+
+done();
+</script>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/mozilla/test_discrete-animations.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [
+    ["dom.animations-api.core.enabled", true],
+    ["layout.css.osx-font-smoothing.enabled", true],
+    ["layout.css.prefixes.webkit", true]
+  ] },
+  function() {
+    window.open("file_discrete-animations.html");
+  });
+</script>
--- a/testing/web-platform/meta/web-animations/animation-model/animation-types/type-per-property.html.ini
+++ b/testing/web-platform/meta/web-animations/animation-model/animation-types/type-per-property.html.ini
@@ -1,8 +1,12 @@
+prefs: [layout.css.contain.enabled:true,
+        layout.css.initial-letter.enabled:true,
+        layout.css.overflow-clip-box.enabled:true,
+        layout.css.shape-outside.enabled:true]
 [type-per-property.html]
   type: testharness
   [flex-basis supports animating as combination units 'px' and '%']
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1291187
 
   [flex-basis supports animating as combination units '%' and 'em']
     expected: FAIL
@@ -26,9 +30,8 @@
 
   [text-combine-upright uses discrete animation when animating between 'all' and 'digits' with effect easing]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1258635
 
   [text-combine-upright uses discrete animation when animating between 'all' and 'digits' with keyframe easing]
     expected: FAIL
     bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1258635
-
--- a/testing/web-platform/tests/web-animations/animation-model/animation-types/type-per-property.html
+++ b/testing/web-platform/tests/web-animations/animation-model/animation-types/type-per-property.html
@@ -29,40 +29,227 @@ var gCSSProperties = {
     ]
   },
   "align-self": {
     // https://drafts.csswg.org/css-align/#propdef-align-self
     tests: [
       discrete("flex-start", "flex-end")
     ]
   },
+  "backface-visibility": {
+    // https://drafts.csswg.org/css-transforms/#propdef-backface-visibility
+    "tests": [
+      discrete("visible", "hidden")
+    ]
+  },
+  "background-attachment": {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-attachment
+    "tests": [
+      discrete("fixed", "local")
+    ]
+  },
+  "background-blend-mode": {
+    // https://drafts.fxtf.org/compositing-1/#propdef-background-blend-mode
+    "tests": [
+      discrete("multiply", "screen")
+    ]
+  },
+  "background-clip": {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-clip
+    "tests": [
+      discrete("padding-box", "content-box")
+    ]
+  },
+  "background-image": {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-image
+    "tests": [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "background-origin": {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-origin
+    "tests": [
+      discrete("padding-box", "content-box")
+    ]
+  },
+  "background-repeat": {
+    // https://drafts.csswg.org/css-backgrounds-3/#background-repeat
+    "tests": [
+      discrete("space", "round")
+    ]
+  },
+  "border-bottom-style": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-bottom-style
+    "tests": [
+      discrete("dotted", "solid")
+    ]
+  },
+  "border-collapse": {
+    // https://drafts.csswg.org/css-tables/#propdef-border-collapse
+    "tests": [
+      discrete("collapse", "separate")
+    ]
+  },
+  "border-image-outset": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-outset
+    "tests": [
+      discrete("1 1 1 1", "5 5 5 5")
+    ]
+  },
+  "border-image-repeat": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-repeat
+    "tests": [
+      discrete("stretch stretch", "repeat repeat")
+    ]
+  },
+  "border-image-slice": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-slice
+    "tests": [
+      discrete("1 1 1 1", "5 5 5 5")
+    ]
+  },
+  "border-image-source": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-source
+    "tests": [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "border-image-width": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-image-width
+    "tests": [
+      discrete("1 1 1 1", "5 5 5 5")
+    ]
+  },
+  "border-left-style": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-left-style
+    "tests": [
+      discrete("dotted", "solid")
+    ]
+  },
+  "border-right-style": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-right-style
+    "tests": [
+      discrete("dotted", "solid")
+    ]
+  },
+  "border-top-style": {
+    // https://drafts.csswg.org/css-backgrounds-3/#border-top-style
+    "tests": [
+      discrete("dotted", "solid")
+    ]
+  },
+  "box-decoration-break": {
+    // https://drafts.csswg.org/css-break/#propdef-box-decoration-break
+    "tests": [
+      discrete("slice", "clone")
+    ]
+  },
+  "box-sizing": {
+    // https://drafts.csswg.org/css-ui-4/#box-sizing
+    "tests": [
+      discrete("content-box", "border-box")
+    ]
+  },
+  "caption-side": {
+    // https://drafts.csswg.org/css-tables/#propdef-caption-side
+    "tests": [
+      discrete("top", "bottom")
+    ]
+  },
+  "clear": {
+    // https://drafts.csswg.org/css-page-floats/#propdef-clear
+    "tests": [
+      discrete("inline-start", "inline-end")
+    ]
+  },
   "clip-rule": {
     // https://drafts.fxtf.org/css-masking-1/#propdef-clip-rule
     tests: [
       discrete("evenodd", "nonzero")
     ]
   },
+  "color-adjust": {
+    // https://drafts.csswg.org/css-color-4/#color-adjust
+    tests: [
+      discrete("economy", "exact")
+    ]
+  },
   "color-interpolation": {
     // https://svgwg.org/svg2-draft/painting.html#ColorInterpolationProperty
     tests: [
       discrete("linearRGB", "auto")
     ]
   },
   "color-interpolation-filters": {
     // https://drafts.fxtf.org/filters-1/#propdef-color-interpolation-filters
     tests: [
       discrete("sRGB", "linearRGB")
     ]
   },
+  "column-fill": {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-fill
+    tests: [
+      discrete("auto", "balance")
+    ]
+  },
+  "column-rule-style": {
+    // https://drafts.csswg.org/css-multicol/#propdef-column-rule-style
+    tests: [
+      discrete("none", "dotted")
+    ]
+  },
+  "contain": {
+    // https://drafts.csswg.org/css-containment/#propdef-contain
+    tests: [
+      discrete("strict", "none")
+    ]
+  },
+  "content": {
+    // https://drafts.csswg.org/css-content-3/#propdef-content
+    tests: [
+      discrete("\"a\"", "\"b\"")
+    ],
+    tagName: "::before"
+  },
+  "counter-increment": {
+    // https://drafts.csswg.org/css-lists-3/#propdef-counter-increment
+    tests: [
+      discrete("ident-1 1", "ident-2 2")
+    ]
+  },
+  "counter-reset": {
+    // https://drafts.csswg.org/css-lists-3/#propdef-counter-reset
+    tests: [
+      discrete("ident-1 1", "ident-2 2")
+    ]
+  },
+  "cursor": {
+    // https://drafts.csswg.org/css2/ui.html#propdef-cursor
+    tests: [
+      discrete("pointer", "wait")
+    ]
+  },
+  "direction": {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-direction
+    tests: [
+      discrete("ltr", "rtl")
+    ]
+  },
   "dominant-baseline": {
     // https://drafts.csswg.org/css-inline/#propdef-dominant-baseline
     tests: [
       discrete("ideographic", "alphabetic")
     ]
   },
+  "empty-cells": {
+    // https://drafts.csswg.org/css-tables/#propdef-empty-cells
+    tests: [
+      discrete("show", "hide")
+    ]
+  },
   "fill-rule": {
     // https://svgwg.org/svg2-draft/painting.html#FillRuleProperty
     tests: [
       discrete("evenodd", "nonzero")
     ]
   },
   "flex-basis": {
     // https://drafts.csswg.org/css-flexbox/#propdef-flex-basis
@@ -96,20 +283,182 @@ var gCSSProperties = {
     ]
   },
   "font-style": {
     // https://drafts.csswg.org/css-fonts/#propdef-font-style
     tests: [
       discrete("italic", "oblique")
     ]
   },
-  "image-rendering": {
-    // https://drafts.csswg.org/css-images/#propdef-image-rendering
+  "float": {
+    // https://drafts.csswg.org/css-page-floats/#propdef-float
+    tests: [
+      discrete("left", "right")
+    ]
+  },
+  "font-family": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-family
+    tests: [
+      discrete("helvetica", "verdana")
+    ]
+  },
+  "font-feature-settings": {
+    // https://drafts.csswg.org/css-fonts/#descdef-font-feature-settings
+    tests: [
+      discrete("\"liga\" 5", "normal")
+    ]
+  },
+  "font-kerning": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-kerning
+    tests: [
+      discrete("auto", "normal")
+    ]
+  },
+  "font-language-override": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override
+    tests: [
+      discrete("\"eng\"", "normal")
+    ]
+  },
+  "font-style": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-style
+    tests: [
+      discrete("italic", "oblique")
+    ]
+  },
+  "font-synthesis": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-synthesis
+    tests: [
+      discrete("none", "weight style")
+    ]
+  },
+  "font-variant-alternates": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-alternates
+    tests: [
+      discrete("swash(unknown)", "stylistic(unknown)")
+    ]
+  },
+  "font-variant-caps": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-caps
+    tests: [
+      discrete("small-caps", "unicase")
+    ]
+  },
+  "font-variant-east-asian": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-east-asian
+    tests: [
+      discrete("full-width", "proportional-width")
+    ]
+  },
+  "font-variant-ligatures": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-ligatures
+    tests: [
+      discrete("common-ligatures", "no-common-ligatures")
+    ]
+  },
+  "font-variant-numeric": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-numeric
+    tests: [
+      discrete("lining-nums", "oldstyle-nums")
+    ]
+  },
+  "font-variant-position": {
+    // https://drafts.csswg.org/css-fonts-3/#propdef-font-variant-position
+    tests: [
+      discrete("sub", "super")
+    ]
+  },
+  "grid-auto-columns": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-auto-columns
     tests: [
-      discrete("optimizeQuality", "pixelated")
+      discrete("1px", "5px")
+    ]
+  },
+  "grid-auto-flow": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow
+    tests: [
+      discrete("row", "column")
+    ]
+  },
+  "grid-auto-rows": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-auto-rows
+    tests: [
+      discrete("1px", "5px")
+    ]
+  },
+  "grid-column-end": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-column-end
+    tests: [
+      discrete("1", "5")
+    ]
+  },
+  "grid-column-start": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-column-start
+    tests: [
+      discrete("1", "5")
+    ]
+  },
+  "grid-row-end": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-row-end
+    tests: [
+      discrete("1", "5")
+    ]
+  },
+  "grid-row-start": {
+    // https://drafts.csswg.org/css-grid/#propdef-grid-row-start
+    tests: [
+      discrete("1", "5")
+    ]
+  },
+  "grid-template-areas": {
+    // https://drafts.csswg.org/css-template/#grid-template-areas
+    tests: [
+      discrete("\". . a b\" \". .a b\"", "none")
+    ]
+  },
+  "grid-template-columns": {
+    // https://drafts.csswg.org/css-template/#grid-template-columns
+    tests: [
+      discrete("1px", "5px")
+    ]
+  },
+  "grid-template-rows": {
+    // https://drafts.csswg.org/css-template/#grid-template-rows
+    tests: [
+      discrete("1px", "5px")
+    ]
+  },
+  "hyphens": {
+    // https://drafts.csswg.org/css-text-3/#propdef-hyphens
+    tests: [
+      discrete("manual", "auto")
+    ]
+  },
+  "image-orientation": {
+    // https://drafts.csswg.org/css-images-3/#propdef-image-orientation
+    tests: [
+      discrete("0deg", "90deg")
+    ]
+  },
+  "ime-mode": {
+    // https://drafts.csswg.org/css-ui/#input-method-editor
+    tests: [
+      discrete("disabled", "auto")
+    ]
+  },
+  "initial-letter": {
+    // https://drafts.csswg.org/css-inline/#propdef-initial-letter
+    tests: [
+      discrete("1 2", "3 4")
+    ]
+  },
+  "isolation": {
+    // https://drafts.fxtf.org/compositing-1/#propdef-isolation
+    tests: [
+      discrete("auto", "isolate")
     ]
   },
   "justify-content": {
     // https://drafts.csswg.org/css-align/#propdef-justify-content
     tests: [
       discrete("baseline", "last baseline")
     ]
   },
@@ -120,47 +469,233 @@ var gCSSProperties = {
     ]
   },
   "justify-self": {
     // https://drafts.csswg.org/css-align/#propdef-justify-self
     tests: [
       discrete("baseline", "last baseline")
     ]
   },
+  "list-style-image": {
+    // https://drafts.csswg.org/css-lists-3/#propdef-list-style-image
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "list-style-position": {
+    // https://drafts.csswg.org/css-lists-3/#propdef-list-style-position
+    tests: [
+      discrete("inside", "outside")
+    ]
+  },
+  "list-style-type": {
+    // https://drafts.csswg.org/css-lists-3/#propdef-list-style-type
+    tests: [
+      discrete("circle", "square")
+    ]
+  },
+  "marker-end": {
+    // https://svgwg.org/specs/markers/#MarkerEndProperty
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "marker-mid": {
+    // https://svgwg.org/specs/markers/#MarkerMidProperty
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "marker-start": {
+    // https://svgwg.org/specs/markers/#MarkerStartProperty
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "mask": {
+    // https://drafts.fxtf.org/css-masking-1/#the-mask
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "mask-clip": {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip
+    tests: [
+      discrete("content-box", "border-box")
+    ]
+  },
+  "mask-composite": {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-composite
+    tests: [
+      discrete("add", "subtract")
+    ]
+  },
+  "mask-image": {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-image
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
+  "mask-mode": {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-mode
+    tests: [
+      discrete("alpha", "luminance")
+    ]
+  },
+  "mask-origin": {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-origin
+    tests: [
+      discrete("content-box", "border-box")
+    ]
+  },
+  "mask-repeat": {
+    // https://drafts.fxtf.org/css-masking-1/#propdef-mask-repeat
+    tests: [
+      discrete("space", "round")
+    ]
+  },
   "mask-type": {
     // https://drafts.fxtf.org/css-masking-1/#propdef-mask-type
     tests: [
       discrete("alpha", "luminance")
     ]
   },
+  "mix-blend-mode": {
+    // https://drafts.fxtf.org/compositing-1/#propdef-mix-blend-mode
+    tests: [
+      discrete("multiply", "screen")
+    ]
+  },
+  "object-fit": {
+    // https://drafts.csswg.org/css-images-3/#propdef-object-fit
+    tests: [
+      discrete("fill", "contain")
+    ]
+  },
   "order": {
     // https://drafts.csswg.org/css-flexbox/#propdef-order
     tests: [
       integer()
     ]
   },
+  "outline-style": {
+    // https://drafts.csswg.org/css-ui/#propdef-outline-style
+    tests: [
+      discrete("none", "dotted")
+    ]
+  },
+  "overflow-clip-box": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/overflow-clip-box
+    tests: [
+      discrete("padding-box", "content-box")
+    ]
+  },
+  "overflow-wrap": {
+    // https://drafts.csswg.org/css-text-3/#propdef-overflow-wrap
+    tests: [
+      discrete("normal", "break-word")
+    ]
+  },
+  "overflow-x": {
+    // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-x
+    tests: [
+      discrete("visible", "hidden")
+    ]
+  },
+  "overflow-y": {
+    // https://drafts.csswg.org/css-overflow-3/#propdef-overflow-y
+    tests: [
+      discrete("visible", "hidden")
+    ]
+  },
+  "page-break-after": {
+    // https://drafts.csswg.org/css-break-3/#propdef-break-after
+    tests: [
+      discrete("always", "auto")
+    ]
+  },
+  "page-break-before": {
+    // https://drafts.csswg.org/css-break-3/#propdef-break-before
+    tests: [
+      discrete("always", "auto")
+    ]
+  },
+  "page-break-inside": {
+    // https://drafts.csswg.org/css-break-3/#propdef-break-inside
+    tests: [
+      discrete("auto", "avoid")
+    ]
+  },
+  "paint-order": {
+    // https://svgwg.org/svg2-draft/painting.html#PaintOrderProperty
+    tests: [
+      discrete("fill", "stroke")
+    ]
+  },
   "pointer-events": {
     // https://svgwg.org/svg2-draft/interact.html#PointerEventsProperty
     tests: [
       discrete("fill", "none")
     ]
   },
+  "position": {
+    // https://drafts.csswg.org/css-position/#propdef-position
+    tests: [
+      discrete("absolute", "fixed")
+    ]
+  },
+  "quotes": {
+    // https://drafts.csswg.org/css-content-3/#propdef-quotes
+    tests: [
+      discrete("\"“\" \"”\" \"‘\" \"’\"", "\"‘\" \"’\" \"“\" \"”\"")
+    ]
+  },
+  "resize": {
+    // https://drafts.csswg.org/css-ui/#propdef-resize
+    tests: [
+      discrete("both", "horizontal")
+    ]
+  },
   "ruby-align": {
     // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-align
     tests: [
       discrete("start", "center")
     ]
   },
   "ruby-position": {
     // https://drafts.csswg.org/css-ruby-1/#propdef-ruby-position
     tests: [
       discrete("under", "over")
     ],
     tagName: "ruby"
   },
+  "scroll-behavior": {
+    // https://drafts.csswg.org/cssom-view/#propdef-scroll-behavior
+    tests: [
+      discrete("auto", "smooth")
+    ]
+  },
+  "scroll-snap-type-x": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-x
+    tests: [
+      discrete("mandatory", "proximity")
+    ]
+  },
+  "scroll-snap-type-y": {
+    // https://developer.mozilla.org/en/docs/Web/CSS/scroll-snap-type-y
+    tests: [
+      discrete("mandatory", "proximity")
+    ]
+  },
+  "shape-outside": {
+    // http://dev.w3.org/csswg/css-shapes/#propdef-shape-outside
+    tests: [
+      discrete("url(\"http://localhost/test-1\")", "url(\"http://localhost/test-2\")")
+    ]
+  },
   "shape-rendering": {
     // https://svgwg.org/svg2-draft/painting.html#ShapeRenderingProperty
     tests: [
       discrete("optimizeSpeed", "crispEdges")
     ]
   },
   "stroke-linecap": {
     // https://svgwg.org/svg2-draft/painting.html#StrokeLinecapProperty
@@ -170,64 +705,148 @@ var gCSSProperties = {
   },
   "stroke-linejoin": {
     // https://svgwg.org/svg2-draft/painting.html#StrokeLinejoinProperty
     tests: [
       discrete("round", "miter")
     ],
     tagName: "rect"
   },
+  "table-layout": {
+    // https://drafts.csswg.org/css-tables/#propdef-table-layout
+    tests: [
+      discrete("auto", "fixed")
+    ]
+  },
+  "text-align": {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-align
+    tests: [
+      discrete("start", "end")
+    ]
+  },
+  "text-align-last": {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-align-last
+    tests: [
+      discrete("start", "end")
+    ]
+  },
   "text-anchor": {
     // https://svgwg.org/svg2-draft/text.html#TextAnchorProperty
     tests: [
       discrete("middle", "end")
     ]
   },
   "text-combine-upright": {
     // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-combine-upright
     tests: [
-      discrete("all", "digits")
+      discrete("all", "none")
     ]
   },
   "text-decoration-line": {
     // https://drafts.csswg.org/css-text-decor-3/#propdef-text-decoration-line
     tests: [
       discrete("underline", "overline")
     ]
   },
+  "text-decoration-style": {
+    // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-decoration-style
+    tests: [
+      discrete("solid", "dotted")
+    ]
+  },
+  "text-emphasis-position": {
+    // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-position
+    tests: [
+      discrete("over right", "under left")
+    ]
+  },
+  "text-emphasis-style": {
+    // http://dev.w3.org/csswg/css-text-decor-3/#propdef-text-emphasis-style
+    tests: [
+      discrete("filled circle", "open dot")
+    ]
+  },
   "text-orientation": {
     // https://drafts.csswg.org/css-writing-modes-3/#propdef-text-orientation
     tests: [
       discrete("upright", "sideways")
     ]
   },
+  "text-overflow": {
+    // https://drafts.csswg.org/css-ui/#propdef-text-overflow
+    tests: [
+      discrete("clip", "ellipsis")
+    ]
+  },
   "text-rendering": {
     // https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty
     tests: [
       discrete("optimizeSpeed", "optimizeLegibility")
     ]
   },
+  "text-transform": {
+    // https://drafts.csswg.org/css-text-3/#propdef-text-transform
+    tests: [
+      discrete("capitalize", "uppercase")
+    ]
+  },
+  "touch-action": {
+    // https://w3c.github.io/pointerevents/#the-touch-action-css-property
+    tests: [
+      discrete("auto", "none")
+    ]
+  },
+  "transform-box": {
+    // https://drafts.csswg.org/css-transforms/#propdef-transform-box
+    tests: [
+      discrete("fill-box", "border-box")
+    ]
+  },
+  "transform-style": {
+    // https://drafts.csswg.org/css-transforms/#propdef-transform-style
+    tests: [
+      discrete("flat", "preserve-3d")
+    ]
+  },
+  "unicode-bidi": {
+    // https://drafts.csswg.org/css-writing-modes-3/#propdef-unicode-bidi
+    tests: [
+      discrete("embed", "bidi-override")
+    ]
+  },
   "vector-effect": {
     // https://svgwg.org/svg2-draft/coords.html#VectorEffectProperty
     tests: [
       discrete("none", "non-scaling-stroke")
     ]
   },
   "visibility": {
     // https://drafts.csswg.org/css2/visufx.html#propdef-visibility
     tests: [
       visibility()
     ]
   },
+  "white-space": {
+    // https://drafts.csswg.org/css-text-4/#propdef-white-space
+    tests: [
+      discrete("pre", "nowrap")
+    ]
+  },
   "word-break": {
     // https://drafts.csswg.org/css-text-3/#propdef-word-break
     tests: [
       discrete("keep-all", "break-all")
     ]
   },
+  "will-change": {
+    // http://dev.w3.org/csswg/css-will-change/#propdef-will-change
+    tests: [
+      discrete("scroll-position", "contents")
+    ]
+  },
   "writing-mode": {
     // https://drafts.csswg.org/css-writing-modes-3/#propdef-writing-mode
     tests: [
       discrete("vertical-rl", "sideways-rl")
     ]
   },
 }
 
@@ -242,17 +861,17 @@ for (var property in gCSSProperties) {
 }
 
 function discrete(from, to) {
   return function(property, options) {
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = [from, to];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: from.toLowerCase() },
                             { time: 499,  expected: from.toLowerCase() },
                             { time: 500,  expected: to.toLowerCase() },
                             { time: 1000, expected: to.toLowerCase() }]);
     }, property + " uses discrete animation when animating between '"
@@ -260,17 +879,17 @@ function discrete(from, to) {
 
     test(function(t) {
       // Easing: http://cubic-bezier.com/#.68,0,1,.01
       // With this curve, we don't reach the 50% point until about 95% of
       // the time has expired.
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = [from, to];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both",
                                        easing: "cubic-bezier(0.68,0,1,0.01)" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: from.toLowerCase() },
                             { time: 940,  expected: from.toLowerCase() },
                             { time: 960,  expected: to.toLowerCase() }]);
     }, property + " uses discrete animation when animating between '"
@@ -279,17 +898,17 @@ function discrete(from, to) {
     test(function(t) {
       // Easing: http://cubic-bezier.com/#.68,0,1,.01
       // With this curve, we don't reach the 50% point until about 95% of
       // the time has expired.
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = [from, to];
       keyframes.easing = "cubic-bezier(0.68,0,1,0.01)";
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: from.toLowerCase() },
                             { time: 940,  expected: from.toLowerCase() },
                             { time: 960,  expected: to.toLowerCase() }]);
     }, property + " uses discrete animation when animating between '"
        + from + "' and '" + to + "' with keyframe easing");
@@ -297,81 +916,81 @@ function discrete(from, to) {
 }
 
 function length() {
   return function(property, options) {
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["10px", "50px"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10px" },
                             { time: 500,  expected: "30px" },
                             { time: 1000, expected: "50px" }]);
     }, property + " supports animating as a length");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["1rem", "5rem"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10px" },
                             { time: 500,  expected: "30px" },
                             { time: 1000, expected: "50px" }]);
     }, property + " supports animating as a length of rem");
   }
 }
 
 function percentage() {
   return function(property, options) {
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["10%", "50%"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10%" },
                             { time: 500,  expected: "30%" },
                             { time: 1000, expected: "50%" }]);
     }, property + " supports animating as a percentage");
   }
 }
 
 function integer() {
   return function(property, options) {
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = [-2, 2];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "-2" },
                             { time: 500,  expected: "0" },
                             { time: 1000, expected: "2" }]);
     }, property + " supports animating as an integer");
   }
 }
 
 function positiveNumber() {
   return function(property, options) {
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = [1.1, 1.5];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "1.1" },
                             { time: 500,  expected: "1.3" },
                             { time: 1000, expected: "1.5" }]);
     }, property + " supports animating as a positive number");
   }
@@ -381,69 +1000,69 @@ function lengthPercentageOrCalc() {
   return function(property, options) {
     length()(property, options);
     percentage()(property, options);
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["10px", "20%"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10px" },
                             { time: 500,  expected: "calc(5px + 10%)" },
                             { time: 1000, expected: "20%" }]);
     }, property + " supports animating as combination units 'px' and '%'");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["10%", "2em"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10%" },
                             { time: 500,  expected: "calc(10px + 5%)" },
                             { time: 1000, expected: "20px" }]);
     }, property + " supports animating as combination units '%' and 'em'");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["1em", "2rem"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10px" },
                             { time: 500,  expected: "15px" },
                             { time: 1000, expected: "20px" }]);
     }, property + " supports animating as combination units 'em' and 'rem'");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["10px", "calc(1em + 20%)"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "10px" },
                             { time: 500,  expected: "calc(10px + 10%)" },
                             { time: 1000, expected: "calc(10px + 20%)" }]);
     }, property + " supports animating as combination units 'px' and 'calc'");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["calc(10px + 10%)", "calc(1em + 1rem + 20%)"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,
                               expected: "calc(10px + 10%)" },
                             { time: 500,
                               expected: "calc(15px + 15%)" },
                             { time: 1000,
@@ -453,45 +1072,45 @@ function lengthPercentageOrCalc() {
 }
 
 function visibility() {
   return function(property, options) {
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["visible", "hidden"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "visible" },
                             { time: 999,  expected: "visible" },
                             { time: 1000, expected: "hidden" }]);
     }, property + " uses visibility animation when animating "
        + "from 'visible' to 'hidden'");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["hidden", "visible"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "hidden" },
                             { time: 1,    expected: "visible" },
                             { time: 1000, expected: "visible" }]);
     }, property + " uses visibility animation when animating "
      + "from 'hidden' to 'visible'");
 
     test(function(t) {
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["hidden", "collapse"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation = target.animate(keyframes,
                                      { duration: 1000, fill: "both" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "hidden" },
                             { time: 499,  expected: "hidden" },
                             { time: 500,  expected: "collapse" },
                             { time: 1000, expected: "collapse" }]);
     }, property + " uses visibility animation when animating "
@@ -499,17 +1118,17 @@ function visibility() {
 
     test(function(t) {
       // Easing: http://cubic-bezier.com/#.68,-.55,.26,1.55
       // With this curve, the value is less than 0 till about 34%
       // also more than 1 since about 63%
       var idlName = propertyToIDL(property);
       var keyframes = {};
       keyframes[idlName] = ["visible", "hidden"];
-      var target = createElement(t, options.tagName);
+      var target = createTestElement(t, options.tagName);
       var animation =
         target.animate(keyframes,
                        { duration: 1000, fill: "both",
                          easing: "cubic-bezier(0.68, -0.55, 0.26, 1.55)" });
       testAnimationSamples(animation, idlName,
                            [{ time: 0,    expected: "visible" },
                             { time: 1,    expected: "visible" },
                             { time: 330,  expected: "visible" },
@@ -518,25 +1137,35 @@ function visibility() {
                             { time: 630,  expected: "hidden" },
                             { time: 1000, expected: "hidden" }]);
     }, property + " uses visibility animation when animating "
      + "from 'visible' to 'hidden' with easeInOutBack easing");
   }
 }
 
 function testAnimationSamples(animation, idlName, testSamples) {
-  var target = animation.effect.target;
+  var type = animation.effect.target.type;
+  var target = type
+               ? animation.effect.target.parentElement
+               : animation.effect.target;
   testSamples.forEach(function(testSample) {
     animation.currentTime = testSample.time;
-    assert_equals(getComputedStyle(target)[idlName], testSample.expected,
+    assert_equals(getComputedStyle(target, type)[idlName],
+                  testSample.expected,
                   "The value should be " + testSample.expected +
                   " at " + testSample.time + "ms");
   });
 }
 
+function createTestElement(t, tagName) {
+  return tagName && tagName.startsWith("::")
+         ? createPseudo(t, tagName.substring(2))
+         : createElement(t, tagName);
+}
+
 function isSupported(property) {
   var testKeyframe = new TestKeyframe(propertyToIDL(property));
   try {
     // Since TestKeyframe returns 'undefined' for |property|,
     // the KeyframeEffect constructor will throw
     // if the string "undefined" is not a valid value for the property.
     new KeyframeEffect(null, testKeyframe);
   } catch(e) {}