Add WPTs for SVG presentation attribute support.

Differential Revision: https://phabricator.services.mozilla.com/D63036

bugzilla-url: https://bugzilla.mozilla.org/show_bug.cgi?id=1615944
gecko-commit: 0c55843ce3af5a70617499e04d7bb9cc2aadcf69
gecko-integration-branch: autoland
gecko-reviewers: longsonr
diff --git a/svg/styling/presentation-attributes-irrelevant.html b/svg/styling/presentation-attributes-irrelevant.html
new file mode 100644
index 0000000..f96d1d9
--- /dev/null
+++ b/svg/styling/presentation-attributes-irrelevant.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SVG presentation attributes - on irrelevant elements</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/styling.html#PresentationAttributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="presentation-attributes.js"></script>
+<svg id="svg"></svg>
+<script>
+// Test that all of the presentation attributes with no special rules are
+// also supported on elements that the corresponding property does not
+// apply to.
+
+for (let p in PROPERTIES) {
+  if (CSS.supports(p, "initial") && PROPERTIES[p].irrelevantElement) {
+    test(function() {
+      assertPresentationAttributeIsSupported(PROPERTIES[p].irrelevantElement, p, PROPERTIES[p].value, p);
+    }, `${p} presentation attribute supported on an irrelevant element`);
+  }
+}
+</script>
diff --git a/svg/styling/presentation-attributes-relevant.html b/svg/styling/presentation-attributes-relevant.html
new file mode 100644
index 0000000..b5f9343
--- /dev/null
+++ b/svg/styling/presentation-attributes-relevant.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SVG presentation attributes - on relevant elements</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/styling.html#PresentationAttributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="presentation-attributes.js"></script>
+<svg id="svg"></svg>
+<script>
+// Test that all of the presentation attributes with no special rules are
+// supported on elements that the corresponding properties apply to.
+
+for (let p in PROPERTIES) {
+  if (CSS.supports(p, "initial")) {
+    test(function() {
+      assertPresentationAttributeIsSupported(PROPERTIES[p].relevantElement, p, PROPERTIES[p].value, p);
+    }, `${p} presentation attribute supported on a relevant element`);
+  }
+}
+</script>
diff --git a/svg/styling/presentation-attributes-special-cases.html b/svg/styling/presentation-attributes-special-cases.html
new file mode 100644
index 0000000..c544c9c
--- /dev/null
+++ b/svg/styling/presentation-attributes-special-cases.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SVG presentation attributes - special cases</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/styling.html#PresentationAttributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="presentation-attributes.js"></script>
+<svg id="svg"></svg>
+<script>
+// 1. Test the special cases where presentation attributes are only allowed on
+// a specific set of elements.
+
+if (propertiesAreSupported(["cx", "cy"])) {
+  for (let e of ["circle", "ellipse"]) {
+    test(function() {
+      for (let p of ["cx", "cy"]) {
+        assertPresentationAttributeIsSupported(e, p, "1", p);
+      }
+    }, `cx and cy presentation attributes supported on ${e} element`);
+  }
+}
+
+if (propertiesAreSupported(["x", "y", "width", "height"])) {
+  for (let e of ["foreignObject", "image", "rect", "svg", "symbol", "use"]) {
+    test(function() {
+      for (let p of ["x", "y", "width", "height"]) {
+        assertPresentationAttributeIsSupported(e, p, "1", p);
+      }
+    }, `x, y, width, and height presentation attributes supported on ${e} element`);
+  }
+}
+
+if (CSS.supports("r", "initial")) {
+  test(function() {
+    assertPresentationAttributeIsSupported("circle", "r", "1", "r");
+  }, `r presentation attribute supported on circle element`);
+}
+
+if (propertiesAreSupported(["rx", "ry"])) {
+  for (let e of ["ellipse", "rect"]) {
+    test(function() {
+      for (let p of ["rx", "ry"]) {
+        assertPresentationAttributeIsSupported(e, p, "1", p);
+      }
+    }, `rx and ry presentation attributes supported on ${e} element`);
+  }
+}
+
+if (CSS.supports("d", "initial")) {
+  test(function() {
+    assertPresentationAttributeIsSupported("path", "d", "M0,0 L1,1", "d");
+  }, `d presentation attribute supported on path element`);
+}
+
+
+// 2. Test that for those presentation attributes only allowed on a specific
+// set of elements, that they are not supported on some other SVG element.
+// (We use 'g' as that element for testing.)
+
+if (propertiesAreSupported(["cx", "cy"])) {
+  test(function() {
+    for (let p of ["cx", "cy"]) {
+      assertPresentationAttributeIsNotSupported("g", p, "1", p);
+    }
+  }, `cx and cy presentation attributes not supported on other elements`);
+}
+
+if (propertiesAreSupported(["x", "y", "width", "height"])) {
+  test(function() {
+    for (let p of ["x", "y", "width", "height"]) {
+      assertPresentationAttributeIsNotSupported("g", p, "1", p);
+    }
+  }, `x, y, width, and height presentation attributes not supported on other elements`);
+}
+
+if (CSS.supports("r", "initial")) {
+  test(function() {
+    assertPresentationAttributeIsNotSupported("g", "r", "1", "r");
+  }, `r presentation attribute not supported on other elements`);
+}
+
+if (propertiesAreSupported(["rx", "ry"])) {
+  test(function() {
+    for (let p of ["rx", "ry"]) {
+      assertPresentationAttributeIsNotSupported("g", p, "1", p);
+    }
+  }, `rx and ry presentation attributes not supported on other elements`);
+}
+
+if (CSS.supports("d", "initial")) {
+  test(function() {
+    assertPresentationAttributeIsSupported("g", "d", "M0,0 L1,1", "d");
+  }, `d presentation attribute not supported on other elements`);
+}
+
+
+// 3. Test that the fill presentation attribute is not supported on any
+// animation elements.
+
+if (CSS.supports("fill", "initial")) {
+  test(function() {
+    for (let e of ["animate", "animateMotion", "animateTransform", "discard", "set"]) {
+      assertPresentationAttributeIsNotSupported(e, "fill", "blue", "fill");
+    }
+  }, `fill presentation attribute not supported on animation elements`);
+}
+
+
+if (CSS.supports("transform", "initial")) {
+  // 4. Test support for the presentation attributes of the transform property,
+  // which have a different spelling depending on which element they're on.
+
+  test(function() {
+    assertPresentationAttributeIsSupported("g", "transform", "scale(2)", "transform");
+  }, `transform presentation attribute supported on g`);
+
+  test(function() {
+    assertPresentationAttributeIsSupported("pattern", "patternTransform", "scale(2)", "transform");
+  }, `patternTransform presentation attribute supported on pattern`);
+
+  for (let e of ["linearGradient", "radialGradient"]) {
+    test(function() {
+      assertPresentationAttributeIsSupported(e, "gradientTransform", "scale(2)", "transform");
+    }, `patternTransform presentation attribute supported on ${e}`);
+  }
+
+
+  // 5. Test that the wrong spellings of the presentation attributes of the
+  // transform property are not supported.
+
+  test(function() {
+    for (let e of ["pattern", "linearGradient", "radialGradient"]) {
+      assertPresentationAttributeIsNotSupported(e, "transform", "scale(2)", "transform");
+    }
+  }, `transform presentation attribute not supported on pattern or gradient elements`);
+
+  test(function() {
+    for (let e of ["g", "linearGradient", "radialGradient"]) {
+      assertPresentationAttributeIsNotSupported(e, "patternTransform", "scale(2)", "transform");
+    }
+  }, `patternTransform presentation attribute not supported on g or gradient elements`);
+
+  test(function() {
+    for (let e of ["g", "pattern"]) {
+      assertPresentationAttributeIsNotSupported(e, "gradientTransform", "scale(2)", "transform");
+    }
+  }, `gradientTransform presentation attribute not supported on g or pattern elements`);
+}
+</script>
diff --git a/svg/styling/presentation-attributes-unknown.html b/svg/styling/presentation-attributes-unknown.html
new file mode 100644
index 0000000..487175b
--- /dev/null
+++ b/svg/styling/presentation-attributes-unknown.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SVG presentation attributes - on unknown SVG elements</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/styling.html#PresentationAttributes">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="presentation-attributes.js"></script>
+<svg id="svg"></svg>
+<script>
+// Test that all of the presentation attributes with no special
+// rules are supported on unknown SVG elements.
+
+for (let p in PROPERTIES) {
+  if (CSS.supports(p, "initial") && PROPERTIES[p].irrelevantElement) {
+    test(function() {
+      assertPresentationAttributeIsSupported("unknown", p, PROPERTIES[p].value, p);
+    }, `${p} presentation attribute supported on an unknown SVG element`);
+  }
+}
+</script>
diff --git a/svg/styling/presentation-attributes.js b/svg/styling/presentation-attributes.js
new file mode 100644
index 0000000..c9dc14a
--- /dev/null
+++ b/svg/styling/presentation-attributes.js
@@ -0,0 +1,385 @@
+const PROPERTIES = {
+  "alignment-baseline": {
+    value: "middle",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "baseline-shift": {
+    value: "1",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "clip-path": {
+    value: "url(#e)",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "clip-rule": {
+    value: "evenodd",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "color": {
+    value: "blue",
+    relevantElement: "g",
+    irrelevantElement: "image",
+  },
+  "color-interpolation-filters": {
+    value: "sRGB",
+    relevantElement: "filter",
+    irrelevantElement: "linearGradient",
+  },
+  "color-interpolation": {
+    value: "linearRGB",
+    relevantElement: "linearGradient",
+    irrelevantElement: "image",
+  },
+  "cursor": {
+    value: "pointer",
+    relevantElement: "g",
+    irrelevantElement: "defs",
+  },
+  "cx": {
+    value: "1",
+    relevantElement: "circle",
+    irrelevantElement: null,
+  },
+  "cy": {
+    value: "1",
+    relevantElement: "circle",
+    irrelevantElement: null,
+  },
+  "direction": {
+    value: "rtl",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "display": {
+    value: "block",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "d": {
+    value: "M0,0 L1,1",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "dominant-baseline": {
+    value: "middle",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "fill": {
+    value: "blue",
+    relevantElement: "g",
+    irrelevantElement: "image",
+  },
+  "fill-opacity": {
+    value: "0.5",
+    relevantElement: "g",
+    irrelevantElement: "image",
+  },
+  "fill-rule": {
+    value: "evenodd",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "filter": {
+    value: "url(#e)",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "flood-color": {
+    value: "blue",
+    relevantElement: "feFlood",
+    irrelevantElement: "rect",
+  },
+  "flood-opacity": {
+    value: "0.5",
+    relevantElement: "feFlood",
+    irrelevantElement: "rect",
+  },
+  "font-family": {
+    value: "Test Family",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "font-size": {
+    value: "50",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "font-size-adjust": {
+    value: "0.5",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "font-stretch": {
+    value: "expanded",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "font-style": {
+    value: "italic",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "font-variant": {
+    value: "small-caps",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "font-weight": {
+    value: "900",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "glyph-orientation-vertical": {
+    value: "90",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "height": {
+    value: "1",
+    relevantElement: "rect",
+    irrelevantElement: "path",
+  },
+  "image-rendering": {
+    value: "optimizeSpeed",
+    relevantElement: "image",
+    irrelevantElement: "path",
+  },
+  "letter-spacing": {
+    value: "1px",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "lighting-color": {
+    value: "blue",
+    relevantElement: "feDiffuseLighting",
+    irrelevantElement: "rect",
+  },
+  "marker-end": {
+    value: "url(#e)",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "marker-mid": {
+    value: "url(#e)",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "marker-start": {
+    value: "url(#e)",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "mask-type": {
+    value: "alpha",
+    relevantElement: "mask",
+    irrelevantElement: "rect",
+  },
+  "mask": {
+    value: "url(#e)",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "opacity": {
+    value: "0.5",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "overflow": {
+    value: "scroll",
+    relevantElement: "svg",
+    irrelevantElement: "rect",
+  },
+  "paint-order": {
+    value: "fill stroke",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "pointer-events": {
+    value: "none",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "r": {
+    value: "1",
+    relevantElement: "circle",
+    irrelevantElement: "rect",
+  },
+  "rx": {
+    value: "1",
+    relevantElement: "rect",
+    irrelevantElement: "path",
+  },
+  "ry": {
+    value: "1",
+    relevantElement: "rect",
+    irrelevantElement: "path",
+  },
+  "shape-rendering": {
+    value: "geometricPrecision",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stop-color": {
+    value: "blue",
+    relevantElement: "stop",
+    irrelevantElement: "rect",
+  },
+  "stop-opacity": {
+    value: "0.5",
+    relevantElement: "stop",
+    irrelevantElement: "rect",
+  },
+  "stroke": {
+    value: "blue",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-dasharray": {
+    value: "1 1",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-dashoffset": {
+    value: "1",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-linecap": {
+    value: "round",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-linejoin": {
+    value: "round",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-miterlimit": {
+    value: "1",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-opacity": {
+    value: "0.5",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "stroke-width": {
+    value: "2",
+    relevantElement: "path",
+    irrelevantElement: "image",
+  },
+  "text-anchor": {
+    value: "middle",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "text-decoration": {
+    value: "underline",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "text-overflow": {
+    value: "ellipsis",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "text-rendering": {
+    value: "geometricPrecision",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "transform-origin": {
+    value: "1px 1px",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "transform": {
+    value: "scale(2)",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "unicode-bidi": {
+    value: "embed",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "vector-effect": {
+    value: "non-scaling-stroke",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "visibility": {
+    value: "hidden",
+    relevantElement: "g",
+    irrelevantElement: "linearGradient",
+  },
+  "white-space": {
+    value: "pre",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "width": {
+    value: "1",
+    relevantElement: "rect",
+    irrelevantElement: "path",
+  },
+  "word-spacing": {
+    value: "1",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "writing-mode": {
+    value: "vertical-rl",
+    relevantElement: "text",
+    irrelevantElement: "rect",
+  },
+  "x": {
+    value: "1",
+    relevantElement: "rect",
+    irrelevantElement: "path",
+  },
+  "y": {
+    value: "1",
+    relevantElement: "rect",
+    irrelevantElement: "path",
+  },
+};
+
+function presentationAttributeIsSupported(element, attribute, value, property) {
+  let e = document.createElementNS("http://www.w3.org/2000/svg", element);
+  svg.append(e);
+  let propertyValueBefore = getComputedStyle(e).getPropertyValue(property);
+  e.setAttribute(attribute, value);
+  let propertyValueAfter = getComputedStyle(e).getPropertyValue(property);
+  e.remove();
+  return propertyValueBefore != propertyValueAfter;
+}
+
+function assertPresentationAttributeIsSupported(element, attribute, value, property) {
+  assert_true(
+    presentationAttributeIsSupported(element, attribute, value, property),
+    `Presentation attribute ${attribute}="${value}" should be supported on ${element} element`
+  );
+}
+
+function assertPresentationAttributeIsNotSupported(element, attribute, value, property) {
+  assert_false(
+    presentationAttributeIsSupported(element, attribute, value, property),
+    `Presentation attribute ${attribute}="${value}" should be supported on ${element} element`
+  );
+}
+
+function propertiesAreSupported(properties) {
+  for (let p of properties) {
+    if (!CSS.supports(p, "initial")) {
+      return false;
+    }
+  }
+  return true;
+}