blob: d7b62e8718591ce2b96bf0e8b97de4e49a4b6d35 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/page/TouchDisambiguation.h"
#include "core/HTMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/dom/NodeTraversal.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLHtmlElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutBlock.h"
#include <algorithm>
#include <cmath>
namespace blink {
static IntRect boundingBoxForEventNodes(Node* eventNode) {
if (!eventNode->document().view())
return IntRect();
IntRect result;
Node* node = eventNode;
while (node) {
// Skip the whole sub-tree if the node doesn't propagate events.
if (node != eventNode && node->willRespondToMouseClickEvents()) {
node = NodeTraversal::nextSkippingChildren(*node, eventNode);
continue;
}
result.unite(node->pixelSnappedBoundingBox());
node = NodeTraversal::next(*node, eventNode);
}
return eventNode->document().view()->contentsToRootFrame(result);
}
static float scoreTouchTarget(IntPoint touchPoint,
int padding,
IntRect boundingBox) {
if (boundingBox.isEmpty())
return 0;
float reciprocalPadding = 1.f / padding;
float score = 1;
IntSize distance = boundingBox.differenceToPoint(touchPoint);
score *= std::max((padding - abs(distance.width())) * reciprocalPadding, 0.f);
score *=
std::max((padding - abs(distance.height())) * reciprocalPadding, 0.f);
return score;
}
struct TouchTargetData {
IntRect windowBoundingBox;
float score;
};
void findGoodTouchTargets(const IntRect& touchBoxInRootFrame,
LocalFrame* mainFrame,
Vector<IntRect>& goodTargets,
HeapVector<Member<Node>>& highlightNodes) {
goodTargets.clear();
int touchPointPadding =
ceil(std::max(touchBoxInRootFrame.width(), touchBoxInRootFrame.height()) *
0.5);
IntPoint touchPoint = touchBoxInRootFrame.center();
IntPoint contentsPoint = mainFrame->view()->rootFrameToContents(touchPoint);
HitTestResult result = mainFrame->eventHandler().hitTestResultAtPoint(
contentsPoint, HitTestRequest::ReadOnly | HitTestRequest::Active |
HitTestRequest::ListBased,
LayoutSize(touchPointPadding, touchPointPadding));
const HeapListHashSet<Member<Node>>& hitResults =
result.listBasedTestResult();
// Blacklist nodes that are container of disambiguated nodes.
// It is not uncommon to have a clickable <div> that contains other clickable
// objects. This heuristic avoids excessive disambiguation in that case.
HeapHashSet<Member<Node>> blackList;
for (const auto& hitResult : hitResults) {
// Ignore any Nodes that can't be clicked on.
LayoutObject* layoutObject = hitResult.get()->layoutObject();
if (!layoutObject || !hitResult.get()->willRespondToMouseClickEvents())
continue;
// Blacklist all of the Node's containers.
for (LayoutBlock* container = layoutObject->containingBlock(); container;
container = container->containingBlock()) {
Node* containerNode = container->node();
if (!containerNode)
continue;
if (!blackList.add(containerNode).isNewEntry)
break;
}
}
HeapHashMap<Member<Node>, TouchTargetData> touchTargets;
float bestScore = 0;
for (const auto& hitResult : hitResults) {
if (!hitResult)
continue;
for (Node& node : NodeTraversal::inclusiveAncestorsOf(*hitResult)) {
if (blackList.contains(&node))
continue;
if (node.isDocumentNode() || isHTMLHtmlElement(node) ||
isHTMLBodyElement(node))
break;
if (node.willRespondToMouseClickEvents()) {
TouchTargetData& targetData =
touchTargets.add(&node, TouchTargetData()).storedValue->value;
targetData.windowBoundingBox = boundingBoxForEventNodes(&node);
targetData.score = scoreTouchTarget(touchPoint, touchPointPadding,
targetData.windowBoundingBox);
bestScore = std::max(bestScore, targetData.score);
break;
}
}
}
for (const auto& touchTarget : touchTargets) {
// Currently the scoring function uses the overlap area with the fat point
// as the score. 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.
if (touchTarget.value.score < bestScore * 0.5)
continue;
goodTargets.append(touchTarget.value.windowBoundingBox);
highlightNodes.append(touchTarget.key);
}
}
} // namespace blink