Bug 619959, part 2. Fix handling of various values of pointer-events when stroke-opacity="0". r=longsonr,heycam. a=roc.
authorJonathan Watt <jwatt@jwatt.org>
Wed, 26 Jan 2011 17:50:29 +1300
changeset 61305 4d6554646378a04e911d00d53bc39f332027bb50
parent 61304 049638523ae99aac1a6171f76a0bb746e948ed99
child 61306 84431e8293867784d04eded2eb75bfa187b0b82e
push idunknown
push userunknown
push dateunknown
reviewerslongsonr, heycam, roc
bugs619959
milestone2.0b11pre
Bug 619959, part 2. Fix handling of various values of pointer-events when stroke-opacity="0". r=longsonr,heycam. a=roc.
content/svg/content/test/Makefile.in
content/svg/content/test/test_pointer-events-2.xhtml
content/svg/content/test/test_pointer-events.xhtml
layout/svg/base/src/nsSVGPathGeometryFrame.cpp
layout/svg/base/src/nsSVGPathGeometryFrame.h
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -60,16 +60,17 @@ include $(topsrcdir)/config/rules.mk
 		dataTypes-helper.svg \
 		getCTM-helper.svg \
 		test_getCTM.html \
 		test_getSubStringLength.xhtml \
 		getSubStringLength-helper.svg \
 		test_isSupported.xhtml \
 		test_nonAnimStrings.xhtml \
 		test_pathSeg.xhtml \
+		test_pointer-events.xhtml \
 		test_pointer-events-2.xhtml \
 		test_scientific.html \
 		scientific-helper.svg \
 		test_SVGAnimatedImageSMILDisabled.html \
 		animated-svg-image-helper.html \
 		animated-svg-image-helper.svg \
 		test_SVGLengthList.xhtml \
 		test_SVGPathSegList.xhtml \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_pointer-events.xhtml
