Title: [122970] trunk
Revision
122970
Author
commit-qu...@webkit.org
Date
2012-07-18 07:47:40 -0700 (Wed, 18 Jul 2012)

Log Message

TOUCH_ADJUSTMENT is too aggressive when snapping to large elements.
https://bugs.webkit.org/show_bug.cgi?id=91262

Patch by Kevin Ellis <kev...@chromium.org> on 2012-07-18
Reviewed by Antonio Gomes.

Source/WebCore:

Constrains the extent to which the touch point can be adjusted when
generating synthetic mouse events when TOUCH_ADJUSTEMNT is enabled.
Previously, the target position snapped to the center of the target
element, which can be far removed from the touch position when tapping
on or near a large element.  The refined strategy is to leave the
adjusted position unchanged if tapping within the element or to snap
to the center of the overlap region if the touch point lies outside the
bounds of the element, but the touch area and element bounds overlap.
For non-rectilineary bounds, a point lying outside the element boundary
is pulled towards the center of the element, by an amount limited by
the radius of the touch area.

Tests: touchadjustment/big-div.html
       touchadjustment/rotated-node.html

* page/TouchAdjustment.cpp:
(WebCore::TouchAdjustment::contentsToWindow):
(TouchAdjustment):
(WebCore::TouchAdjustment::snapTo):
(WebCore::TouchAdjustment::findNodeWithLowestDistanceMetric):

LayoutTests:

Adding a test case to ensure that the adjusted touch position is
within the bounds of the target element and the touch area.
Previously, the target position snapped to the center of the target
element, which can be far removed from the touch area.

The second test is for non-rectilinear elements, and verifies that
the touch area must overlap the true bounds of the element for an
adjustment to occur.

* touchadjustment/big-div-expected.txt: Added.
* touchadjustment/big-div.html: Added.
* touchadjustment/rotated-node-expected.txt: Added.
* touchadjustment/rotated-node.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (122969 => 122970)


--- trunk/LayoutTests/ChangeLog	2012-07-18 14:41:22 UTC (rev 122969)
+++ trunk/LayoutTests/ChangeLog	2012-07-18 14:47:40 UTC (rev 122970)
@@ -1,3 +1,24 @@
+2012-07-18  Kevin Ellis  <kev...@chromium.org>
+
+        TOUCH_ADJUSTMENT is too aggressive when snapping to large elements.
+        https://bugs.webkit.org/show_bug.cgi?id=91262
+
+        Reviewed by Antonio Gomes.
+
+        Adding a test case to ensure that the adjusted touch position is 
+        within the bounds of the target element and the touch area.
+        Previously, the target position snapped to the center of the target
+        element, which can be far removed from the touch area.
+
+        The second test is for non-rectilinear elements, and verifies that
+        the touch area must overlap the true bounds of the element for an
+        adjustment to occur.
+
+        * touchadjustment/big-div-expected.txt: Added.
+        * touchadjustment/big-div.html: Added.
+        * touchadjustment/rotated-node-expected.txt: Added.
+        * touchadjustment/rotated-node.html: Added.
+
 2012-07-18  Vsevolod Vlasov  <vse...@chromium.org>
 
         Unreviewed chromium gardening, unskipped tests.

Added: trunk/LayoutTests/touchadjustment/big-div-expected.txt (0 => 122970)


