NodesFromRect and area-based hit-testing can not handle CSS transforms.
authorallan.jensen@nokia.com <allan.jensen@nokia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 25 Jul 2012 16:09:53 +0000 (16:09 +0000)
committerallan.jensen@nokia.com <allan.jensen@nokia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 25 Jul 2012 16:09:53 +0000 (16:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=85792

Reviewed by Eric Seidel.

Source/WebCore:

To support the combination of CSS transforms and rect based hit testing,
we need to test against the transformed rect, instead of the original rect.

This patch makes HitTestPoint store the exact transformed FloatPoint and
FloatQuad, and modifies the intersection methods so that they will use a
new FloatQuad based intersection when transforms requires it.

Tests: fast/dom/nodesFromRect/nodesFromRect-rotate.html
       fast/dom/nodesFromRect/nodesFromRect-scale.html

* platform/graphics/FloatQuad.cpp:
(WebCore::determinant):
(WebCore::rightMostCornerToVector):
(WebCore::FloatQuad::intersectsRect):
(WebCore::FloatQuad::isCounterclockwise):
* platform/graphics/FloatQuad.h:
(FloatQuad):
* rendering/HitTestResult.cpp:
(WebCore::HitTestPoint::HitTestPoint):
(WebCore::HitTestPoint::operator=):
(WebCore::HitTestPoint::move):
(WebCore::HitTestPoint::intersectsRect):
(WebCore::HitTestPoint::intersects):
* rendering/HitTestResult.h:
(HitTestPoint):
(WebCore::HitTestPoint::isRectilinear):
(WebCore::HitTestPoint::transformedPoint):
(WebCore::HitTestPoint::transformedRect):
* rendering/HitTestingTransformState.cpp:
(WebCore::HitTestingTransformState::flattenWithTransform):
(WebCore::HitTestingTransformState::mappedArea):
(WebCore::HitTestingTransformState::boundsOfMappedArea):
* rendering/HitTestingTransformState.h:
(WebCore::HitTestingTransformState::create):
(WebCore::HitTestingTransformState::HitTestingTransformState):
* rendering/RenderFlowThread.cpp:
(WebCore::RenderFlowThread::hitTestRegion):
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::hitTest):
(WebCore::RenderLayer::createLocalTransformState):
(WebCore::RenderLayer::hitTestLayer):
(WebCore::RenderLayer::hitTestChildLayerColumns):
* rendering/RenderLayer.h:
* rendering/svg/RenderSVGText.cpp:
(WebCore::RenderSVGText::nodeAtFloatPoint):

LayoutTests:

Adds nodesFromRect tests that tests the API under the CSS transforms
scale and rotate.

* fast/dom/nodesFromRect/nodesFromRect-rotate-expected.txt: Added.
* fast/dom/nodesFromRect/nodesFromRect-rotate.html: Added.
* fast/dom/nodesFromRect/nodesFromRect-scale-expected.txt: Added.
* fast/dom/nodesFromRect/nodesFromRect-scale.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@123624 268f45cc-cd09-0410-ab3c-d52691b4dbfc

16 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/nodesFromRect/nodesFromRect-rotate-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/nodesFromRect/nodesFromRect-rotate.html [new file with mode: 0644]
LayoutTests/fast/dom/nodesFromRect/nodesFromRect-scale-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/nodesFromRect/nodesFromRect-scale.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/FloatQuad.cpp
Source/WebCore/platform/graphics/FloatQuad.h
Source/WebCore/rendering/HitTestResult.cpp
Source/WebCore/rendering/HitTestResult.h
Source/WebCore/rendering/HitTestingTransformState.cpp
Source/WebCore/rendering/HitTestingTransformState.h
Source/WebCore/rendering/RenderFlowThread.cpp
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderLayer.h
Source/WebCore/rendering/svg/RenderSVGText.cpp

index 4b36613..ef88bb4 100644 (file)
@@ -1,3 +1,18 @@
+2012-07-25  Allan Sandfeld Jensen  <allan.jensen@nokia.com>
+
+        NodesFromRect and area-based hit-testing can not handle CSS transforms.
+        https://bugs.webkit.org/show_bug.cgi?id=85792
+
+        Reviewed by Eric Seidel.
+
+        Adds nodesFromRect tests that tests the API under the CSS transforms
+        scale and rotate.
+
+        * fast/dom/nodesFromRect/nodesFromRect-rotate-expected.txt: Added.
+        * fast/dom/nodesFromRect/nodesFromRect-rotate.html: Added.
+        * fast/dom/nodesFromRect/nodesFromRect-scale-expected.txt: Added.
+        * fast/dom/nodesFromRect/nodesFromRect-scale.html: Added.
+
 2012-07-25  Caio Marcelo de Oliveira Filho  <caio.oliveira@openbossa.org>
 
         [Qt] css2.1/t1* tests needs rebaseline after new testfonts