@@ -0,0 +1,273 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=619959
+-->
+<head>
+  <title>Test 'pointer-events' handling</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run_tests()">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var pointer_events_values = [
+  '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 stroke and fill.
+ *
+ * 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_attr_values = {
+  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 }
+  ]
+}
+
+/**
+ * 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 = {
+  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
+  }
+}
+
+/**
+ * Examine the current attribute values and return true if the specified target
+ * (fill or stroke) is expected to intercept events, or else return false.
+ */
+function hit_expected(target /* {stroke|fill} */, attributes)
+{
+  var intercepts_iff =
+    hit_conditions[attributes['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(attributes[attr]) > -1;
+    if (invert) {
+      match = !match;
+    }
+    if (!match) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+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 log_msg(target, id, attributes)
+{
+  var msg = 'Check if events are intercepted by '+target+' on '+id+' for';
+  for (var attr in attributes) {
+    msg += ' '+attr+'='+attributes[attr];
+  }
+  return msg;
+}
+
+function run_tests()
+{
+  var div = document.getElementById("div");
+  var dx = div.offsetLeft;
+  var dy = div.offsetTop;
+
+  var id, element, target;  // target is 'fill' or 'stroke'
+  var x, y;  // coordinates to hit test
+
+  function test_permutation(attributes) {
+    for (var attr in attributes) {
+      element.setAttribute(attr, attributes[attr]);
+    }
+    var hit = document.elementFromPoint(dx + x, dy + y) == element;
+    is(hit, hit_expected(target, attributes), log_msg(target, id, attributes));
+  }
+
+  // To reduce the chance of bogus results
+  function clear_attributes_for_next_test(inputs) {
+    for (var i = 0; i < inputs.length; ++i) {
+      element.removeAttribute(inputs[i].name);
+    }
+    element.setAttribute('fill', 'none');
+    element.setAttribute('stroke', 'none');
+  }
+
+  id = 'rect';
+  element = document.getElementById(id);
+
+  var target = 'fill';
+  x = 30;
+  y = 30;
+  for_all_permutations(hit_test_attr_values[target], test_permutation);
+  clear_attributes_for_next_test(hit_test_attr_values[target]);
+
+  var target = 'stroke';
+  x = 5;
+  y = 5;
+  for_all_permutations(hit_test_attr_values[target], test_permutation);
+  clear_attributes_for_next_test(hit_test_attr_values[target]);
+
+  SimpleTest.finish();
+}
+
+]]>
+</script>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=619959">Mozilla Bug 619959</a>
+<p id="display"></p>
+<div id="content">
+
+  <div width="100%" height="1" id="div"></div>
+
+  <svg xmlns="http://www.w3.org/2000/svg" id="svg">
+    <rect id="rect" x="10" y="10" width="40" height="40" stroke-width="20"/>
+  </svg>
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.cpp
@@ -154,17 +154,17 @@ NS_IMETHODIMP_(nsIFrame*)
 nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint)
 {
   PRUint16 fillRule, mask;
   if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) {
     mask = HITTEST_MASK_FILL;
     fillRule = GetClipRule();
   } else {
     mask = GetHittestMask();
-    if (!mask || (!(mask & HITTEST_MASK_FORCE_TEST) &&
+    if (!mask || ((mask & HITTEST_MASK_CHECK_MRECT) &&
                   !mRect.Contains(aPoint)))
       return nsnull;
     fillRule = GetStyleSVG()->mFillRule;
   }
 
   PRBool isHit = PR_FALSE;
 
   nsRefPtr<gfxContext> context =
@@ -516,53 +516,55 @@ nsSVGPathGeometryFrame::GetHittestMask()
       break;
     case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED:
     case NS_STYLE_POINTER_EVENTS_AUTO:
       if (GetStyleVisibility()->IsVisible()) {
         if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)
           mask |= HITTEST_MASK_FILL;
         if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None)
           mask |= HITTEST_MASK_STROKE;
+        if (GetStyleSVG()->mStrokeOpacity > 0)
+          mask |= HITTEST_MASK_CHECK_MRECT;
       }
       break;
     case NS_STYLE_POINTER_EVENTS_VISIBLEFILL:
       if (GetStyleVisibility()->IsVisible()) {
-        mask |= HITTEST_MASK_FILL | HITTEST_MASK_FORCE_TEST;
+        mask |= HITTEST_MASK_FILL;
       }
       break;
     case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE:
       if (GetStyleVisibility()->IsVisible()) {
-        mask |= HITTEST_MASK_STROKE | HITTEST_MASK_FORCE_TEST;
+        mask |= HITTEST_MASK_STROKE;
       }
       break;
     case NS_STYLE_POINTER_EVENTS_VISIBLE:
       if (GetStyleVisibility()->IsVisible()) {
         mask |=
           HITTEST_MASK_FILL |
-          HITTEST_MASK_STROKE |
-          HITTEST_MASK_FORCE_TEST;
+          HITTEST_MASK_STROKE;
       }
       break;
     case NS_STYLE_POINTER_EVENTS_PAINTED:
       if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None)
         mask |= HITTEST_MASK_FILL;
       if (GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None)
         mask |= HITTEST_MASK_STROKE;
+      if (GetStyleSVG()->mStrokeOpacity)
+        mask |= HITTEST_MASK_CHECK_MRECT;
       break;
     case NS_STYLE_POINTER_EVENTS_FILL:
-      mask |= HITTEST_MASK_FILL | HITTEST_MASK_FORCE_TEST;
+      mask |= HITTEST_MASK_FILL;
       break;
     case NS_STYLE_POINTER_EVENTS_STROKE:
-      mask |= HITTEST_MASK_STROKE | HITTEST_MASK_FORCE_TEST;
+      mask |= HITTEST_MASK_STROKE;
       break;
     case NS_STYLE_POINTER_EVENTS_ALL:
       mask |=
         HITTEST_MASK_FILL |
-        HITTEST_MASK_STROKE |
-        HITTEST_MASK_FORCE_TEST;
+        HITTEST_MASK_STROKE;
       break;
     default:
       NS_ERROR("not reached");
       break;
   }
 
   return mask;
 }
--- a/layout/svg/base/src/nsSVGPathGeometryFrame.h
+++ b/layout/svg/base/src/nsSVGPathGeometryFrame.h
@@ -49,17 +49,17 @@
 
 class nsSVGMarkerFrame;
 class nsSVGMarkerProperty;
 
 typedef nsSVGGeometryFrame nsSVGPathGeometryFrameBase;
 
 #define HITTEST_MASK_FILL        0x01
 #define HITTEST_MASK_STROKE      0x02
-#define HITTEST_MASK_FORCE_TEST  0x04
+#define HITTEST_MASK_CHECK_MRECT 0x04
 
 class nsSVGPathGeometryFrame : public nsSVGPathGeometryFrameBase,
                                public nsISVGChildFrame
 {
   friend nsIFrame*
   NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 protected:
   nsSVGPathGeometryFrame(nsStyleContext* aContext) :