--- trunk/LayoutTests/touchadjustment/big-div-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/touchadjustment/big-div-expected.txt	2012-07-18 14:47:40 UTC (rev 122970)
@@ -0,0 +1,64 @@
+Test touch adjustment on a large div. The adjusted touch point should lie inside the target element and within the touch area.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+Overlapping touch above the target should snap to the top of the target element.
+PASS adjustedNode.id is element.id
+PASS adjustedPoint.x >= targetBounds.x is true
+PASS adjustedPoint.x <= targetBounds.x + targetBounds.width is true
+PASS adjustedPoint.y >= targetBounds.y is true
+PASS adjustedPoint.y <= targetBounds.y + targetBounds.height is true
+PASS adjustedPoint.x >= touchBounds.x is true
+PASS adjustedPoint.x <= touchBounds.x + touchBounds.width is true
+PASS adjustedPoint.y >= touchBounds.y is true
+PASS adjustedPoint.y <= touchBounds.y + touchBounds.height is true
+
+Overlapping touch below the target should snap to the bottom of the target element.
+PASS adjustedNode.id is element.id
+PASS adjustedPoint.x >= targetBounds.x is true
+PASS adjustedPoint.x <= targetBounds.x + targetBounds.width is true
+PASS adjustedPoint.y >= targetBounds.y is true
+PASS adjustedPoint.y <= targetBounds.y + targetBounds.height is true
+PASS adjustedPoint.x >= touchBounds.x is true
+PASS adjustedPoint.x <= touchBounds.x + touchBounds.width is true
+PASS adjustedPoint.y >= touchBounds.y is true
+PASS adjustedPoint.y <= touchBounds.y + touchBounds.height is true
+
+Overlapping touch left of the target should snap to the left side of the target element.
+PASS adjustedNode.id is element.id
+PASS adjustedPoint.x >= targetBounds.x is true
+PASS adjustedPoint.x <= targetBounds.x + targetBounds.width is true
+PASS adjustedPoint.y >= targetBounds.y is true
+PASS adjustedPoint.y <= targetBounds.y + targetBounds.height is true
+PASS adjustedPoint.x >= touchBounds.x is true
+PASS adjustedPoint.x <= touchBounds.x + touchBounds.width is true
+PASS adjustedPoint.y >= touchBounds.y is true
+PASS adjustedPoint.y <= touchBounds.y + touchBounds.height is true
+
+Overlapping touch right of the target should snap to the right side of the target element.
+PASS adjustedNode.id is element.id
+PASS adjustedPoint.x >= targetBounds.x is true
+PASS adjustedPoint.x <= targetBounds.x + targetBounds.width is true
+PASS adjustedPoint.y >= targetBounds.y is true
+PASS adjustedPoint.y <= targetBounds.y + targetBounds.height is true
+PASS adjustedPoint.x >= touchBounds.x is true
+PASS adjustedPoint.x <= touchBounds.x + touchBounds.width is true
+PASS adjustedPoint.y >= touchBounds.y is true
+PASS adjustedPoint.y <= touchBounds.y + touchBounds.height is true
+
+Test touch area contained within the target element.
+PASS adjustedNode.id is element.id
+PASS adjustedPoint.x >= targetBounds.x is true
+PASS adjustedPoint.x <= targetBounds.x + targetBounds.width is true
+PASS adjustedPoint.y >= targetBounds.y is true
+PASS adjustedPoint.y <= targetBounds.y + targetBounds.height is true
+PASS adjustedPoint.x >= touchBounds.x is true
+PASS adjustedPoint.x <= touchBounds.x + touchBounds.width is true
+PASS adjustedPoint.y >= touchBounds.y is true
+PASS adjustedPoint.y <= touchBounds.y + touchBounds.height is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/touchadjustment/big-div.html (0 => 122970)


