This source file includes following definitions.
- isOffscreenAfterScrolling
- isSpatialNavigationEnabled
- alignmentForRects
- isHorizontalMove
- start
- middle
- end
- areRectsFullyAligned
- areRectsPartiallyAligned
- areRectsMoreThanFullScreenApart
- below
- rightOf
- isRectInDirection
- hasOffscreenRect
- scrollInDirection
- scrollInDirection
- deflateIfOverlapped
- isScrollableNode
- scrollableEnclosingBoxOrParentFrameForNodeInDirection
- canScrollInDirection
- canScrollInDirection
- rectToAbsoluteCoordinates
- nodeRectInAbsoluteCoordinates
- frameRectInAbsoluteCoordinates
- entryAndExitPointsForDirection
- areElementsOnSameLine
- distanceDataForNode
- canBeScrolledIntoView
- virtualRectForDirection
- virtualRectForAreaElementAndDirection
- frameOwnerElement
#include "config.h"
#include "core/page/SpatialNavigation.h"
#include "HTMLNames.h"
#include "core/frame/FrameView.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLAreaElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/page/FrameTree.h"
#include "core/page/Page.h"
#include "core/rendering/RenderLayer.h"
#include "platform/geometry/IntRect.h"
namespace WebCore {
using namespace HTMLNames;
static RectsAlignment alignmentForRects(FocusType, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize);
static bool areRectsFullyAligned(FocusType, const LayoutRect&, const LayoutRect&);
static bool areRectsPartiallyAligned(FocusType, const LayoutRect&, const LayoutRect&);
static bool areRectsMoreThanFullScreenApart(FocusType, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize);
static bool isRectInDirection(FocusType, const LayoutRect&, const LayoutRect&);
static void deflateIfOverlapped(LayoutRect&, LayoutRect&);
static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect&);
static void entryAndExitPointsForDirection(FocusType, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint);
static bool isScrollableNode(const Node*);
FocusCandidate::FocusCandidate(Node* node, FocusType type)
: visibleNode(0)
, focusableNode(0)
, enclosingScrollableBox(0)
, distance(maxDistance())
, parentDistance(maxDistance())
, alignment(None)
, parentAlignment(None)
, isOffscreen(true)
, isOffscreenAfterScrolling(true)
{
ASSERT(node);
ASSERT(node->isElementNode());
if (isHTMLAreaElement(*node)) {
HTMLAreaElement& area = toHTMLAreaElement(*node);
HTMLImageElement* image = area.imageElement();
if (!image || !image->renderer())
return;
visibleNode = image;
rect = virtualRectForAreaElementAndDirection(area, type);
} else {
if (!node->renderer())
return;
visibleNode = node;
rect = nodeRectInAbsoluteCoordinates(node, true );
}
focusableNode = node;
isOffscreen = hasOffscreenRect(visibleNode);
isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, type);
}
bool isSpatialNavigationEnabled(const LocalFrame* frame)
{
return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled());
}
static RectsAlignment alignmentForRects(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
{
if (areRectsMoreThanFullScreenApart(type, curRect, targetRect, viewSize))
return None;
if (areRectsFullyAligned(type, curRect, targetRect))
return Full;
if (areRectsPartiallyAligned(type, curRect, targetRect))
return Partial;
return None;
}
static inline bool isHorizontalMove(FocusType type)
{
return type == FocusTypeLeft || type == FocusTypeRight;
}
static inline LayoutUnit start(FocusType type, const LayoutRect& rect)
{
return isHorizontalMove(type) ? rect.y() : rect.x();
}
static inline LayoutUnit middle(FocusType type, const LayoutRect& rect)
{
LayoutPoint center(rect.center());
return isHorizontalMove(type) ? center.y(): center.x();
}
static inline LayoutUnit end(FocusType type, const LayoutRect& rect)
{
return isHorizontalMove(type) ? rect.maxY() : rect.maxX();
}
static bool areRectsFullyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b)
{
LayoutUnit aStart, bStart, aEnd, bEnd;
switch (type) {
case FocusTypeLeft:
aStart = a.x();
bEnd = b.maxX();
break;
case FocusTypeRight:
aStart = b.x();
bEnd = a.maxX();
break;
case FocusTypeUp:
aStart = a.y();
bEnd = b.y();
break;
case FocusTypeDown:
aStart = b.y();
bEnd = a.y();
break;
default:
ASSERT_NOT_REACHED();
return false;
}
if (aStart < bEnd)
return false;
aStart = start(type, a);
bStart = start(type, b);
LayoutUnit aMiddle = middle(type, a);
LayoutUnit bMiddle = middle(type, b);
aEnd = end(type, a);
bEnd = end(type, b);
return ((bMiddle >= aStart && bMiddle <= aEnd)
|| (aMiddle >= bStart && aMiddle <= bEnd)
|| (bStart == aStart)
|| (bEnd == aEnd));
}
static bool areRectsPartiallyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b)
{
LayoutUnit aStart = start(type, a);
LayoutUnit bStart = start(type, b);
LayoutUnit bMiddle = middle(type, b);
LayoutUnit aEnd = end(type, a);
LayoutUnit bEnd = end(type, b);
return ((bStart >= aStart && bStart <= aEnd)
|| (bEnd >= aStart && bEnd <= aEnd)
|| (bMiddle >= aStart && bMiddle <= aEnd));
}
static bool areRectsMoreThanFullScreenApart(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
{
ASSERT(isRectInDirection(type, curRect, targetRect));
switch (type) {
case FocusTypeLeft:
return curRect.x() - targetRect.maxX() > viewSize.width();
case FocusTypeRight:
return targetRect.x() - curRect.maxX() > viewSize.width();
case FocusTypeUp:
return curRect.y() - targetRect.maxY() > viewSize.height();
case FocusTypeDown:
return targetRect.y() - curRect.maxY() > viewSize.height();
default:
ASSERT_NOT_REACHED();
return true;
}
}
static inline bool below(const LayoutRect& a, const LayoutRect& b)
{
return a.y() > b.maxY();
}
static inline bool rightOf(const LayoutRect& a, const LayoutRect& b)
{
return a.x() > b.maxX();
}
static bool isRectInDirection(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect)
{
switch (type) {
case FocusTypeLeft:
return targetRect.maxX() <= curRect.x();
case FocusTypeRight:
return targetRect.x() >= curRect.maxX();
case FocusTypeUp:
return targetRect.maxY() <= curRect.y();
case FocusTypeDown:
return targetRect.y() >= curRect.maxY();
default:
ASSERT_NOT_REACHED();
return false;
}
}
bool hasOffscreenRect(Node* node, FocusType type)
{
FrameView* frameView = node->document().view();
if (!frameView)
return true;
ASSERT(!frameView->needsLayout());
LayoutRect containerViewportRect = frameView->visibleContentRect();
switch (type) {
case FocusTypeLeft:
containerViewportRect.setX(containerViewportRect.x() - ScrollableArea::pixelsPerLineStep());
containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep());
break;
case FocusTypeRight:
containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep());
break;
case FocusTypeUp:
containerViewportRect.setY(containerViewportRect.y() - ScrollableArea::pixelsPerLineStep());
containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep());
break;
case FocusTypeDown:
containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep());
break;
default:
break;
}
RenderObject* render = node->renderer();
if (!render)
return true;
LayoutRect rect(render->absoluteClippedOverflowRect());
if (rect.isEmpty())
return true;
return !containerViewportRect.intersects(rect);
}
bool scrollInDirection(LocalFrame* frame, FocusType type)
{
ASSERT(frame);
if (frame && canScrollInDirection(frame->document(), type)) {
LayoutUnit dx = 0;
LayoutUnit dy = 0;
switch (type) {
case FocusTypeLeft:
dx = - ScrollableArea::pixelsPerLineStep();
break;
case FocusTypeRight:
dx = ScrollableArea::pixelsPerLineStep();
break;
case FocusTypeUp:
dy = - ScrollableArea::pixelsPerLineStep();
break;
case FocusTypeDown:
dy = ScrollableArea::pixelsPerLineStep();
break;
default:
ASSERT_NOT_REACHED();
return false;
}
frame->view()->scrollBy(IntSize(dx, dy));
return true;
}
return false;
}
bool scrollInDirection(Node* container, FocusType type)
{
ASSERT(container);
if (container->isDocumentNode())
return scrollInDirection(toDocument(container)->frame(), type);
if (!container->renderBox())
return false;
if (canScrollInDirection(container, type)) {
LayoutUnit dx = 0;
LayoutUnit dy = 0;
switch (type) {
case FocusTypeLeft:
dx = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollLeft());
break;
case FocusTypeRight:
ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
dx = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
break;
case FocusTypeUp:
dy = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollTop());
break;
case FocusTypeDown:
ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
dy = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
break;
default:
ASSERT_NOT_REACHED();
return false;
}
container->renderBox()->scrollByRecursively(IntSize(dx, dy));
return true;
}
return false;
}
static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b)
{
if (!a.intersects(b) || a.contains(b) || b.contains(a))
return;
LayoutUnit deflateFactor = -fudgeFactor();
if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0))
a.inflate(deflateFactor);
if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0))
b.inflate(deflateFactor);
}
bool isScrollableNode(const Node* node)
{
ASSERT(!node->isDocumentNode());
if (!node)
return false;
if (RenderObject* renderer = node->renderer())
return renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea() && node->hasChildren();
return false;
}
Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusType type, Node* node)
{
ASSERT(node);
Node* parent = node;
do {
if (parent->isDocumentNode())
parent = toDocument(parent)->frame()->ownerElement();
else
parent = parent->parentOrShadowHostNode();
} while (parent && !canScrollInDirection(parent, type) && !parent->isDocumentNode());
return parent;
}
bool canScrollInDirection(const Node* container, FocusType type)
{
ASSERT(container);
if (container->isDocumentNode())
return canScrollInDirection(toDocument(container)->frame(), type);
if (!isScrollableNode(container))
return false;
switch (type) {
case FocusTypeLeft:
return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
case FocusTypeUp:
return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
case FocusTypeRight:
return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
case FocusTypeDown:
return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
default:
ASSERT_NOT_REACHED();
return false;
}
}
bool canScrollInDirection(const LocalFrame* frame, FocusType type)
{
if (!frame->view())
return false;
ScrollbarMode verticalMode;
ScrollbarMode horizontalMode;
frame->view()->calculateScrollbarModesForLayoutAndSetViewportRenderer(horizontalMode, verticalMode);
if ((type == FocusTypeLeft || type == FocusTypeRight) && ScrollbarAlwaysOff == horizontalMode)
return false;
if ((type == FocusTypeUp || type == FocusTypeDown) && ScrollbarAlwaysOff == verticalMode)
return false;
LayoutSize size = frame->view()->contentsSize();
LayoutSize offset = frame->view()->scrollOffset();
LayoutRect rect = frame->view()->visibleContentRect(IncludeScrollbars);
switch (type) {
case FocusTypeLeft:
return offset.width() > 0;
case FocusTypeUp:
return offset.height() > 0;
case FocusTypeRight:
return rect.width() + offset.width() < size.width();
case FocusTypeDown:
return rect.height() + offset.height() < size.height();
default:
ASSERT_NOT_REACHED();
return false;
}
}
static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect& initialRect)
{
LayoutRect rect = initialRect;
for (LocalFrame* frame = initialFrame; frame; frame = frame->tree().parent()) {
if (Element* element = frame->ownerElement()) {
do {
rect.move(element->offsetLeft(), element->offsetTop());
} while ((element = element->offsetParent()));
rect.move((-frame->view()->scrollOffset()));
}
}
return rect;
}
LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
{
ASSERT(node && node->renderer() && !node->document().view()->needsLayout());
if (node->isDocumentNode())
return frameRectInAbsoluteCoordinates(toDocument(node)->frame());
LayoutRect rect = rectToAbsoluteCoordinates(node->document().frame(), node->boundingBox());
if (ignoreBorder) {
rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth());
rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth());
rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth());
}
return rect;
}
LayoutRect frameRectInAbsoluteCoordinates(LocalFrame* frame)
{
return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
}
void entryAndExitPointsForDirection(FocusType type, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint)
{
switch (type) {
case FocusTypeLeft:
exitPoint.setX(startingRect.x());
entryPoint.setX(potentialRect.maxX());
break;
case FocusTypeUp:
exitPoint.setY(startingRect.y());
entryPoint.setY(potentialRect.maxY());
break;
case FocusTypeRight:
exitPoint.setX(startingRect.maxX());
entryPoint.setX(potentialRect.x());
break;
case FocusTypeDown:
exitPoint.setY(startingRect.maxY());
entryPoint.setY(potentialRect.y());
break;
default:
ASSERT_NOT_REACHED();
}
switch (type) {
case FocusTypeLeft:
case FocusTypeRight:
if (below(startingRect, potentialRect)) {
exitPoint.setY(startingRect.y());
entryPoint.setY(potentialRect.maxY());
} else if (below(potentialRect, startingRect)) {
exitPoint.setY(startingRect.maxY());
entryPoint.setY(potentialRect.y());
} else {
exitPoint.setY(max(startingRect.y(), potentialRect.y()));
entryPoint.setY(exitPoint.y());
}
break;
case FocusTypeUp:
case FocusTypeDown:
if (rightOf(startingRect, potentialRect)) {
exitPoint.setX(startingRect.x());
entryPoint.setX(potentialRect.maxX());
} else if (rightOf(potentialRect, startingRect)) {
exitPoint.setX(startingRect.maxX());
entryPoint.setX(potentialRect.x());
} else {
exitPoint.setX(max(startingRect.x(), potentialRect.x()));
entryPoint.setX(exitPoint.x());
}
break;
default:
ASSERT_NOT_REACHED();
}
}
bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate)
{
if (firstCandidate.isNull() || secondCandidate.isNull())
return false;
if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer())
return false;
if (!firstCandidate.rect.intersects(secondCandidate.rect))
return false;
if (isHTMLAreaElement(*firstCandidate.focusableNode) || isHTMLAreaElement(*secondCandidate.focusableNode))
return false;
if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline())
return false;
if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock())
return false;
return true;
}
void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCandidate& candidate)
{
if (areElementsOnSameLine(current, candidate)) {
if ((type == FocusTypeUp && current.rect.y() > candidate.rect.y()) || (type == FocusTypeDown && candidate.rect.y() > current.rect.y())) {
candidate.distance = 0;
candidate.alignment = Full;
return;
}
}
LayoutRect nodeRect = candidate.rect;
LayoutRect currentRect = current.rect;
deflateIfOverlapped(currentRect, nodeRect);
if (!isRectInDirection(type, currentRect, nodeRect))
return;
LayoutPoint exitPoint;
LayoutPoint entryPoint;
LayoutUnit sameAxisDistance = 0;
LayoutUnit otherAxisDistance = 0;
entryAndExitPointsForDirection(type, currentRect, nodeRect, exitPoint, entryPoint);
switch (type) {
case FocusTypeLeft:
sameAxisDistance = exitPoint.x() - entryPoint.x();
otherAxisDistance = absoluteValue(exitPoint.y() - entryPoint.y());
break;
case FocusTypeUp:
sameAxisDistance = exitPoint.y() - entryPoint.y();
otherAxisDistance = absoluteValue(exitPoint.x() - entryPoint.x());
break;
case FocusTypeRight:
sameAxisDistance = entryPoint.x() - exitPoint.x();
otherAxisDistance = absoluteValue(entryPoint.y() - exitPoint.y());
break;
case FocusTypeDown:
sameAxisDistance = entryPoint.y() - exitPoint.y();
otherAxisDistance = absoluteValue(entryPoint.x() - exitPoint.x());
break;
default:
ASSERT_NOT_REACHED();
return;
}
float x = ((entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x())).toFloat();
float y = ((entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y())).toFloat();
float euclidianDistance = sqrt(x + y);
float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance;
candidate.distance = roundf(distance);
LayoutSize viewSize = candidate.visibleNode->document().page()->mainFrame()->view()->visibleContentRect().size();
candidate.alignment = alignmentForRects(type, currentRect, nodeRect, viewSize);
}
bool canBeScrolledIntoView(FocusType type, const FocusCandidate& candidate)
{
ASSERT(candidate.visibleNode && candidate.isOffscreen);
LayoutRect candidateRect = candidate.rect;
for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) {
LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
if (!candidateRect.intersects(parentRect)) {
if (((type == FocusTypeLeft || type == FocusTypeRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
|| ((type == FocusTypeUp || type == FocusTypeDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
return false;
}
if (parentNode == candidate.enclosingScrollableBox)
return canScrollInDirection(parentNode, type);
}
return true;
}
LayoutRect virtualRectForDirection(FocusType type, const LayoutRect& startingRect, LayoutUnit width)
{
LayoutRect virtualStartingRect = startingRect;
switch (type) {
case FocusTypeLeft:
virtualStartingRect.setX(virtualStartingRect.maxX() - width);
virtualStartingRect.setWidth(width);
break;
case FocusTypeUp:
virtualStartingRect.setY(virtualStartingRect.maxY() - width);
virtualStartingRect.setHeight(width);
break;
case FocusTypeRight:
virtualStartingRect.setWidth(width);
break;
case FocusTypeDown:
virtualStartingRect.setHeight(width);
break;
default:
ASSERT_NOT_REACHED();
}
return virtualStartingRect;
}
LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement& area, FocusType type)
{
ASSERT(area.imageElement());
LayoutRect rect = virtualRectForDirection(type, rectToAbsoluteCoordinates(area.document().frame(), area.computeRect(area.imageElement()->renderer())), 1);
return rect;
}
HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate)
{
return candidate.isFrameOwnerElement() ? toHTMLFrameOwnerElement(candidate.visibleNode) : 0;
};
}