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 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 . 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(); }