Source code
Revision control
Copy as Markdown
Other Tools
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:
//
//
// 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();
}