summaryrefslogtreecommitdiffstats
path: root/dom/svg/test/pointer-events.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/svg/test/pointer-events.js')
-rw-r--r--dom/svg/test/pointer-events.js328
1 files changed, 328 insertions, 0 deletions
diff --git a/dom/svg/test/pointer-events.js b/dom/svg/test/pointer-events.js
new file mode 100644
index 0000000000..e65fd3d390
--- /dev/null
+++ b/dom/svg/test/pointer-events.js
@@ -0,0 +1,328 @@
+SimpleTest.waitForExplicitFinish();
+
+var pointer_events_values = [
+ "auto",
+ "visiblePainted",
+ "visibleFill",
+ "visibleStroke",
+ "visible",
+ "painted",
+ "fill",
+ "stroke",
+ "all",
+ "none",
+];
+
+var paint_values = ["blue", "transparent", "none"];
+
+var opacity_values = ["1", "0.5", "0"];
+
+var visibility_values = ["visible", "hidden", "collapse"];
+
+/**
+ * List of attributes and various values for which we want to test permutations
+ * when hit testing a pointer event that is over an element's fill area,
+ * stroke area, or both (where they overlap).
+ *
+ * We're using an array of objects so that we have control over the order in
+ * which permutations are tested.
+ *
+ * TODO: test the effect of clipping, masking, filters, markers, etc.
+ */
+var hit_test_inputs = {
+ fill: [
+ { name: "pointer-events", values: pointer_events_values },
+ { name: "fill", values: paint_values },
+ { name: "fill-opacity", values: opacity_values },
+ { name: "opacity", values: opacity_values },
+ { name: "visibility", values: visibility_values },
+ ],
+ stroke: [
+ { name: "pointer-events", values: pointer_events_values },
+ { name: "stroke", values: paint_values },
+ { name: "stroke-opacity", values: opacity_values },
+ { name: "opacity", values: opacity_values },
+ { name: "visibility", values: visibility_values },
+ ],
+ both: [
+ { name: "pointer-events", values: pointer_events_values },
+ { name: "fill", values: paint_values },
+ { name: "fill-opacity", values: opacity_values },
+ { name: "stroke", values: paint_values },
+ { name: "stroke-opacity", values: opacity_values },
+ { name: "opacity", values: opacity_values },
+ { name: "visibility", values: visibility_values },
+ ],
+};
+
+/**
+ * The following object contains a list of 'pointer-events' property values,
+ * each with an object detailing the conditions under which the fill and stroke
+ * of a graphical object will intercept pointer events for the given value. If
+ * the object contains a 'fill-intercepts-iff' property then the fill is
+ * expected to intercept pointer events for that value of 'pointer-events' if
+ * and only if the conditions listed in the 'fill-intercepts-iff' object are
+ * met. If there are no conditions in the 'fill-intercepts-iff' object then the
+ * fill should always intercept pointer events. However, if the
+ * 'fill-intercepts-iff' property is not set at all then it indicates that the
+ * fill should never intercept pointer events. The same rules apply for
+ * 'stroke-intercepts-iff'.
+ *
+ * If an attribute name in the conditions list is followed by the "!"
+ * character then the requirement for a hit is that its value is NOT any
+ * of the values listed in the given array.
+ */
+var hit_conditions = {
+ auto: {
+ "fill-intercepts-iff": {
+ visibility: ["visible"],
+ "fill!": ["none"],
+ },
+ "stroke-intercepts-iff": {
+ visibility: ["visible"],
+ "stroke!": ["none"],
+ },
+ },
+ visiblePainted: {
+ "fill-intercepts-iff": {
+ visibility: ["visible"],
+ "fill!": ["none"],
+ },
+ "stroke-intercepts-iff": {
+ visibility: ["visible"],
+ "stroke!": ["none"],
+ },
+ },
+ visibleFill: {
+ "fill-intercepts-iff": {
+ visibility: ["visible"],
+ },
+ // stroke never intercepts pointer events
+ },
+ visibleStroke: {
+ // fill never intercepts pointer events
+ "stroke-intercepts-iff": {
+ visibility: ["visible"],
+ },
+ },
+ visible: {
+ "fill-intercepts-iff": {
+ visibility: ["visible"],
+ },
+ "stroke-intercepts-iff": {
+ visibility: ["visible"],
+ },
+ },
+ painted: {
+ "fill-intercepts-iff": {
+ "fill!": ["none"],
+ },
+ "stroke-intercepts-iff": {
+ "stroke!": ["none"],
+ },
+ },
+ fill: {
+ "fill-intercepts-iff": {
+ // fill always intercepts pointer events
+ },
+ // stroke never intercepts pointer events
+ },
+ stroke: {
+ // fill never intercepts pointer events
+ "stroke-intercepts-iff": {
+ // stroke always intercepts pointer events
+ },
+ },
+ all: {
+ "fill-intercepts-iff": {
+ // fill always intercepts pointer events
+ },
+ "stroke-intercepts-iff": {
+ // stroke always intercepts pointer events
+ },
+ },
+ none: {
+ // neither fill nor stroke intercept pointer events
+ },
+};
+
+// bit flags
+var POINT_OVER_FILL = 0x1;
+var POINT_OVER_STROKE = 0x2;
+
+/**
+ * Examine the element's attribute values and, based on the area(s) of the
+ * element that the pointer event is over (fill and/or stroke areas), return
+ * true if the element is expected to intercept the event, otherwise false.
+ */
+function hit_expected(
+ element,
+ over /* bit flags indicating which area(s) of the element the pointer is over */
+) {
+ function expect_hit(target) {
+ var intercepts_iff =
+ hit_conditions[element.getAttribute("pointer-events")][
+ target + "-intercepts-iff"
+ ];
+
+ if (!intercepts_iff) {
+ return false; // never intercepts events
+ }
+
+ for (var attr in intercepts_iff) {
+ var vals = intercepts_iff[attr]; // must get this before we adjust 'attr'
+ var invert = false;
+ if (attr.substr(-1) == "!") {
+ invert = true;
+ attr = attr.substr(0, attr.length - 1);
+ }
+ var match = vals.indexOf(element.getAttribute(attr)) > -1;
+ if (invert) {
+ match = !match;
+ }
+ if (!match) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return (
+ ((over & POINT_OVER_FILL) != 0 && expect_hit("fill")) ||
+ ((over & POINT_OVER_STROKE) != 0 && expect_hit("stroke"))
+ );
+}
+
+function for_all_permutations(inputs, callback) {
+ var current_permutation = arguments[2] || {};
+ var index = arguments[3] || 0;
+
+ if (index < inputs.length) {
+ var name = inputs[index].name;
+ var values = inputs[index].values;
+ for (var i = 0; i < values.length; ++i) {
+ current_permutation[name] = values[i];
+ for_all_permutations(inputs, callback, current_permutation, index + 1);
+ }
+ return;
+ }
+
+ callback(current_permutation);
+}
+
+function make_log_msg(over, tag, attributes) {
+ var target;
+ if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
+ target = "fill and stroke";
+ } else if (over == POINT_OVER_FILL) {
+ target = "fill";
+ } else if (over == POINT_OVER_STROKE) {
+ target = "stroke";
+ } else {
+ throw new Error("unexpected bit combination in 'over'");
+ }
+ var msg =
+ "Check if events are intercepted at a point over the " +
+ target +
+ " on <" +
+ tag +
+ "> for";
+ for (var attr in attributes) {
+ msg += " " + attr + "=" + attributes[attr];
+ }
+ return msg;
+}
+
+var dx, dy; // offset of <svg> element from pointer coordinates origin
+
+function test_element(
+ id,
+ x,
+ y,
+ over /* bit flags indicating which area(s) of the element the pointer is over */
+) {
+ var element = document.getElementById(id);
+ var tag = element.tagName;
+
+ function test_permutation(attributes) {
+ for (var attr in attributes) {
+ element.setAttribute(attr, attributes[attr]);
+ }
+ var hits = document.elementFromPoint(dx + x, dy + y) == element;
+ var msg = make_log_msg(over, tag, attributes);
+
+ is(hits, hit_expected(element, over), msg);
+ }
+
+ var inputs;
+ if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
+ inputs = hit_test_inputs.both;
+ } else if (over == POINT_OVER_FILL) {
+ inputs = hit_test_inputs.fill;
+ } else if (over == POINT_OVER_STROKE) {
+ inputs = hit_test_inputs.stroke;
+ } else {
+ throw new Error("unexpected bit combination in 'over'");
+ }
+
+ for_all_permutations(inputs, test_permutation);
+
+ // To reduce the chance of bogus results in subsequent tests:
+ element.setAttribute("fill", "none");
+ element.setAttribute("stroke", "none");
+}
+
+function run_tests(subtest) {
+ var div = document.getElementById("div");
+ dx = div.offsetLeft;
+ dy = div.offsetTop;
+
+ // Run the test with only a subset of pointer-events values, to avoid
+ // running over the mochitest time limit. The subtest argument indicates
+ // whether to use the first half of the pointer-events values (0)
+ // or the second half (1).
+ var partition = Math.floor(pointer_events_values.length / 2);
+ switch (subtest) {
+ case 0:
+ pointer_events_values.splice(partition);
+ break;
+ case 1:
+ pointer_events_values.splice(0, partition);
+ break;
+ case 2:
+ throw new Error("unexpected subtest number");
+ }
+
+ test_element("rect", 30, 30, POINT_OVER_FILL);
+ test_element("rect", 5, 5, POINT_OVER_STROKE);
+
+ // The SVG 1.1 spec essentially says that, for text, hit testing is done
+ // against the character cells of the text, and not the fill and stroke as
+ // you might expect for a normal graphics element like <path>. See the
+ // paragraph starting "For text elements..." in this section:
+ //
+ // http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
+ //
+ // This requirement essentially means that for the purposes of hit testing
+ // the fill and stroke areas are the same area - the character cell. (At
+ // least until we support having any fill or stroke that lies outside the
+ // character cells intercept events like Opera does - see below.) Thus, for
+ // text, when a pointer event is over a character cell it is essentially over
+ // both the fill and stroke at the same time. That's the reason we pass both
+ // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over'
+ // argument below. It's also the reason why we only test one point in the
+ // text rather than having separate tests for fill and stroke.
+ //
+ // For hit testing of text, Opera essentially treats fill and stroke like it
+ // would on any normal element, but it adds the character cells of glyhs to
+ // both the glyphs' fill AND stroke. I think this is what we should do too.
+ // It's compatible with the letter of the SVG 1.1 rules, and it allows any
+ // parts of a glyph that are outside the glyph's character cells to also
+ // intercept events in the normal way. When we make that change we'll be able
+ // to add separate fill and stroke tests for text below.
+
+ test_element("text", 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE);
+
+ SimpleTest.finish();
+}