--- trunk/LayoutTests/touchadjustment/big-div.html	                        (rev 0)
+++ trunk/LayoutTests/touchadjustment/big-div.html	2012-07-18 14:47:40 UTC (rev 122970)
@@ -0,0 +1,109 @@
+<html>
+<head>
+<style>
+    #targetDiv {
+        background: #00f;
+        height: 400px;
+        width: 400px;
+    }
+</style>
+<script src=""
+
+</head>
+
+<body _onload_="runTests()">
+
+<div id="targetDiv">
+</div>
+
+<p id='description'></p>
+<div id='console'></div>
+
+<script>
+    var element;
+    var adjustedNode;
+    var adjustedPoint;
+    var targetBounds;
+    var touchBounds;
+
+    function findAbsolutePosition(node) {
+        var pos = {left: 0, top: 0};
+        do {
+            pos.left += node.offsetLeft;
+            pos.top += node.offsetTop;
+        } while (node = node.offsetParent);
+        return pos;
+    }
+
+    function runTouchTests() {
+        element = document.getElementById("targetDiv");
+        element.addEventListener('click', function() {}, false);
+        document.addEventListener('click', function() {}, false);
+
+        var pos = findAbsolutePosition(element);
+
+        var x = pos.left;
+        var y = pos.top;
+        var width = element.clientWidth;
+        var height = element.clientHeight;
+
+        targetBounds = {
+            x: x,
+            y: y,
+            width: width,
+            height: height
+        };
+
+        var touchRadius = 10;
+        var offset = touchRadius / 2;
+        var midX = x + width / 2;
+        var midY = y + height / 2;
+
+        debug('\nOverlapping touch above the target should snap to the top of the target element.');
+        testTouch(midX, y - offset, touchRadius, targetBounds);
+        debug('\nOverlapping touch below the target should snap to the bottom of the target element.');
+        testTouch(midX, y + height + offset, touchRadius, targetBounds);
+        debug('\nOverlapping touch left of the target should snap to the left side of the target element.');
+        testTouch(x - offset, midY, touchRadius, targetBounds);
+        debug('\nOverlapping touch right of the target should snap to the right side of the target element.');
+        testTouch(x + width + offset, midY, touchRadius, targetBounds);
+        debug('\nTest touch area contained within the target element.');
+        testTouch(midX, midY, touchRadius, targetBounds);
+    }
+
+    function testTouch(touchX, touchY, radius, targetBounds) {
+
+        touchBounds = {
+            x: touchX - radius,
+            y: touchY - radius,
+            width: 2 * radius,
+            height: 2 * radius
+        };
+        adjustedNode = internals.touchNodeAdjustedToBestClickableNode(touchBounds.x, touchBounds.y, touchBounds.width, touchBounds.height, document);
+        shouldBe('adjustedNode.id', 'element.id');
+        adjustedPoint = internals.touchPositionAdjustedToBestClickableNode(touchBounds.x, touchBounds.y, touchBounds.width, touchBounds.height, document);
+
+        shouldBeTrue('adjustedPoint.x >= targetBounds.x');
+        shouldBeTrue('adjustedPoint.x <= targetBounds.x + targetBounds.width');
+        shouldBeTrue('adjustedPoint.y >= targetBounds.y');
+        shouldBeTrue('adjustedPoint.y <= targetBounds.y + targetBounds.height');
+        shouldBeTrue('adjustedPoint.x >= touchBounds.x');
+        shouldBeTrue('adjustedPoint.x <= touchBounds.x + touchBounds.width');
+        shouldBeTrue('adjustedPoint.y >= touchBounds.y');
+        shouldBeTrue('adjustedPoint.y <= touchBounds.y + touchBounds.height');
+    }
+
+    function runTests()
+    {
+        if (window.testRunner && window.internals && internals.touchNodeAdjustedToBestClickableNode) {
+            description('Test touch adjustment on a large div.  The adjusted touch point should lie inside the target element and within the touch area.');
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+            runTouchTests();
+            isSuccessfullyParsed();
+            testRunner.notifyDone();
+        }
+    }
+</script>
+</body>
+</html>

Added: trunk/LayoutTests/touchadjustment/rotated-node-expected.txt (0 => 122970)