diff --git a/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-rotate-expected.txt b/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-rotate-expected.txt
new file mode 100644 (file)
index 0000000..3fc3e19
--- /dev/null
@@ -0,0 +1,49 @@
+Document::nodesFromRect : CSS rotate transforms - bug 85792
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Check unrotated area-testing for sanity
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+Check rotated 180deg
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+Check rotated 90deg
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+Check rotated 45deg
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+
diff --git a/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-rotate.html b/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-rotate.html
new file mode 100644 (file)
index 0000000..9ed5aa2
--- /dev/null
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Document::nodesFromRect : CSS rotate transforms - bug 85792</title>
+<style type="text/css">
+    #sandbox {
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        width: 700px;
+        height: 700px;
+    }
+    #layer {
+        position: absolute;
+        left: 200px;
+        top: 200px;
+        width: 300px;
+        height: 300px;
+    }
+    .rotate45 { -webkit-transform: rotate(45deg); }
+    .rotate90 { -webkit-transform: rotate(90deg); }
+    .rotate180 { -webkit-transform: rotate(180deg); }
+    #layer > #fleft { float: left; width: 50px; height: 300px; }
+    #layer > #fright { float: right; width: 50px; height: 300px; }
+    #layer > .hbox { height: 100px; margin-right: 50px; margin-left: 50px }
+</style>
+<script src="../../js/resources/js-test-pre.js"></script>
+<script src="resources/nodesFromRect.js"></script>
+</head>
+
+<body>
+    <div id=sandbox>
+        <div id=layer>
+            <div id=fleft></div>
+            <div id=fright></div>
+            <div id=box1 class=hbox></div>
+            <div id=box2 class=hbox></div>
+            <div id=box3 class=hbox></div>
+        </div>
+    </div>
+
+    <script>
+        function runTest()
+        {
+            description(document.title);
+            var e = {};
+
+            // Set up shortcut access to elements
+            ['sandbox', 'layer', 'fleft', 'fright', 'box1', 'box2', 'box3'].forEach(function(a) {
+                e[a] = document.getElementById(a);
+            });
+
+            window.scrollTo(0, 0);
+            debug('Check unrotated area-testing for sanity');
+            check(150, 150, 30, 30, 30, 30, [e.sandbox]);
+            check(220, 220, 10, 10, 10, 10, [e.fleft]);
+            check(470, 220, 10, 10, 10, 10, [e.fright]);
+            check(270, 220, 10, 10, 10, 10, [e.box1]);
+            check(270, 320, 10, 10, 10, 10, [e.box2]);
+            check(240, 290, 30, 30, 30, 30, [e.fleft, e.box2, e.box1, e.layer]);
+            check(180, 220, 10, 30, 10, 0, [e.fleft, e.layer, e.sandbox]);
+            check(270, 280, 0, 10, 30, 10, [e.box2, e.box1, e.layer]);
+            check(180, 220, 10, 0, 10, 30, [e.sandbox]);
+
+
+            debug('Check rotated 180deg');
+            e['layer'].setAttribute('class', 'rotate180');
+            check(150, 150, 30, 30, 30, 30, [e.sandbox]);
+            check(220, 220, 10, 10, 10, 10, [e.fright]);
+            check(470, 220, 10, 10, 10, 10, [e.fleft]);
+            check(270, 220, 10, 10, 10, 10, [e.box3]);
+            check(270, 320, 10, 10, 10, 10, [e.box2]);
+            check(240, 290, 30, 30, 30, 30, [e.fright, e.box3, e.box2, e.layer]);
+            check(180, 220, 10, 30, 10, 0, [e.fright, e.layer, e.sandbox]);
+            check(270, 280, 0, 10, 30, 10, [e.box3, e.box2, e.layer]);
+            check(180, 220, 10, 0, 10, 30, [e.sandbox]);
+
+            debug('Check rotated 90deg');
+            e['layer'].setAttribute('class', 'rotate90');
+            check(150, 150, 30, 30, 30, 30, [e.sandbox]);
+            check(220, 280, 10, 10, 10, 10, [e.box3]);
+            check(470, 280, 10, 10, 10, 10, [e.box1]);
+            check(270, 220, 10, 10, 10, 10, [e.fleft]);
+            check(270, 470, 10, 10, 10, 10, [e.fright]);
+            check(290, 240, 30, 30, 30, 30, [e.fleft, e.box3, e.box2, e.layer]);
+            check(180, 280, 10, 30, 10, 0, [e.box3, e.layer, e.sandbox]);
+            check(330, 180, 0, 10, 30, 10, [e.fleft, e.layer, e.sandbox]);
+            check(280, 260, 40, 10, 10, 10, [e.fleft, e.box3, e.layer]);
+
+            debug('Check rotated 45deg');
+            e['layer'].setAttribute('class', 'rotate45');
+            check(150, 150, 30, 30, 30, 30, [e.sandbox]);
+            check(260, 260, 10, 10, 10, 10, [e.fleft]);
+            check(440, 440, 10, 10, 10, 10, [e.fright]);
+            check(440, 260, 10, 10, 10, 10, [e.box1]);
+            check(260, 440, 10, 10, 10, 10, [e.box3]);
+            check(350, 140, 20, 20, 20, 20, [e.fleft, e.layer, e.sandbox]);
+            check(350, 140, 20, 20, 70, 20, [e.fleft, e.box1, e.layer, e.sandbox]);
+            check(350, 350, 40, 40, 40, 40, [e.box3, e.box2, e.box1, e.layer]);
+            check(260, 260, 1, 27, 1, 27, [e.fleft]);
+
+        }
+
+        window.onload = runTest;
+    </script>
+
+    <p id='description'></p>
+    <span id="console"></span>
+    <script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
+
diff --git a/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-scale-expected.txt b/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-scale-expected.txt
new file mode 100644 (file)
index 0000000..6739a11
--- /dev/null
@@ -0,0 +1,38 @@
+Document::nodesFromRect : CSS scale transforms - bug 85792
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Check unscaled area-testing for sanity
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+Check scaling 2X
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+Check scaling 0.5X
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+PASS All correct nodes found for rect
+
diff --git a/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-scale.html b/LayoutTests/fast/dom/nodesFromRect/nodesFromRect-scale.html
new file mode 100644 (file)
index 0000000..65d53b8
--- /dev/null
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Document::nodesFromRect : CSS scale transforms - bug 85792</title>
+<style type="text/css">
+    #sandbox {
+        position: absolute;
+        left: 0px;
+        top: 0px;
+        width: 600px;
+        height: 600px;
+    }
+    #layer {
+        position: absolute;
+        left: 200px;
+        top: 200px;
+        width: 200px;
+        height: 200px;
+    }
+    .scaleup { -webkit-transform: scale(2); }
+    .scaledown { -webkit-transform: scale(0.5); }
+    #layer > #fleft { float: left; width: 50px; height: 200px; }
+    #layer > #fright { float: right; width: 50px; height: 200px; }
+    #layer > .hbox { height: 100px; margin-right: 50px; margin-left: 50px }
+</style>
+<script src="../../js/resources/js-test-pre.js"></script>
+<script src="resources/nodesFromRect.js"></script>
+</head>
+
+<body>
+    <div id=sandbox>
+        <div id=layer>
+            <div id=fleft></div>
+            <div id=fright></div>
+            <div id=box1 class=hbox></div>
+            <div id=box2 class=hbox></div>
+        </div>
+    </div>
+
+    <script>
+        function runTest()
+        {
+            description(document.title);
+            var e = {};
+
+            // Set up shortcut access to elements
+            ['sandbox', 'layer', 'fleft', 'fright', 'box1', 'box2', 'box3'].forEach(function(a) {
+                e[a] = document.getElementById(a);
+            });
+
+            window.scrollTo(0, 0);
+            debug('Check unscaled area-testing for sanity');
+            check(150, 150, 30, 30, 30, 30, [e.sandbox]);
+            check(220, 220, 10, 10, 10, 10, [e.fleft]);
+            check(370, 220, 10, 10, 10, 10, [e.fright]);
+            check(270, 220, 10, 10, 10, 10, [e.box1]);
+            check(270, 320, 10, 10, 10, 10, [e.box2]);
+            check(240, 290, 30, 30, 30, 30, [e.fleft, e.box2, e.box1, e.layer]);
+            check(180, 220, 10, 30, 10, 0, [e.fleft, e.layer, e.sandbox]);
+            check(270, 280, 0, 10, 30, 10, [e.box2, e.box1, e.layer]);
+            check(180, 220, 10, 0, 10, 30, [e.sandbox]);
+
+            debug('Check scaling 2X');
+            e['layer'].setAttribute('class', 'scaleup');
+            check(150, 150, 30, 30, 30, 30, [e.fleft]);
+            check(450, 150, 30, 30, 30, 30, [e.fright]);
+            check(350, 220, 10, 10, 10, 10, [e.box1]);
+            check(350, 420, 10, 10, 10, 10, [e.box2]);
+            check(180, 280, 60, 60, 60, 60, [e.fleft, e.box2, e.box1, e.layer]);
+            check(60, 140, 20, 60, 20, 0, [e.fleft, e.layer, e.sandbox]);
+            check(240, 260, 0, 20, 60, 20, [e.box2, e.box1, e.layer]);
+            check(60, 140, 20, 0, 20, 60, [e.sandbox]);
+
+            debug('Check scaling 0.5X');
+            e['layer'].setAttribute('class', 'scaledown');
+            check(225, 225, 15, 15, 15, 15, [e.sandbox]);
+            check(260, 260, 5, 5, 5, 5, [e.fleft]);
+            check(335, 260, 5, 5, 5, 5, [e.fright]);
+            check(285, 260, 5, 5, 5, 5, [e.box1]);
+            check(285, 310, 5, 5, 5, 5, [e.box2]);
+            check(270, 295, 15, 15, 15, 15, [e.fleft, e.box2, e.box1, e.layer]);
+            check(240, 260, 5, 15, 5, 0, [e.fleft, e.layer, e.sandbox]);
+            check(285, 290, 0, 5, 15, 5, [e.box2, e.box1, e.layer]);
+            check(240, 260, 5, 0, 5, 15, [e.sandbox]);
+
+        }
+
+        window.onload = runTest;
+    </script>
+
+    <p id='description'></p>
+    <span id="console"></span>
+    <script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
+
index 08f8791..3817449 100644 (file)
@@ -1,3 +1,56 @@
+2012-07-25  Allan Sandfeld Jensen  <allan.jensen@nokia.com>
+
+        NodesFromRect and area-based hit-testing can not handle CSS transforms.
+        https://bugs.webkit.org/show_bug.cgi?id=85792
+
+        Reviewed by Eric Seidel.
+
+        To support the combination of CSS transforms and rect based hit testing,
+        we need to test against the transformed rect, instead of the original rect.
+
+        This patch makes HitTestPoint store the exact transformed FloatPoint and 
+        FloatQuad, and modifies the intersection methods so that they will use a 
+        new FloatQuad based intersection when transforms requires it.
+
+        Tests: fast/dom/nodesFromRect/nodesFromRect-rotate.html
+               fast/dom/nodesFromRect/nodesFromRect-scale.html
+
+        * platform/graphics/FloatQuad.cpp:
+        (WebCore::determinant):
+        (WebCore::rightMostCornerToVector):
+        (WebCore::FloatQuad::intersectsRect):
+        (WebCore::FloatQuad::isCounterclockwise):
+        * platform/graphics/FloatQuad.h:
+        (FloatQuad):
+        * rendering/HitTestResult.cpp:
+        (WebCore::HitTestPoint::HitTestPoint):
+        (WebCore::HitTestPoint::operator=):
+        (WebCore::HitTestPoint::move):
+        (WebCore::HitTestPoint::intersectsRect):
+        (WebCore::HitTestPoint::intersects):
+        * rendering/HitTestResult.h:
+        (HitTestPoint):
+        (WebCore::HitTestPoint::isRectilinear):
+        (WebCore::HitTestPoint::transformedPoint):
+        (WebCore::HitTestPoint::transformedRect):
+        * rendering/HitTestingTransformState.cpp:
+        (WebCore::HitTestingTransformState::flattenWithTransform):
+        (WebCore::HitTestingTransformState::mappedArea):
+        (WebCore::HitTestingTransformState::boundsOfMappedArea):
+        * rendering/HitTestingTransformState.h:
+        (WebCore::HitTestingTransformState::create):
+        (WebCore::HitTestingTransformState::HitTestingTransformState):
+        * rendering/RenderFlowThread.cpp:
+        (WebCore::RenderFlowThread::hitTestRegion):
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::hitTest):
+        (WebCore::RenderLayer::createLocalTransformState):
+        (WebCore::RenderLayer::hitTestLayer):
+        (WebCore::RenderLayer::hitTestChildLayerColumns):
+        * rendering/RenderLayer.h:
+        * rendering/svg/RenderSVGText.cpp:
+        (WebCore::RenderSVGText::nodeAtFloatPoint):
+
 2012-07-25  Kwang Yul Seo  <skyul@company100.net>
 
         Add HTMLStackItem.h to project files
