TouchAdjustment.cpp [plain text]
#include "config.h"
#include "TouchAdjustment.h"
#include "ContainerNode.h"
#include "FloatPoint.h"
#include "FloatQuad.h"
#include "FrameView.h"
#include "HTMLLabelElement.h"
#include "HTMLNames.h"
#include "IntPoint.h"
#include "IntSize.h"
#include "Node.h"
#include "NodeRenderStyle.h"
#include "RenderBox.h"
#include "RenderObject.h"
#include "RenderStyle.h"
namespace WebCore {
namespace TouchAdjustment {
class SubtargetGeometry {
public:
SubtargetGeometry(Node* node, const FloatQuad& quad)
: m_node(node)
, m_quad(quad)
{ }
Node* node() const { return m_node; }
FloatQuad quad() const { return m_quad; }
IntRect boundingBox() const { return m_quad.enclosingBoundingBox(); }
private:
Node* m_node;
FloatQuad m_quad;
};
typedef Vector<SubtargetGeometry> SubtargetGeometryList;
typedef bool (*NodeFilter)(Node*);
typedef float (*DistanceFunction)(const IntPoint&, const IntRect&, const SubtargetGeometry&);
bool nodeRespondsToTapGesture(Node* node)
{
if (node->isLink()
|| node->isContentEditable()
|| node->isMouseFocusable())
return true;
if (node->isElementNode()) {
Element* element = static_cast<Element*>(node);
if (element->hasTagName(HTMLNames::labelTag) && static_cast<HTMLLabelElement*>(element)->control())
return true;
}
if (node->hasEventListeners()
&& (node->hasEventListeners(eventNames().clickEvent)
|| node->hasEventListeners(eventNames().DOMActivateEvent)
|| node->hasEventListeners(eventNames().mousedownEvent)
|| node->hasEventListeners(eventNames().mouseupEvent)
|| node->hasEventListeners(eventNames().mousemoveEvent)
))
return true;
if (node->renderStyle()) {
if (node->renderStyle()->affectedByActiveRules() || node->renderStyle()->affectedByHoverRules())
return true;
}
return false;
}
bool nodeIsZoomTarget(Node* node)
{
if (node->isTextNode() || node->isShadowRoot())
return false;
ASSERT(node->renderer());
return node->renderer()->isBox();
}
static inline void appendSubtargetsForNodeToList(Node* node, SubtargetGeometryList& subtargets)
{
ASSERT(node->renderer());
Vector<FloatQuad> quads;
node->renderer()->absoluteQuads(quads);
Vector<FloatQuad>::const_iterator it = quads.begin();
const Vector<FloatQuad>::const_iterator end = quads.end();
for (; it != end; ++it)
subtargets.append(SubtargetGeometry(node, *it));
}
static inline void appendZoomableSubtargets(Node* node, SubtargetGeometryList& subtargets)
{
RenderBox* renderer = toRenderBox(node->renderer());
ASSERT(renderer);
Vector<FloatQuad> quads;
FloatRect borderBoxRect = renderer->borderBoxRect();
FloatRect contentBoxRect = renderer->contentBoxRect();
quads.append(renderer->localToAbsoluteQuad(borderBoxRect));
if (borderBoxRect != contentBoxRect)
quads.append(renderer->localToAbsoluteQuad(contentBoxRect));
Vector<FloatQuad>::const_iterator it = quads.begin();
const Vector<FloatQuad>::const_iterator end = quads.end();
for (; it != end; ++it)
subtargets.append(SubtargetGeometry(node, *it));
}
void compileSubtargetList(const NodeList& intersectedNodes, SubtargetGeometryList& subtargets, NodeFilter nodeFilter)
{
HashMap<Node*, Node*> responderMap;
HashSet<Node*> ancestorsToRespondersSet;
Vector<Node*> candidates;
unsigned length = intersectedNodes.length();
for (unsigned i = 0; i < length; ++i) {
Node* const node = intersectedNodes.item(i);
Vector<Node*> visitedNodes;
Node* respondingNode = 0;
for (Node* visitedNode = node; visitedNode; visitedNode = visitedNode->parentOrHostNode()) {
respondingNode = responderMap.get(visitedNode);
if (respondingNode)
break;
visitedNodes.append(visitedNode);
if (nodeFilter(visitedNode)) {
respondingNode = visitedNode;
for (visitedNode = visitedNode->parentOrHostNode(); visitedNode; visitedNode = visitedNode->parentOrHostNode()) {
HashSet<Node*>::AddResult addResult = ancestorsToRespondersSet.add(visitedNode);
if (!addResult.isNewEntry)
break;
}
break;
}
}
for (unsigned j = 0; j < visitedNodes.size(); j++)
responderMap.add(visitedNodes[j], respondingNode);
if (respondingNode)
candidates.append(node);
}
length = candidates.size();
for (unsigned i = 0; i < length; i++) {
Node* candidate = candidates[i];
Node* respondingNode = responderMap.get(candidate);
ASSERT(respondingNode);
if (ancestorsToRespondersSet.contains(respondingNode))
continue;
appendSubtargetsForNodeToList(candidate, subtargets);
}
}
void compileZoomableSubtargets(const NodeList& intersectedNodes, SubtargetGeometryList& subtargets)
{
unsigned length = intersectedNodes.length();
for (unsigned i = 0; i < length; ++i) {
Node* const candidate = intersectedNodes.item(i);
if (nodeIsZoomTarget(candidate))
appendZoomableSubtargets(candidate, subtargets);
}
}
float distanceSquaredToTargetCenterLine(const IntPoint& touchHotspot, const IntRect& touchArea, const SubtargetGeometry& subtarget)
{
UNUSED_PARAM(touchArea);
IntRect rect = subtarget.boundingBox();
ASSERT(subtarget.node()->document());
ASSERT(subtarget.node()->document()->view());
rect = subtarget.node()->document()->view()->contentsToWindow(rect);
return rect.distanceSquaredFromCenterLineToPoint(touchHotspot);
}
float zoomableIntersectionQuotient(const IntPoint& touchHotspot, const IntRect& touchArea, const SubtargetGeometry& subtarget)
{
IntRect rect = subtarget.boundingBox();
rect = subtarget.node()->document()->view()->contentsToWindow(rect);
if (!rect.contains(touchHotspot))
return INFINITY;
IntRect intersection = rect;
intersection.intersect(touchArea);
return rect.size().area() / (float)intersection.size().area();
}
bool findNodeWithLowestDistanceMetric(Node*& targetNode, IntPoint& targetPoint, IntRect& targetArea, const IntPoint& touchHotspot, const IntRect& touchArea, SubtargetGeometryList& subtargets, DistanceFunction distanceFunction)
{
targetNode = 0;
float bestDistanceMetric = INFINITY;
SubtargetGeometryList::const_iterator it = subtargets.begin();
const SubtargetGeometryList::const_iterator end = subtargets.end();
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;
} else if (distanceMetric == bestDistanceMetric) {
if (node->isDescendantOf(targetNode)) {
targetNode = node;
targetArea = it->boundingBox();
}
}
}
return (targetNode);
}
}
bool findBestClickableCandidate(Node*& targetNode, IntPoint &targetPoint, const IntPoint &touchHotspot, const IntRect &touchArea, const NodeList& nodeList)
{
IntRect targetArea;
TouchAdjustment::SubtargetGeometryList subtargets;
TouchAdjustment::compileSubtargetList(nodeList, subtargets, TouchAdjustment::nodeRespondsToTapGesture);
return TouchAdjustment::findNodeWithLowestDistanceMetric(targetNode, targetPoint, targetArea, touchHotspot, touchArea, subtargets, TouchAdjustment::distanceSquaredToTargetCenterLine);
}
bool findBestZoomableArea(Node*& targetNode, IntRect& targetArea, const IntPoint& touchHotspot, const IntRect& touchArea, const NodeList& nodeList)
{
IntPoint targetPoint;
TouchAdjustment::SubtargetGeometryList subtargets;
TouchAdjustment::compileZoomableSubtargets(nodeList, subtargets);
return TouchAdjustment::findNodeWithLowestDistanceMetric(targetNode, targetPoint, targetArea, touchHotspot, touchArea, subtargets, TouchAdjustment::zoomableIntersectionQuotient);
}
}