--- trunk/LayoutTests/touchadjustment/rotated-node-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/touchadjustment/rotated-node-expected.txt	2012-07-18 14:47:40 UTC (rev 122970)
@@ -0,0 +1,26 @@
+Test touch adjustment to a non-rectilinear element.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Direct Touches
+PASS adjustedNode.id is "rotatedDiv"
+PASS adjustedNode.id is "rotatedDiv"
+PASS adjustedNode.id is "rotatedDiv"
+PASS adjustedNode.id is "rotatedDiv"
+PASS adjustedNode.id is "rotatedDiv"
+PASS adjustedNode.id is "containerDiv"
+PASS adjustedNode.id is "containerDiv"
+PASS adjustedNode.id is "containerDiv"
+
+Adjusted Touches
+PASS adjustedNode.id is "rotatedDiv"
+PASS adjustedNode.id is "rotatedDiv"
+
+Near Misses
+PASS adjustedNode.id is "containerDiv"
+PASS adjustedNode.id is "containerDiv"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/touchadjustment/rotated-node.html (0 => 122970)


--- trunk/LayoutTests/touchadjustment/rotated-node.html	                        (rev 0)
+++ trunk/LayoutTests/touchadjustment/rotated-node.html	2012-07-18 14:47:40 UTC (rev 122970)
@@ -0,0 +1,136 @@
+<html>
+<head>
+<style>
+    #containerDiv {
+        -webkit-box-sizing: border-box;
+        border: 1px solid black;
+        height: 100px;
+        position: relative;
+        width: 100px;
+    }
+    #rotatedDiv {
+      -webkit-box-sizing: border-box;
+      -webkit-transform: rotate(45deg);
+      border: 1px solid black;
+      height: 40px;
+      left: 10px;
+      position: absolute;
+      top: 10px;
+      width: 40px;
+    }
+
+    #testDiv {
+      -webkit-box-sizing: border-box;
+      border: 1px solid black;
+      height: 40px;
+      left: 50px;
+      position: absolute;
+      top: 40px;
+      width: 40px;
+    }
+
+</style>
+<script src=""
+
+</head>
+
+<body _onload_="runTests()">
+
+<div id="containerDiv">
+  <div id="rotatedDiv"></div>
+  <!-- <div id="testDiv"></div> -->
+</div>
+
+<p id='description'></p>
+<div id='console'></div>
+
+<script>
+    var adjustedNode;
+    var adjustedPoint;
+
+    function nodeAtTouch(x, y, radius)
+    {
+        var touchArea = getTouchArea(x, y, radius);
+        adjustedNode = internals.touchNodeAdjustedToBestClickableNode(touchArea.x, touchArea.y, touchArea.width, touchArea.height, document);
+    }
+
+    function getTouchArea(x, y, radius) {
+        var node = document.getElementById('containerDiv');
+        var pos = findAbsolutePosition(node);
+        return {
+            x: pos.left + x - radius,
+            y: pos.top + y - radius,
+            width: 2 * radius,
+            height: 2 * radius
+        };
+    }
+
+    function findAbsolutePosition(node) {
+        var pos = {left: 0, top: 0};
+        do {
+            pos.left += node.offsetLeft;
+            pos.top += node.offsetTop;
+        } while (node = node.offsetParent);
+        return pos;
+    }
+
+    function testDirectTouches()
+    {
+        debug('Direct Touches');
+
+        nodeAtTouch(30, 30, 20);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+        nodeAtTouch(20, 30, 20);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+        nodeAtTouch(40, 30, 20);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+        nodeAtTouch(30, 20, 20);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+        nodeAtTouch(30, 40, 20);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+
+        nodeAtTouch(80, 80, 20);
+        shouldBeEqualToString('adjustedNode.id', 'containerDiv');
+        nodeAtTouch(80, 20, 20);
+        shouldBeEqualToString('adjustedNode.id', 'containerDiv');
+        nodeAtTouch(20, 80, 20);
+        shouldBeEqualToString('adjustedNode.id', 'containerDiv');
+    }
+
+    function testAdjustedTouches()
+    {
+        debug('\nAdjusted Touches');
+
+        // Touch overlaps center of element.
+        nodeAtTouch(10, 10, 30);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+
+        // Touch overlaps corner of element.
+        nodeAtTouch(20, 0, 20);
+        shouldBeEqualToString('adjustedNode.id', 'rotatedDiv');
+
+        debug('\nNear Misses');
+
+        // Touch overlaps bounding-box of element, but not the actual bounds.
+        nodeAtTouch(70, 0, 20);
+        shouldBeEqualToString('adjustedNode.id', 'containerDiv');
+        nodeAtTouch(70, 60, 20);
+        shouldBeEqualToString('adjustedNode.id', 'containerDiv');
+    }
+
+    function runTests()
+    {
+        document.addEventListener('click', function() {}, false);
+        if (window.testRunner && window.internals && internals.touchNodeAdjustedToBestClickableNode) {
+            description('Test touch adjustment to a non-rectilinear element.');
+            testRunner.dumpAsText();
+            testRunner.waitUntilDone();
+            testDirectTouches();
+            testAdjustedTouches();
+            isSuccessfullyParsed();
+            testRunner.notifyDone();
+        }
+    }
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (122969 => 122970)