index 1ad787b..fc9d613 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -51,6 +52,11 @@ inline float dot(const FloatSize& a, const FloatSize& b)
     return a.width() * b.width() + a.height() * b.height();
 }
 
+inline float determinant(const FloatSize& a, const FloatSize& b)
+{
+    return a.width() * b.height() - a.height() * b.width();
+}
+
 inline bool isPointInTriangle(const FloatPoint& p, const FloatPoint& t1, const FloatPoint& t2, const FloatPoint& t3)
 {
     // Compute vectors        
@@ -107,11 +113,74 @@ bool FloatQuad::containsQuad(const FloatQuad& other) const
     return containsPoint(other.p1()) && containsPoint(other.p2()) && containsPoint(other.p3()) && containsPoint(other.p4());
 }
 
+static inline FloatPoint rightMostCornerToVector(const FloatRect& rect, const FloatSize& vector)
+{
+    // Return the corner of the rectangle that if it is to the left of the vector
+    // would mean all of the rectangle is to the left of the vector.
+    // The vector here represents the side between two points in a clockwise convex polygon.
+    //
+    //  Q  XXX
+    // QQQ XXX   If the lower left corner of X is left of the vector that goes from the top corner of Q to
+    //  QQQ      the right corner of Q, then all of X is left of the vector, and intersection impossible.
+    //   Q
+    //
+    FloatPoint point;
+    if (vector.width() >= 0)
+        point.setY(rect.maxY());
+    else
+        point.setY(rect.y());
+    if (vector.height() >= 0)
+        point.setX(rect.x());
+    else
+        point.setX(rect.maxX());
+    return point;
+}
+
+bool FloatQuad::intersectsRect(const FloatRect& rect) const
+{
+    // For each side of the quad clockwise we check if the rectangle is to the left of it
+    // since only content on the right can onlap with the quad.
+    // This only works if the quad is convex.
+    FloatSize v1, v2, v3, v4;
+
+    // Ensure we use clockwise vectors.
+    if (!isCounterclockwise()) {
+        v1 = m_p2 - m_p1;
+        v2 = m_p3 - m_p2;
+        v3 = m_p4 - m_p3;
+        v4 = m_p1 - m_p4;
+    } else {
+        v1 = m_p4 - m_p1;
+        v2 = m_p1 - m_p2;
+        v3 = m_p2 - m_p3;
+        v4 = m_p3 - m_p4;
+    }
+
+    FloatPoint p = rightMostCornerToVector(rect, v1);
+    if (determinant(v1, p - m_p1) < 0)
+        return false;
+
+    p = rightMostCornerToVector(rect, v2);
+    if (determinant(v2, p - m_p2) < 0)
+        return false;
+
+    p = rightMostCornerToVector(rect, v3);
+    if (determinant(v3, p - m_p3) < 0)
+        return false;
+
+    p = rightMostCornerToVector(rect, v4);
+    if (determinant(v4, p - m_p4) < 0)
+        return false;
+
+    // If not all of the rectangle is outside one of the quad's four sides, then that means at least
+    // a part of the rectangle is overlapping the quad.
+    return true;
+}
+
 bool FloatQuad::isCounterclockwise() const
 {
-    FloatPoint v1 = FloatPoint(m_p2.x() - m_p1.x(), m_p2.y() - m_p1.y());
-    FloatPoint v2 = FloatPoint(m_p3.x() - m_p2.x(), m_p3.y() - m_p2.y());
-    return (v1.x() * v2.y() - v1.y() * v2.x()) < 0;
+    // Return if the two first vectors are turning clockwise. If the quad is convex then all following vectors will turn the same way.
+    return determinant(m_p2 - m_p1, m_p3 - m_p2) < 0;
 }
 
 } // namespace WebCore
