Bug 1941128 - Turn off network.dns.native_https_query on Mac again
[gecko.git] / dom / svg / test / pointer-events.js
blobe65fd3d390e916c6ed439908f828a7eaf992e997
1 SimpleTest.waitForExplicitFinish();
3 var pointer_events_values = [
4   "auto",
5   "visiblePainted",
6   "visibleFill",
7   "visibleStroke",
8   "visible",
9   "painted",
10   "fill",
11   "stroke",
12   "all",
13   "none",
16 var paint_values = ["blue", "transparent", "none"];
18 var opacity_values = ["1", "0.5", "0"];
20 var visibility_values = ["visible", "hidden", "collapse"];
22 /**
23  * List of attributes and various values for which we want to test permutations
24  * when hit testing a pointer event that is over an element's fill area,
25  * stroke area, or both (where they overlap).
26  *
27  * We're using an array of objects so that we have control over the order in
28  * which permutations are tested.
29  *
30  * TODO: test the effect of clipping, masking, filters, markers, etc.
31  */
32 var hit_test_inputs = {
33   fill: [
34     { name: "pointer-events", values: pointer_events_values },
35     { name: "fill", values: paint_values },
36     { name: "fill-opacity", values: opacity_values },
37     { name: "opacity", values: opacity_values },
38     { name: "visibility", values: visibility_values },
39   ],
40   stroke: [
41     { name: "pointer-events", values: pointer_events_values },
42     { name: "stroke", values: paint_values },
43     { name: "stroke-opacity", values: opacity_values },
44     { name: "opacity", values: opacity_values },
45     { name: "visibility", values: visibility_values },
46   ],
47   both: [
48     { name: "pointer-events", values: pointer_events_values },
49     { name: "fill", values: paint_values },
50     { name: "fill-opacity", values: opacity_values },
51     { name: "stroke", values: paint_values },
52     { name: "stroke-opacity", values: opacity_values },
53     { name: "opacity", values: opacity_values },
54     { name: "visibility", values: visibility_values },
55   ],
58 /**
59  * The following object contains a list of 'pointer-events' property values,
60  * each with an object detailing the conditions under which the fill and stroke
61  * of a graphical object will intercept pointer events for the given value. If
62  * the object contains a 'fill-intercepts-iff' property then the fill is
63  * expected to intercept pointer events for that value of 'pointer-events' if
64  * and only if the conditions listed in the 'fill-intercepts-iff' object are
65  * met. If there are no conditions in the 'fill-intercepts-iff' object then the
66  * fill should always intercept pointer events. However, if the
67  * 'fill-intercepts-iff' property is not set at all then it indicates that the
68  * fill should never intercept pointer events. The same rules apply for
69  * 'stroke-intercepts-iff'.
70  *
71  * If an attribute name in the conditions list is followed by the "!"
72  * character then the requirement for a hit is that its value is NOT any
73  * of the values listed in the given array.
74  */
75 var hit_conditions = {
76   auto: {
77     "fill-intercepts-iff": {
78       visibility: ["visible"],
79       "fill!": ["none"],
80     },
81     "stroke-intercepts-iff": {
82       visibility: ["visible"],
83       "stroke!": ["none"],
84     },
85   },
86   visiblePainted: {
87     "fill-intercepts-iff": {
88       visibility: ["visible"],
89       "fill!": ["none"],
90     },
91     "stroke-intercepts-iff": {
92       visibility: ["visible"],
93       "stroke!": ["none"],
94     },
95   },
96   visibleFill: {
97     "fill-intercepts-iff": {
98       visibility: ["visible"],
99     },
100     // stroke never intercepts pointer events
101   },
102   visibleStroke: {
103     // fill never intercepts pointer events
104     "stroke-intercepts-iff": {
105       visibility: ["visible"],
106     },
107   },
108   visible: {
109     "fill-intercepts-iff": {
110       visibility: ["visible"],
111     },
112     "stroke-intercepts-iff": {
113       visibility: ["visible"],
114     },
115   },
116   painted: {
117     "fill-intercepts-iff": {
118       "fill!": ["none"],
119     },
120     "stroke-intercepts-iff": {
121       "stroke!": ["none"],
122     },
123   },
124   fill: {
125     "fill-intercepts-iff": {
126       // fill always intercepts pointer events
127     },
128     // stroke never intercepts pointer events
129   },
130   stroke: {
131     // fill never intercepts pointer events
132     "stroke-intercepts-iff": {
133       // stroke always intercepts pointer events
134     },
135   },
136   all: {
137     "fill-intercepts-iff": {
138       // fill always intercepts pointer events
139     },
140     "stroke-intercepts-iff": {
141       // stroke always intercepts pointer events
142     },
143   },
144   none: {
145     // neither fill nor stroke intercept pointer events
146   },
149 // bit flags
150 var POINT_OVER_FILL = 0x1;
151 var POINT_OVER_STROKE = 0x2;
154  * Examine the element's attribute values and, based on the area(s) of the
155  * element that the pointer event is over (fill and/or stroke areas), return
156  * true if the element is expected to intercept the event, otherwise false.
157  */
158 function hit_expected(
159   element,
160   over /* bit flags indicating which area(s) of the element the pointer is over */
161 ) {
162   function expect_hit(target) {
163     var intercepts_iff =
164       hit_conditions[element.getAttribute("pointer-events")][
165         target + "-intercepts-iff"
166       ];
168     if (!intercepts_iff) {
169       return false; // never intercepts events
170     }
172     for (var attr in intercepts_iff) {
173       var vals = intercepts_iff[attr]; // must get this before we adjust 'attr'
174       var invert = false;
175       if (attr.substr(-1) == "!") {
176         invert = true;
177         attr = attr.substr(0, attr.length - 1);
178       }
179       var match = vals.indexOf(element.getAttribute(attr)) > -1;
180       if (invert) {
181         match = !match;
182       }
183       if (!match) {
184         return false;
185       }
186     }
188     return true;
189   }
191   return (
192     ((over & POINT_OVER_FILL) != 0 && expect_hit("fill")) ||
193     ((over & POINT_OVER_STROKE) != 0 && expect_hit("stroke"))
194   );
197 function for_all_permutations(inputs, callback) {
198   var current_permutation = arguments[2] || {};
199   var index = arguments[3] || 0;
201   if (index < inputs.length) {
202     var name = inputs[index].name;
203     var values = inputs[index].values;
204     for (var i = 0; i < values.length; ++i) {
205       current_permutation[name] = values[i];
206       for_all_permutations(inputs, callback, current_permutation, index + 1);
207     }
208     return;
209   }
211   callback(current_permutation);
214 function make_log_msg(over, tag, attributes) {
215   var target;
216   if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
217     target = "fill and stroke";
218   } else if (over == POINT_OVER_FILL) {
219     target = "fill";
220   } else if (over == POINT_OVER_STROKE) {
221     target = "stroke";
222   } else {
223     throw new Error("unexpected bit combination in 'over'");
224   }
225   var msg =
226     "Check if events are intercepted at a point over the " +
227     target +
228     " on <" +
229     tag +
230     "> for";
231   for (var attr in attributes) {
232     msg += " " + attr + "=" + attributes[attr];
233   }
234   return msg;
237 var dx, dy; // offset of <svg> element from pointer coordinates origin
239 function test_element(
240   id,
241   x,
242   y,
243   over /* bit flags indicating which area(s) of the element the pointer is over */
244 ) {
245   var element = document.getElementById(id);
246   var tag = element.tagName;
248   function test_permutation(attributes) {
249     for (var attr in attributes) {
250       element.setAttribute(attr, attributes[attr]);
251     }
252     var hits = document.elementFromPoint(dx + x, dy + y) == element;
253     var msg = make_log_msg(over, tag, attributes);
255     is(hits, hit_expected(element, over), msg);
256   }
258   var inputs;
259   if (over == (POINT_OVER_FILL | POINT_OVER_STROKE)) {
260     inputs = hit_test_inputs.both;
261   } else if (over == POINT_OVER_FILL) {
262     inputs = hit_test_inputs.fill;
263   } else if (over == POINT_OVER_STROKE) {
264     inputs = hit_test_inputs.stroke;
265   } else {
266     throw new Error("unexpected bit combination in 'over'");
267   }
269   for_all_permutations(inputs, test_permutation);
271   // To reduce the chance of bogus results in subsequent tests:
272   element.setAttribute("fill", "none");
273   element.setAttribute("stroke", "none");
276 function run_tests(subtest) {
277   var div = document.getElementById("div");
278   dx = div.offsetLeft;
279   dy = div.offsetTop;
281   // Run the test with only a subset of pointer-events values, to avoid
282   // running over the mochitest time limit.  The subtest argument indicates
283   // whether to use the first half of the pointer-events values (0)
284   // or the second half (1).
285   var partition = Math.floor(pointer_events_values.length / 2);
286   switch (subtest) {
287     case 0:
288       pointer_events_values.splice(partition);
289       break;
290     case 1:
291       pointer_events_values.splice(0, partition);
292       break;
293     case 2:
294       throw new Error("unexpected subtest number");
295   }
297   test_element("rect", 30, 30, POINT_OVER_FILL);
298   test_element("rect", 5, 5, POINT_OVER_STROKE);
300   // The SVG 1.1 spec essentially says that, for text, hit testing is done
301   // against the character cells of the text, and not the fill and stroke as
302   // you might expect for a normal graphics element like <path>. See the
303   // paragraph starting "For text elements..." in this section:
304   //
305   //   http://www.w3.org/TR/SVG11/interact.html#PointerEventsProperty
306   //
307   // This requirement essentially means that for the purposes of hit testing
308   // the fill and stroke areas are the same area - the character cell. (At
309   // least until we support having any fill or stroke that lies outside the
310   // character cells intercept events like Opera does - see below.) Thus, for
311   // text, when a pointer event is over a character cell it is essentially over
312   // both the fill and stroke at the same time. That's the reason we pass both
313   // the POINT_OVER_FILL and POINT_OVER_STROKE bits in test_element's 'over'
314   // argument below. It's also the reason why we only test one point in the
315   // text rather than having separate tests for fill and stroke.
316   //
317   // For hit testing of text, Opera essentially treats fill and stroke like it
318   // would on any normal element, but it adds the character cells of glyhs to
319   // both the glyphs' fill AND stroke. I think this is what we should do too.
320   // It's compatible with the letter of the SVG 1.1 rules, and it allows any
321   // parts of a glyph that are outside the glyph's character cells to also
322   // intercept events in the normal way. When we make that change we'll be able
323   // to add separate fill and stroke tests for text below.
325   test_element("text", 210, 30, POINT_OVER_FILL | POINT_OVER_STROKE);
327   SimpleTest.finish();