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 000000000..cf5d6cb70
--- /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 "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 "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 "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();
+}