Diff
Modified: trunk/LayoutTests/ChangeLog (116542 => 116543)
--- trunk/LayoutTests/ChangeLog 2012-05-09 17:47:03 UTC (rev 116542)
+++ trunk/LayoutTests/ChangeLog 2012-05-09 17:57:50 UTC (rev 116543)
@@ -1,3 +1,15 @@
+2012-05-03 Shawn Singh <[email protected]>
+
+ Hit testing is incorrect in some cases with perspective transforms
+ https://bugs.webkit.org/show_bug.cgi?id=79136
+
+ Reviewed by Simon Fraser.
+
+ * transforms/3d/hit-testing/coplanar-with-camera-expected.txt: Added.
+ * transforms/3d/hit-testing/coplanar-with-camera.html: Added.
+ * transforms/3d/hit-testing/perspective-clipped-expected.txt: Added.
+ * transforms/3d/hit-testing/perspective-clipped.html: Added.
+
2012-05-09 Dominik Röttsches <[email protected]>
Textarea-placeholder-wrapping.html may fail because of scrollbars
Added: trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera-expected.txt (0 => 116543)
--- trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera-expected.txt (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera-expected.txt 2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,9 @@
+The text on this element should be selectable. Hovering on this element should cause a highlight. Element at 98, 200 has id "background": PASS
+Element at 302, 200 has id "background": PASS
+Element at 200, 98 has id "background": PASS
+Element at 200, 302 has id "background": PASS
+Element at 101, 200 has id "layer": PASS
+Element at 299, 200 has id "layer": PASS
+Element at 200, 101 has id "layer": PASS
+Element at 200, 299 has id "layer": PASS
+
Added: trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera.html (0 => 116543)
--- trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera.html (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/coplanar-with-camera.html 2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,84 @@
+<html>
+ <!-- This test reproduces a divide-by-zero error that is hopefully fixed by
+ https://bugs.webkit.org/show_bug.cgi?id=79136. In that bug, a layer that gets
+ translated by z so that it is coplanar with the camera origin. As a result, when
+ trying to project a point from the container space to the local space, the
+ implementation had a divide-by-zero which made hit-testing results incorrect. -->
+
+<head>
+ <style type="text/css">
+ /* Marquee content. */
+ #camera {
+ position: absolute;
+ top: 100px;
+ left: 100px;
+ -webkit-perspective: 800px;
+ }
+
+ #container {
+ -webkit-transform-style: preserve-3d;
+ -webkit-transform: translateZ(800px)
+ }
+
+ #layer {
+ position: absolute;
+ width: 200px;
+ height: 200px;
+ background-color: green;
+
+ /* This should theoretically cancel out the container's transform, and hit-testing should work. */
+ -webkit-transform: translateZ(-800px);
+ }
+
+ #background {
+ position: absolute;
+ width: 400px;
+ height: 400px;
+ background-color: gray;
+ }
+
+ #layer:hover {
+ background-color: orange;
+ }
+
+ #results {
+ position: absolute;
+ top: 420px;
+ left: 20px;
+ }
+ </style>
+
+ <script src=""
+ <script>
+ const hitTestData = [
+ { 'point': [98, 200], 'target' : 'background' },
+ { 'point': [302, 200], 'target' : 'background' },
+ { 'point': [200, 98], 'target' : 'background' },
+ { 'point': [200, 302], 'target' : 'background' },
+ { 'point': [101, 200], 'target' : 'layer' },
+ { 'point': [299, 200], 'target' : 'layer' },
+ { 'point': [200, 101], 'target' : 'layer' },
+ { 'point': [200, 299], 'target' : 'layer' },
+ ];
+
+ window.addEventListener('load', runTest, false);
+ </script>
+</head>
+
+<body>
+
+ <div id="background"></div>
+
+ <div id="camera">
+ <div id="container">
+ <div id="layer">
+ The text on this element should be selectable.
+ Hovering on this element should cause a highlight.
+ </div>
+ </div>
+ </div>
+
+ <div id="results"></div>
+
+</body>
+</html>
Added: trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped-expected.txt (0 => 116543)
--- trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped-expected.txt (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped-expected.txt 2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,13 @@
+Element at 35, 100 has id "topLayer": PASS
+Element at 370, 100 has id "topLayer": PASS
+Element at 40, 40 has id "topLayer": PASS
+Element at 365, 40 has id "topLayer": PASS
+Element at 35, 40 has id "backgroundLayer": PASS
+Element at 370, 40 has id "backgroundLayer": PASS
+Element at 40, 340 has id "bottomLayer": PASS
+Element at 365, 340 has id "bottomLayer": PASS
+Element at 35, 260 has id "bottomLayer": PASS
+Element at 370, 280 has id "bottomLayer": PASS
+Element at 35, 340 has id "backgroundLayer": PASS
+Element at 370, 340 has id "backgroundLayer": PASS
+
Added: trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped.html (0 => 116543)
--- trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped.html (rev 0)
+++ trunk/LayoutTests/transforms/3d/hit-testing/perspective-clipped.html 2012-05-09 17:57:50 UTC (rev 116543)
@@ -0,0 +1,103 @@
+<html>
+ <!-- This test reproduces a perspective w < 0 error addressed in
+ https://bugs.webkit.org/show_bug.cgi?id=79136. In that bug, as a layer is being
+ transformed, it may get "clamped" by the homogeneous coordinate w < 0. When
+ projecting individual points, this was handled correctly, but projecting quads was
+ ignoring this clamping, causing invalid quads to be generated, which ultimately did
+ not hit-test correctly.
+ -->
+<head>
+ <style type="text/css">
+ #container {
+ -webkit-perspective: 1000px;
+ -webkit-perspective-origin-x: 145px;
+ /* removing this property fixes the issue, but doesn't provide the desired rendering */
+ -webkit-perspective-origin-y: 189px;
+ }
+
+ #intermediate {
+ position: absolute;
+ left: 40px;
+ top: 189px;
+ -webkit-transform-style: preserve-3d;
+ }
+
+ #results {
+ position: absolute;
+ top: 420px;
+ left: 20px;
+ }
+
+ #backgroundLayer {
+ position: absolute;
+ width: 400px;
+ height: 400px;
+ background-color: gray;
+ }
+
+ .highlightOnHover:hover {
+ background-color: orange;
+ }
+
+ .rotatedUp {
+ -webkit-transform: rotateX(-240deg) translateZ(200px)
+ }
+
+ .rotatedDown {
+ -webkit-transform: rotateX(-300deg) translateZ(200px)
+ }
+
+ .green {
+ background-color: green;
+ }
+
+ .box {
+ position: absolute;
+ width: 300px;
+ height: 110px;
+ }
+
+ </style>
+ <script src=""
+ <script>
+ const hitTestData = [
+ // Points near the corners of the top layer
+ { 'point': [35, 100], 'target' : 'topLayer' },
+ { 'point': [370, 100], 'target' : 'topLayer' },
+ { 'point': [40, 40], 'target' : 'topLayer' },
+ { 'point': [365, 40], 'target' : 'topLayer' },
+
+ // Points within the axis-aligned bounding box of the top layer, but not actually on the layer itself
+ { 'point': [35, 40], 'target' : 'backgroundLayer' },
+ { 'point': [370, 40], 'target' : 'backgroundLayer' },
+
+ // Points near the corners of the top layer
+ { 'point': [40, 340], 'target' : 'bottomLayer' },
+ { 'point': [365, 340], 'target' : 'bottomLayer' },
+ { 'point': [35, 260], 'target' : 'bottomLayer' },
+ { 'point': [370, 280], 'target' : 'bottomLayer' },
+
+ // Points within the axis-aligned bounding box of the bottom layer, but not actually on the layer itself
+ { 'point': [35, 340], 'target' : 'backgroundLayer' },
+ { 'point': [370, 340], 'target' : 'backgroundLayer' },
+ ];
+
+ window.addEventListener('load', runTest, false);
+ </script>
+</head>
+
+<body>
+
+ <div id="backgroundLayer"></div>
+
+ <div id="container">
+ <div id="intermediate" class="host" style="-webkit-transform: rotate3d(1, 0, 0, 270deg)">
+ <div id="topLayer" class="highlightOnHover rotatedUp green box" style=""></div>
+ <div id="bottomLayer" class="highlightOnHover rotatedDown green box" style=""></div>
+ </div>
+ </div>
+
+ <div id="results"></div>
+
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (116542 => 116543)
--- trunk/Source/WebCore/ChangeLog 2012-05-09 17:47:03 UTC (rev 116542)
+++ trunk/Source/WebCore/ChangeLog 2012-05-09 17:57:50 UTC (rev 116543)
@@ -1,3 +1,23 @@
+2012-05-03 Shawn Singh <[email protected]>
+
+ Hit testing is incorrect in some cases with perspective transforms
+ https://bugs.webkit.org/show_bug.cgi?id=79136
+
+ Reviewed by Simon Fraser.
+
+ Tests: transforms/3d/hit-testing/coplanar-with-camera.html
+ transforms/3d/hit-testing/perspective-clipped.html
+
+ * platform/graphics/transforms/TransformationMatrix.cpp:
+ (WebCore::TransformationMatrix::projectPoint): Fix a
+ divide-by-zero error so that values do not become Inf or Nan. Also
+ fix an overflow error by using a large, but not-too-large constant
+ to represent infinity.
+
+ (WebCore::TransformationMatrix::projectQuad): Fix an error where
+ incorrect quads were being returned. Incorrect quads can occur
+ when projectPoint clamped==true after returning.
+
2012-05-09 Caio Marcelo de Oliveira Filho <[email protected]>
Simplify CSSParser::parseSimpleLengthValue()
Modified: trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp (116542 => 116543)
--- trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp 2012-05-09 17:47:03 UTC (rev 116542)
+++ trunk/Source/WebCore/platform/graphics/transforms/TransformationMatrix.cpp 2012-05-09 17:57:50 UTC (rev 116543)
@@ -551,6 +551,12 @@
// d = -dot (Pn', R0) / dot (Pn', Rd)
if (clamped)
*clamped = false;
+
+ if (m33() == 0) {
+ // In this case, the projection plane is parallel to the ray we are trying to
+ // trace, and there is no well-defined value for the projection.
+ return FloatPoint();
+ }
double x = p.x();
double y = p.y();
@@ -562,8 +568,12 @@
double w = x * m14() + y * m24() + z * m34() + m44();
if (w <= 0) {
- outX = copysign(numeric_limits<int>::max(), outX);
- outY = copysign(numeric_limits<int>::max(), outY);
+ // Using int max causes overflow when other code uses the projected point. To
+ // represent infinity yet reduce the risk of overflow, we use a large but
+ // not-too-large number here when clamping.
+ const int kLargeNumber = 100000000;
+ outX = copysign(kLargeNumber, outX);
+ outY = copysign(kLargeNumber, outY);
if (clamped)
*clamped = true;
} else if (w != 1) {
@@ -577,11 +587,22 @@
FloatQuad TransformationMatrix::projectQuad(const FloatQuad& q) const
{
FloatQuad projectedQuad;
- projectedQuad.setP1(projectPoint(q.p1()));
- projectedQuad.setP2(projectPoint(q.p2()));
- projectedQuad.setP3(projectPoint(q.p3()));
- projectedQuad.setP4(projectPoint(q.p4()));
-
+
+ bool clamped1 = false;
+ bool clamped2 = false;
+ bool clamped3 = false;
+ bool clamped4 = false;
+
+ projectedQuad.setP1(projectPoint(q.p1(), &clamped1));
+ projectedQuad.setP2(projectPoint(q.p2(), &clamped2));
+ projectedQuad.setP3(projectPoint(q.p3(), &clamped3));
+ projectedQuad.setP4(projectPoint(q.p4(), &clamped4));
+
+ // If all points on the quad had w < 0, then the entire quad would not be visible to the projected surface.
+ bool everythingWasClipped = clamped1 && clamped2 && clamped3 && clamped4;
+ if (everythingWasClipped)
+ return FloatQuad();
+
return projectedQuad;
}