--- trunk/Source/WebCore/ChangeLog	2012-07-18 14:41:22 UTC (rev 122969)
+++ trunk/Source/WebCore/ChangeLog	2012-07-18 14:47:40 UTC (rev 122970)
@@ -1,3 +1,31 @@
+2012-07-18  Kevin Ellis  <kev...@chromium.org>
+
+        TOUCH_ADJUSTMENT is too aggressive when snapping to large elements.
+        https://bugs.webkit.org/show_bug.cgi?id=91262
+
+        Reviewed by Antonio Gomes.
+
+        Constrains the extent to which the touch point can be adjusted when
+        generating synthetic mouse events when TOUCH_ADJUSTEMNT is enabled.
+        Previously, the target position snapped to the center of the target
+        element, which can be far removed from the touch position when tapping
+        on or near a large element.  The refined strategy is to leave the
+        adjusted position unchanged if tapping within the element or to snap
+        to the center of the overlap region if the touch point lies outside the
+        bounds of the element, but the touch area and element bounds overlap.
+        For non-rectilineary bounds, a point lying outside the element boundary
+        is pulled towards the center of the element, by an amount limited by
+        the radius of the touch area.
+
+        Tests: touchadjustment/big-div.html
+               touchadjustment/rotated-node.html
+
+        * page/TouchAdjustment.cpp:
+        (WebCore::TouchAdjustment::contentsToWindow):
+        (TouchAdjustment):
+        (WebCore::TouchAdjustment::snapTo):
+        (WebCore::TouchAdjustment::findNodeWithLowestDistanceMetric):
+
 2012-07-18  Sergey Rogulenko  <rogule...@google.com>
 
         Web Inspector: adding pause icon for _javascript_ debugging

Modified: trunk/Source/WebCore/page/TouchAdjustment.cpp (122969 => 122970)


--- trunk/Source/WebCore/page/TouchAdjustment.cpp	2012-07-18 14:41:22 UTC (rev 122969)
+++ trunk/Source/WebCore/page/TouchAdjustment.cpp	2012-07-18 14:47:40 UTC (rev 122970)
@@ -239,7 +239,77 @@
     return rect.size().area() / (float)intersection.size().area();
 }
 
+FloatPoint contentsToWindow(FrameView *view, FloatPoint pt)
+{
+    int x = static_cast<int>(pt.x() + 0.5f);
+    int y = static_cast<int>(pt.y() + 0.5f);
+    IntPoint adjusted = view->contentsToWindow(IntPoint(x, y));
+    return FloatPoint(adjusted.x(), adjusted.y());
+}
 