index bfb794d..44d2e9d 100644 (file)
@@ -88,6 +88,10 @@ public:
     // from transformed rects.
     bool containsQuad(const FloatQuad&) const;
 
+    // Tests whether any part of the rectangle intersects with this quad.
+    // This only works for convex quads.
+    bool intersectsRect(const FloatRect&) const;
+
     // The center of the quad. If the quad is the result of a affine-transformed rectangle this is the same as the original center transformed.
     FloatPoint center() const
     {
index 541f5d0..769865b 100644 (file)
@@ -50,27 +50,57 @@ using namespace HTMLNames;
 
 HitTestPoint::HitTestPoint()
     : m_isRectBased(false)
+    , m_isRectilinear(true)
 {
 }
 
 HitTestPoint::HitTestPoint(const LayoutPoint& point)
     : m_point(point)
     , m_boundingBox(rectForPoint(point, 0, 0, 0, 0))
+    , m_transformedPoint(point)
+    , m_transformedRect(m_boundingBox)
     , m_isRectBased(false)
+    , m_isRectilinear(true)
 {
 }
 
+HitTestPoint::HitTestPoint(const FloatPoint& point)
+    : m_point(roundedLayoutPoint(point))
+    , m_boundingBox(rectForPoint(m_point, 0, 0, 0, 0))
+    , m_transformedPoint(point)
+    , m_transformedRect(m_boundingBox)
+    , m_isRectBased(false)
+    , m_isRectilinear(true)
+{
+}
+
+HitTestPoint::HitTestPoint(const FloatPoint& point, const FloatQuad& quad)
+    : m_transformedPoint(point)
+    , m_transformedRect(quad)
+    , m_isRectBased(true)
+{
+    m_point = roundedLayoutPoint(point);
+    m_boundingBox = enclosingIntRect(quad.boundingBox());
+    m_isRectilinear = quad.isRectilinear();
+}
+
 HitTestPoint::HitTestPoint(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
     : m_point(centerPoint)
     , m_boundingBox(rectForPoint(centerPoint, topPadding, rightPadding, bottomPadding, leftPadding))
+    , m_transformedPoint(centerPoint)
     , m_isRectBased(topPadding || rightPadding || bottomPadding || leftPadding)
+    , m_isRectilinear(true)
 {
+    m_transformedRect = FloatQuad(m_boundingBox);
 }
 
 HitTestPoint::HitTestPoint(const HitTestPoint& other)
     : m_point(other.m_point)
     , m_boundingBox(other.m_boundingBox)
+    , m_transformedPoint(other.m_transformedPoint)
+    , m_transformedRect(other.m_transformedRect)
     , m_isRectBased(other.m_isRectBased)
+    , m_isRectilinear(other.m_isRectilinear)
 {
 }
 
@@ -82,40 +112,52 @@ HitTestPoint& HitTestPoint::operator=(const HitTestPoint& other)
 {
     m_point = other.m_point;
     m_boundingBox = other.m_boundingBox;
+    m_transformedPoint = other.m_transformedPoint;
+    m_transformedRect = other.m_transformedRect;
     m_isRectBased = other.m_isRectBased;
+    m_isRectilinear = other.m_isRectilinear;
 
     return *this;
 }
 
-void HitTestPoint::setPoint(const LayoutPoint& point)
+void HitTestPoint::move(const LayoutSize& offset)
 {
-    m_boundingBox.move(roundedIntPoint(point) - roundedIntPoint(m_point));
-    m_point = point;
+    m_point.move(offset);
+    m_transformedPoint.move(offset);
+    m_transformedRect.move(offset);
+    m_boundingBox = enclosingIntRect(m_transformedRect.boundingBox());
 }
 
 template<typename RectType>
-bool hitTestPointIntersects(const HitTestPoint& hitTestPoint, const RectType& rect)
+bool HitTestPoint::intersectsRect(const RectType& rect) const
 {
     // FIXME: When the hit test is not rect based we should use rect.contains(m_point).
     // That does change some corner case tests though.
 
-    // First check if rect even intersects our bounding rect.
-    if (!rect.intersects(hitTestPoint.boundingBox()))
+    // First check if rect even intersects our bounding box.
+    if (!rect.intersects(m_boundingBox))
         return false;
 
-    // FIXME: Implement quad based intersection test to handle transformed hit test rectangles.
-    return true;
+    // If the transformed rect is rectilinear the bounding box intersection was accurate.
+    if (m_isRectilinear)
+        return true;
+
+    // If rect fully contains our bounding box, we are also sure of an intersection.
+    if (rect.contains(m_boundingBox))
+        return true;
 
+    // Otherwise we need to do a slower quad based intersection test.
+    return m_transformedRect.intersectsRect(rect);
 }
 
 bool HitTestPoint::intersects(const LayoutRect& rect) const
 {
-    return hitTestPointIntersects(*this, rect);
+    return intersectsRect(rect);
 }
 
 bool HitTestPoint::intersects(const FloatRect& rect) const
 {
-    return hitTestPointIntersects(*this, rect);
+    return intersectsRect(rect);
 }
 
 IntRect HitTestPoint::rectForPoint(const LayoutPoint& point, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding)
index affab95..e6114a1 100644 (file)
@@ -22,6 +22,7 @@
 #ifndef HitTestResult_h
 #define HitTestResult_h
 
+#include "FloatQuad.h"
 #include "FloatRect.h"
 #include "HitTestRequest.h"
 #include "LayoutTypes.h"
@@ -51,6 +52,8 @@ public:
 
     HitTestPoint();
     HitTestPoint(const LayoutPoint&);
+    HitTestPoint(const FloatPoint&);
+    HitTestPoint(const FloatPoint&, const FloatQuad&);
     // Pass non-zero padding values to perform a rect-based hit test.
     HitTestPoint(const LayoutPoint& centerPoint, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding);
     HitTestPoint(const HitTestPoint&);
@@ -60,10 +63,11 @@ public:
     LayoutPoint point() const { return m_point; }
     IntPoint roundedPoint() const { return roundedIntPoint(m_point); }
 
-    void setPoint(const LayoutPoint&);
+    void move(const LayoutSize& offset);
 
     // Rect-based hit test related methods.
     bool isRectBasedTest() const { return m_isRectBased; }
+    bool isRectilinear() const { return m_isRectilinear; }
     IntRect boundingBox() const { return m_boundingBox; }
 
     static IntRect rectForPoint(const LayoutPoint&, unsigned topPadding, unsigned rightPadding, unsigned bottomPadding, unsigned leftPadding);
@@ -75,11 +79,21 @@ public:
     bool intersects(const LayoutRect&) const;
     bool intersects(const FloatRect&) const;
 
+    const FloatPoint& transformedPoint() const { return m_transformedPoint; }
+    const FloatQuad& transformedRect() const { return m_transformedRect; }
+
 private:
-    LayoutPoint m_point;
+    template<typename RectType>
+    bool intersectsRect(const RectType&) const;
 
+    // This is cached forms of the more accurate point and area below.
+    LayoutPoint m_point;
     IntRect m_boundingBox;
+
+    FloatPoint m_transformedPoint;
+    FloatQuad m_transformedRect;
     bool m_isRectBased;
+    bool m_isRectilinear;
 };
 
 class HitTestResult : public HitTestPoint {
index 4934a7b..b089336 100644 (file)
@@ -58,6 +58,7 @@ void HitTestingTransformState::flattenWithTransform(const TransformationMatrix&
     TransformationMatrix inverseTransform = t.inverse();
     m_lastPlanarPoint = inverseTransform.projectPoint(m_lastPlanarPoint);
     m_lastPlanarQuad = inverseTransform.projectQuad(m_lastPlanarQuad);
+    m_lastPlanarArea = inverseTransform.projectQuad(m_lastPlanarArea);
 
     m_accumulatedTransform.makeIdentity();
     m_accumulatingTransform = false;
@@ -73,9 +74,14 @@ FloatQuad HitTestingTransformState::mappedQuad() const
     return m_accumulatedTransform.inverse().projectQuad(m_lastPlanarQuad);
 }
 
-LayoutRect HitTestingTransformState::boundsOfMappedQuad() const
+FloatQuad HitTestingTransformState::mappedArea() const
 {
-    return m_accumulatedTransform.inverse().clampedBoundsOfProjectedQuad(m_lastPlanarQuad);
+    return m_accumulatedTransform.inverse().projectQuad(m_lastPlanarArea);
+}
+
+LayoutRect HitTestingTransformState::boundsOfMappedArea() const
+{
+    return m_accumulatedTransform.inverse().clampedBoundsOfProjectedQuad(m_lastPlanarArea);
 }
 
 } // namespace WebCore
index 2439dff..95ec25b 100644 (file)
@@ -42,9 +42,9 @@ namespace WebCore {
 // differently than move()) so care has to be taken when this is done.
 class HitTestingTransformState : public RefCounted<HitTestingTransformState> {
 public:
-    static PassRefPtr<HitTestingTransformState> create(const FloatPoint& p, const FloatQuad& quad)
+    static PassRefPtr<HitTestingTransformState> create(const FloatPoint& p, const FloatQuad& quad, const FloatQuad& area)
     {
-        return adoptRef(new HitTestingTransformState(p, quad));
+        return adoptRef(new HitTestingTransformState(p, quad, area));
     }
 
     static PassRefPtr<HitTestingTransformState> create(const HitTestingTransformState& other)
@@ -58,18 +58,21 @@ public:
 
     FloatPoint mappedPoint() const;
     FloatQuad mappedQuad() const;
-    LayoutRect boundsOfMappedQuad() const;
+    FloatQuad mappedArea() const;
+    LayoutRect boundsOfMappedArea() const;
     void flatten();
 
     FloatPoint m_lastPlanarPoint;
     FloatQuad m_lastPlanarQuad;
+    FloatQuad m_lastPlanarArea;
     TransformationMatrix m_accumulatedTransform;
     bool m_accumulatingTransform;
 
 private:
-    HitTestingTransformState(const FloatPoint& p, const FloatQuad& quad)
+    HitTestingTransformState(const FloatPoint& p, const FloatQuad& quad, const FloatQuad& area)
         : m_lastPlanarPoint(p)
         , m_lastPlanarQuad(quad)
+        , m_lastPlanarArea(area)
         , m_accumulatingTransform(false)
     {
     }
@@ -78,6 +81,7 @@ private:
         : RefCounted<HitTestingTransformState>()
         , m_lastPlanarPoint(other.m_lastPlanarPoint)
         , m_lastPlanarQuad(other.m_lastPlanarQuad)
+        , m_lastPlanarArea(other.m_lastPlanarArea)
         , m_accumulatedTransform(other.m_accumulatedTransform)
         , m_accumulatingTransform(other.m_accumulatingTransform)
     {
index 669d46d..b7a6e58 100644 (file)
@@ -315,17 +315,16 @@ bool RenderFlowThread::hitTestRegion(RenderRegion* region, const HitTestRequest&
     } else
         renderFlowThreadOffset = accumulatedOffset - regionRect.location();
 
-    LayoutPoint transformedPoint = pointInContainer.point() - renderFlowThreadOffset;
-
     // Always ignore clipping, since the RenderFlowThread has nothing to do with the bounds of the FrameView.
     HitTestRequest newRequest(request.type() | HitTestRequest::IgnoreClipping);
 
     RenderRegion* oldRegion = result.region();
     result.setRegion(region);
-    LayoutPoint oldPoint = result.point();
-    result.setPoint(transformedPoint);
-    bool isPointInsideFlowThread = layer()->hitTest(newRequest, result);
-    result.setPoint(oldPoint);
+
+    HitTestPoint newHitTestPoint(pointInContainer);
+    newHitTestPoint.move(-renderFlowThreadOffset);
+
+    bool isPointInsideFlowThread = layer()->hitTest(newRequest, newHitTestPoint, result);
     result.setRegion(oldRegion);
 
     // FIXME: Should we set result.m_localPoint back to the RenderRegion's coordinate space or leave it in the RenderFlowThread's coordinate
index 441c0be..27425f7 100644 (file)
@@ -3386,13 +3386,18 @@ static inline LayoutRect frameVisibleRect(RenderObject* renderer)
 
 bool RenderLayer::hitTest(const HitTestRequest& request, HitTestResult& result)
 {
+    return hitTest(request, result.hitTestPoint(), result);
+}
+
+bool RenderLayer::hitTest(const HitTestRequest& request, const HitTestPoint& hitTestPoint, HitTestResult& result)
+{
     renderer()->document()->updateLayout();
     
     LayoutRect hitTestArea = renderer()->isRenderFlowThread() ? toRenderFlowThread(renderer())->borderBoxRect() : renderer()->view()->documentRect();
     if (!request.ignoreClipping())
         hitTestArea.intersect(frameVisibleRect(renderer()));
 
-    RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, result.hitTestPoint(), false);
+    RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, hitTestPoint, false);
     if (!insideLayer) {
         // We didn't hit any layer. If we are the root layer and the mouse is -- or just was -- down, 
         // return ourselves. We do this so mouse events continue getting delivered after a drag has 
@@ -3456,7 +3461,7 @@ PassRefPtr<HitTestingTransformState> RenderLayer::createLocalTransformState(Rend
     } else {
         // If this is the first time we need to make transform state, then base it off of hitTestPoint,
         // which is relative to rootLayer.
-        transformState = HitTestingTransformState::create(hitTestPoint.point(), FloatQuad(hitTestRect));
+        transformState = HitTestingTransformState::create(hitTestPoint.transformedPoint(), hitTestPoint.transformedRect(), FloatQuad(hitTestRect));
         convertToLayerCoords(rootLayer, offset);
     }
     
@@ -3536,10 +3541,14 @@ RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* cont
         //
         // We can't just map hitTestPoint and hitTestRect because they may have been flattened (losing z)
         // by our container.
-        LayoutPoint localPoint = roundedLayoutPoint(newTransformState->mappedPoint());
-        LayoutRect localHitTestRect = newTransformState->boundsOfMappedQuad();
-        HitTestPoint newHitTestPoint(result.hitTestPoint());
-        newHitTestPoint.setPoint(localPoint);
+        FloatPoint localPoint = newTransformState->mappedPoint();
+        FloatQuad localPointQuad = newTransformState->mappedQuad();
+        LayoutRect localHitTestRect = newTransformState->boundsOfMappedArea();
+        HitTestPoint newHitTestPoint;
+        if (hitTestPoint.isRectBasedTest())
+            newHitTestPoint = HitTestPoint(localPoint, localPointQuad);
+        else
+            newHitTestPoint = HitTestPoint(localPoint);
 
         // Now do a hit test with the root layer shifted to be us.
         return hitTestLayer(this, containerLayer, request, result, localHitTestRect, newHitTestPoint, true, newTransformState.get(), zOffset);
@@ -3835,10 +3844,14 @@ RenderLayer* RenderLayer::hitTestChildLayerColumns(RenderLayer* childLayer, Rend
                 RenderLayer* nextLayer = columnLayers[columnIndex - 1];
                 RefPtr<HitTestingTransformState> newTransformState = nextLayer->createLocalTransformState(rootLayer, nextLayer, localClipRect, hitTestPoint, transformState);
                 newTransformState->translate(offset.width(), offset.height(), HitTestingTransformState::AccumulateTransform);
-                LayoutPoint localPoint = roundedLayoutPoint(newTransformState->mappedPoint());
-                LayoutRect localHitTestRect = newTransformState->mappedQuad().enclosingBoundingBox();
-                HitTestPoint newHitTestPoint(result.hitTestPoint());
-                newHitTestPoint.setPoint(localPoint);
+                FloatPoint localPoint = newTransformState->mappedPoint();
+                FloatQuad localPointQuad = newTransformState->mappedQuad();
+                LayoutRect localHitTestRect = newTransformState->mappedArea().enclosingBoundingBox();
+                HitTestPoint newHitTestPoint;
+                if (hitTestPoint.isRectBasedTest())
+                    newHitTestPoint = HitTestPoint(localPoint, localPointQuad);
+                else
+                    newHitTestPoint = HitTestPoint(localPoint);
                 newTransformState->flatten();
 
                 hitLayer = hitTestChildLayerColumns(childLayer, columnLayers[columnIndex - 1], request, result, localHitTestRect, newHitTestPoint,
index a863a4c..67519a7 100644 (file)
@@ -512,6 +512,7 @@ public:
     void paint(GraphicsContext*, const LayoutRect& damageRect, PaintBehavior = PaintBehaviorNormal, RenderObject* paintingRoot = 0,
         RenderRegion* = 0, PaintLayerFlags = 0);
     bool hitTest(const HitTestRequest&, HitTestResult&);
+    bool hitTest(const HitTestRequest&, const HitTestPoint&, HitTestResult&);
     void paintOverlayScrollbars(GraphicsContext*, const LayoutRect& damageRect, PaintBehavior, RenderObject* paintingRoot);
 
     // This method figures out our layerBounds in coordinates relative to
index f514e21..d30ba1c 100644 (file)
@@ -451,7 +451,7 @@ bool RenderSVGText::nodeAtFloatPoint(const HitTestRequest& request, HitTestResul
             if (!SVGRenderSupport::pointInClippingArea(this, localPoint))
                 return false;       
 
-            HitTestPoint hitTestPoint(flooredIntPoint(localPoint));
+            HitTestPoint hitTestPoint(LayoutPoint(flooredIntPoint(localPoint)));
             return RenderBlock::nodeAtPoint(request, result, hitTestPoint, LayoutPoint(), hitTestAction);
         }
     }