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);
}