+bool snapTo(const SubtargetGeometry& geom, const IntPoint& touchPoint, const IntRect& touchArea, IntPoint& adjustedPoint)
+{
+    FrameView* view = geom.node()->document()->view();
+    FloatQuad quad = geom.quad();
+
+    if (quad.isRectilinear()) {
+        IntRect contentBounds = geom.boundingBox();
+        // Convert from frame coordinates to window coordinates.
+        IntRect bounds = view->contentsToWindow(contentBounds);
+        if (bounds.contains(touchPoint)) {
+            adjustedPoint = touchPoint;
+            return true;
+        }
+        if (bounds.intersects(touchArea)) {
+            bounds.intersect(touchArea);
+            adjustedPoint = bounds.center();
+            return true;
+        }
+        return false;
+    }
+
+    // Non-rectilinear element.
+    // Convert quad from content to window coordinates.
+    FloatPoint p1 = contentsToWindow(view, quad.p1());
+    FloatPoint p2 = contentsToWindow(view, quad.p2());
+    FloatPoint p3 = contentsToWindow(view, quad.p3());
+    FloatPoint p4 = contentsToWindow(view, quad.p4());
+    quad = FloatQuad(p1, p2, p3, p4);
+
+    if (quad.containsPoint(touchPoint)) {
+        adjustedPoint = touchPoint;
+        return true;
+    }
+
+    // Pull point towards the center of the element.
+    float cx = 0.25 * (p1.x() + p2.x() + p3.x() + p4.x());
+    float cy = 0.25 * (p1.y() + p2.y() + p3.y() + p4.y());
+    FloatPoint center = FloatPoint(cx, cy);
+
+    FloatSize pullDirection = center - touchPoint;
+    float distanceToCenter = pullDirection.diagonalLength();
+
+    // Use distance from center to corner of touch area to limit adjustment distance.
+    float dx = 0.5f * touchArea.width();
+    float dy = 0.5f * touchArea.height();
+    float touchRadius = sqrt(dx * dx + dy * dy);
+
+    float scaleFactor = touchRadius / distanceToCenter;
+    if (scaleFactor > 1)
+        scaleFactor = 1;
+    pullDirection.scale(scaleFactor);
+
+    int x = static_cast<int>(touchPoint.x() + pullDirection.width());
+    int y = static_cast<int>(touchPoint.y() + pullDirection.height());
+    IntPoint point(x, y);
+
+    if (quad.containsPoint(point)) {
+        adjustedPoint = point;
+        return true;
+    }
+    return false;
+}
+
 // A generic function for finding the target node with the lowest distance metric. A distance metric here is the result
 // of a distance-like function, that computes how well the touch hits the node.
 // Distance functions could for instance be distance squared or area of intersection.
@@ -249,17 +319,21 @@
     float bestDistanceMetric = std::numeric_limits<float>::infinity();
     SubtargetGeometryList::const_iterator it = subtargets.begin();
     const SubtargetGeometryList::const_iterator end = subtargets.end();
+    IntPoint adjustedPoint;
     for (; it != end; ++it) {
         Node* node = it->node();
         float distanceMetric = distanceFunction(touchHotspot, touchArea, *it);
         if (distanceMetric < bestDistanceMetric) {
-            targetPoint = roundedIntPoint(it->quad().center());
-            targetArea = it->boundingBox();
-            targetNode = node;
-            bestDistanceMetric = distanceMetric;
+            if (snapTo(*it, touchHotspot, touchArea, adjustedPoint)) {
+                targetPoint = adjustedPoint;
+                targetArea = it->boundingBox();
+                targetNode = node;
+                bestDistanceMetric = distanceMetric;
+            }
         } else if (distanceMetric == bestDistanceMetric) {
             // Try to always return the inner-most element.
-            if (node->isDescendantOf(targetNode)) {
+            if (node->isDescendantOf(targetNode) && snapTo(*it, touchHotspot, touchArea, adjustedPoint)) {
+                targetPoint = adjustedPoint;
                 targetNode = node;
                 targetArea = it->boundingBox();
             }
@@ -267,9 +341,7 @@
     }
     if (targetNode) {
         targetArea = targetNode->document()->view()->contentsToWindow(targetArea);
-        targetPoint = targetNode->document()->view()->contentsToWindow(targetPoint);
     }
-
     return (targetNode);
 }
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to