1/* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "config.h" 32 33#include "TouchDisambiguation.h" 34 35#include "Document.h" 36#include "Element.h" 37#include "Frame.h" 38#include "FrameView.h" 39#include "HTMLNames.h" 40#include "HitTestResult.h" 41#include "NodeTraversal.h" 42#include "RenderBlock.h" 43#include <algorithm> 44#include <cmath> 45 46using namespace std; 47 48namespace WebCore { 49 50static IntRect boundingBoxForEventNodes(Node* eventNode) 51{ 52 if (!eventNode->document()->view()) 53 return IntRect(); 54 55 IntRect result; 56 Node* node = eventNode; 57 while (node) { 58 // Skip the whole sub-tree if the node doesn't propagate events. 59 if (node != eventNode && node->willRespondToMouseClickEvents()) { 60 node = NodeTraversal::nextSkippingChildren(node, eventNode); 61 continue; 62 } 63 result.unite(node->pixelSnappedBoundingBox()); 64 node = NodeTraversal::next(node, eventNode); 65 } 66 return eventNode->document()->view()->contentsToWindow(result); 67} 68 69static float scoreTouchTarget(IntPoint touchPoint, int padding, IntRect boundingBox) 70{ 71 if (boundingBox.isEmpty()) 72 return 0; 73 74 float reciprocalPadding = 1.f / padding; 75 float score = 1; 76 77 IntSize distance = boundingBox.differenceToPoint(touchPoint); 78 score *= max((padding - abs(distance.width())) * reciprocalPadding, 0.f); 79 score *= max((padding - abs(distance.height())) * reciprocalPadding, 0.f); 80 81 return score; 82} 83 84struct TouchTargetData { 85 IntRect windowBoundingBox; 86 float score; 87}; 88 89void findGoodTouchTargets(const IntRect& touchBox, Frame* mainFrame, Vector<IntRect>& goodTargets) 90{ 91 goodTargets.clear(); 92 93 int touchPointPadding = ceil(max(touchBox.width(), touchBox.height()) * 0.5); 94 // FIXME: Rect-based hit test doesn't transform the touch point size. 95 // We have to pre-apply frame scale factor here. 96 int padding = ceil(touchPointPadding / mainFrame->frameScaleFactor()); 97 98 IntPoint touchPoint = touchBox.center(); 99 IntPoint contentsPoint = mainFrame->view()->windowToContents(touchPoint); 100 101 HitTestResult result = mainFrame->eventHandler()->hitTestResultAtPoint(contentsPoint, HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent, IntSize(padding, padding)); 102 const ListHashSet<RefPtr<Node> >& hitResults = result.rectBasedTestResult(); 103 104 // Blacklist nodes that are container of disambiguated nodes. 105 // It is not uncommon to have a clickable <div> that contains other clickable objects. 106 // This heuristic avoids excessive disambiguation in that case. 107 HashSet<Node*> blackList; 108 for (ListHashSet<RefPtr<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { 109 // Ignore any Nodes that can't be clicked on. 110 RenderObject* renderer = it->get()->renderer(); 111 if (!renderer || !it->get()->willRespondToMouseClickEvents()) 112 continue; 113 114 // Blacklist all of the Node's containers. 115 for (RenderBlock* container = renderer->containingBlock(); container; container = container->containingBlock()) { 116 Node* containerNode = container->node(); 117 if (!containerNode) 118 continue; 119 if (!blackList.add(containerNode).isNewEntry) 120 break; 121 } 122 } 123 124 HashMap<Node*, TouchTargetData> touchTargets; 125 float bestScore = 0; 126 for (ListHashSet<RefPtr<Node> >::const_iterator it = hitResults.begin(); it != hitResults.end(); ++it) { 127 for (Node* node = it->get(); node; node = node->parentNode()) { 128 if (blackList.contains(node)) 129 continue; 130 if (node->isDocumentNode() || node->hasTagName(HTMLNames::htmlTag) || node->hasTagName(HTMLNames::bodyTag)) 131 break; 132 if (node->willRespondToMouseClickEvents()) { 133 TouchTargetData& targetData = touchTargets.add(node, TouchTargetData()).iterator->value; 134 targetData.windowBoundingBox = boundingBoxForEventNodes(node); 135 targetData.score = scoreTouchTarget(touchPoint, touchPointPadding, targetData.windowBoundingBox); 136 bestScore = max(bestScore, targetData.score); 137 break; 138 } 139 } 140 } 141 142 for (HashMap<Node*, TouchTargetData>::iterator it = touchTargets.begin(); it != touchTargets.end(); ++it) { 143 // Currently the scoring function uses the overlap area with the fat point as the score. 144 // We ignore the candidates that has less than 1/2 overlap (we consider not really ambiguous enough) than the best candidate to avoid excessive popups. 145 if (it->value.score < bestScore * 0.5) 146 continue; 147 goodTargets.append(it->value.windowBoundingBox); 148 } 149} 150 151} // namespace WebCore 152