summaryrefslogtreecommitdiffstats
path: root/WebCore/page/SpatialNavigation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/page/SpatialNavigation.cpp')
-rw-r--r--WebCore/page/SpatialNavigation.cpp658
1 files changed, 380 insertions, 278 deletions
diff --git a/WebCore/page/SpatialNavigation.cpp b/WebCore/page/SpatialNavigation.cpp
index a42435f..ffc65b4 100644
--- a/WebCore/page/SpatialNavigation.cpp
+++ b/WebCore/page/SpatialNavigation.cpp
@@ -41,103 +41,40 @@
namespace WebCore {
-static long long spatialDistance(FocusDirection, const IntRect&, const IntRect&);
-static IntRect renderRectRelativeToRootDocument(RenderObject*);
-static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&);
+static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&, const IntSize& viewSize);
static bool areRectsFullyAligned(FocusDirection, const IntRect&, const IntRect&);
static bool areRectsPartiallyAligned(FocusDirection, const IntRect&, const IntRect&);
+static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize);
static bool isRectInDirection(FocusDirection, const IntRect&, const IntRect&);
static void deflateIfOverlapped(IntRect&, IntRect&);
-static bool checkNegativeCoordsForNode(Node*, const IntRect&);
-
-bool isSpatialNavigationEnabled(const Frame* frame)
+static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& rect);
+static void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint);
+
+
+FocusCandidate::FocusCandidate(Node* n, FocusDirection direction)
+ : node(n)
+ , enclosingScrollableBox(0)
+ , distance(maxDistance())
+ , parentDistance(maxDistance())
+ , alignment(None)
+ , parentAlignment(None)
+ , rect(nodeRectInAbsoluteCoordinates(n, true /* ignore border */))
+ , isOffscreen(hasOffscreenRect(n))
+ , isOffscreenAfterScrolling(hasOffscreenRect(n, direction))
{
- return (frame && frame->settings() && frame->settings()->isSpatialNavigationEnabled());
}
-void distanceDataForNode(FocusDirection direction, Node* start, FocusCandidate& candidate)
+bool isSpatialNavigationEnabled(const Frame* frame)
{
- RenderObject* startRender = start->renderer();
- if (!startRender) {
- candidate.distance = maxDistance();
- return;
- }
-
- RenderObject* destRender = candidate.node->renderer();
- if (!destRender) {
- candidate.distance = maxDistance();
- return;
- }
-
- IntRect curRect = renderRectRelativeToRootDocument(startRender);
- IntRect targetRect = renderRectRelativeToRootDocument(destRender);
-
- // The bounding rectangle of two consecutive nodes can overlap. In such cases,
- // deflate both.
- deflateIfOverlapped(curRect, targetRect);
-
- // If empty rects or negative width or height, bail out.
- if (curRect.isEmpty() || targetRect.isEmpty()
- || targetRect.width() <= 0 || targetRect.height() <= 0) {
- candidate.distance = maxDistance();
- return;
- }
-
- // Negative coordinates can be used if node is scrolled up offscreen.
- if (!checkNegativeCoordsForNode(start, curRect)) {
- candidate.distance = maxDistance();
- return;
- }
-
- if (!checkNegativeCoordsForNode(candidate.node, targetRect)) {
- candidate.distance = maxDistance();
- return;
- }
-
- if (!isRectInDirection(direction, curRect, targetRect)) {
- candidate.distance = maxDistance();
- return;
- }
-
- // The distance between two nodes is not to be considered alone when evaluating/looking
- // for the best focus candidate node. Alignment of rects can be also a good point to be
- // considered in order to make the algorithm to behavior in a more intuitive way.
- candidate.alignment = alignmentForRects(direction, curRect, targetRect);
- candidate.distance = spatialDistance(direction, curRect, targetRect);
+ return (frame && frame->settings() && frame->settings()->isSpatialNavigationEnabled());
}
-// FIXME: This function does not behave correctly with transformed frames.
-static IntRect renderRectRelativeToRootDocument(RenderObject* render)
+static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
{
- ASSERT(render && render->node());
-
- IntRect rect = render->node()->getRect();
-
- // In cases when the |render|'s associated node is in a scrollable inner
- // document, we only consider its scrollOffset if it is not offscreen.
- Node* node = render->node();
- Document* mainDocument = node->document()->page()->mainFrame()->document();
- bool considerScrollOffset = !(hasOffscreenRect(node) && node->document() != mainDocument);
-
- if (considerScrollOffset) {
- if (FrameView* frameView = render->node()->document()->view())
- rect.move(-frameView->scrollOffset());
- }
+ // If we found a node in full alignment, but it is too far away, ignore it.
+ if (areRectsMoreThanFullScreenApart(direction, curRect, targetRect, viewSize))
+ return None;
- // Handle nested frames.
- for (Frame* frame = render->document()->frame(); frame; frame = frame->tree()->parent()) {
- if (Element* element = static_cast<Element*>(frame->ownerElement())) {
- do {
- rect.move(element->offsetLeft(), element->offsetTop());
- } while ((element = element->offsetParent()));
- }
- }
-
- return rect;
-}
-
-static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
-{
if (areRectsFullyAligned(direction, curRect, targetRect))
return Full;
@@ -277,6 +214,25 @@ static bool areRectsPartiallyAligned(FocusDirection direction, const IntRect& a,
|| (bEnd >= aStart && bEnd <= aEnd));
}
+static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
+{
+ ASSERT(isRectInDirection(direction, curRect, targetRect));
+
+ switch (direction) {
+ case FocusDirectionLeft:
+ return curRect.x() - targetRect.right() > viewSize.width();
+ case FocusDirectionRight:
+ return targetRect.x() - curRect.right() > viewSize.width();
+ case FocusDirectionUp:
+ return curRect.y() - targetRect.bottom() > viewSize.height();
+ case FocusDirectionDown:
+ return targetRect.y() - curRect.bottom() > viewSize.height();
+ default:
+ ASSERT_NOT_REACHED();
+ return true;
+ }
+}
+
// Return true if rect |a| is below |b|. False otherwise.
static inline bool below(const IntRect& a, const IntRect& b)
{
@@ -289,143 +245,27 @@ static inline bool rightOf(const IntRect& a, const IntRect& b)
return a.x() > b.right();
}
-// * a = Current focused node's rect.
-// * b = Focus candidate node's rect.
-static long long spatialDistance(FocusDirection direction, const IntRect& a, const IntRect& b)
-{
- int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
-
- if (direction == FocusDirectionLeft) {
- // #1 |--|
- //
- // #2 |--| |--|
- //
- // #3 |--|
-
- x1 = a.x();
- x2 = b.right();
-
- if (below(a, b)) {
- // #1 The a rect is below b.
- y1 = a.y();
- y2 = b.bottom();
- } else if (below(b, a)) {
- // #3 The b rect is below a.
- y1 = a.bottom();
- y2 = b.y();
- } else {
- // #2 Both b and a share some common y's.
- y1 = 0;
- y2 = 0;
- }
- } else if (direction == FocusDirectionRight) {
- // |--| #1
- //
- // |--| |--| #2
- //
- // |--| #3
-
- x1 = a.right();
- x2 = b.x();
-
- if (below(a, b)) {
- // #1 The b rect is above a.
- y1 = a.y();
- y2 = b.bottom();
- } else if (below(b, a)) {
- // #3 The b rect is below a.
- y1 = a.bottom();
- y2 = b.y();
- } else {
- // #2 Both b and a share some common y's.
- y1 = 0;
- y2 = 0;
- }
- } else if (direction == FocusDirectionUp) {
- //
- // #1 #2 #3
- //
- // |--| |--| |--|
- //
- // |--|
-
- y1 = a.y();
- y2 = b.bottom();
-
- if (rightOf(a, b)) {
- // #1 The b rect is to the left of a.
- x1 = a.x();
- x2 = b.right();
- } else if (rightOf(b, a)) {
- // #3 The b rect is to the right of a.
- x1 = a.right();
- x2 = b.x();
- } else {
- // #2 Both b and a share some common x's.
- x1 = 0;
- x2 = 0;
- }
- } else if (direction == FocusDirectionDown) {
- // |--|
- //
- // |--| |--| |--|
- //
- // #1 #2 #3
-
- y1 = a.bottom();
- y2 = b.y();
-
- if (rightOf(a, b)) {
- // #1 The b rect is to the left of a.
- x1 = a.x();
- x2 = b.right();
- } else if (rightOf(b, a)) {
- // #3 The b rect is to the right of a
- x1 = a.right();
- x2 = b.x();
- } else {
- // #2 Both b and a share some common x's.
- x1 = 0;
- x2 = 0;
- }
- }
-
- long long dx = x1 - x2;
- long long dy = y1 - y2;
-
- long long distance = (dx * dx) + (dy * dy);
-
- if (distance < 0)
- distance *= -1;
-
- return distance;
-}
-
static bool isRectInDirection(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
{
- IntPoint center(targetRect.center());
- int targetMiddle = isHorizontalMove(direction) ? center.x() : center.y();
-
switch (direction) {
case FocusDirectionLeft:
- return targetMiddle < curRect.x();
+ return targetRect.right() <= curRect.x();
case FocusDirectionRight:
- return targetMiddle > curRect.right();
+ return targetRect.x() >= curRect.right();
case FocusDirectionUp:
- return targetMiddle < curRect.y();
+ return targetRect.bottom() <= curRect.y();
case FocusDirectionDown:
- return targetMiddle > curRect.bottom();
+ return targetRect.y() >= curRect.bottom();
default:
ASSERT_NOT_REACHED();
+ return false;
}
-
- return false;
}
// Checks if |node| is offscreen the visible area (viewport) of its container
// document. In case it is, one can scroll in direction or take any different
// desired action later on.
-bool hasOffscreenRect(Node* node)
+bool hasOffscreenRect(Node* node, FocusDirection direction)
{
// Get the FrameView in which |node| is (which means the current viewport if |node|
// is not in an inner document), so we can check if its content rect is visible
@@ -435,68 +275,106 @@ bool hasOffscreenRect(Node* node)
return true;
IntRect containerViewportRect = frameView->visibleContentRect();
-
- RenderObject* render = node->renderer();
- if (!render)
- return true;
-
- IntRect rect(render->absoluteClippedOverflowRect());
- if (rect.isEmpty())
- return true;
-
- return !containerViewportRect.intersects(rect);
-}
-
-// In a bottom-up way, this method tries to scroll |frame| in a given direction
-// |direction|, going up in the frame tree hierarchy in case it does not succeed.
-bool scrollInDirection(Frame* frame, FocusDirection direction, const FocusCandidate& candidate)
-{
- if (!frame)
- return false;
-
- ScrollDirection scrollDirection;
-
+ // We want to select a node if it is currently off screen, but will be
+ // exposed after we scroll. Adjust the viewport to post-scrolling position.
+ // If the container has overflow:hidden, we cannot scroll, so we do not pass direction
+ // and we do not adjust for scrolling.
switch (direction) {
case FocusDirectionLeft:
- scrollDirection = ScrollLeft;
+ containerViewportRect.setX(containerViewportRect.x() - Scrollbar::pixelsPerLineStep());
+ containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
break;
case FocusDirectionRight:
- scrollDirection = ScrollRight;
+ containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
break;
case FocusDirectionUp:
- scrollDirection = ScrollUp;
+ containerViewportRect.setY(containerViewportRect.y() - Scrollbar::pixelsPerLineStep());
+ containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
break;
case FocusDirectionDown:
- scrollDirection = ScrollDown;
+ containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
break;
default:
- return false;
+ break;
}
- if (!candidate.isNull() && isScrollableContainerNode(candidate.enclosingScrollableBox))
- return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine, candidate.enclosingScrollableBox);
+ RenderObject* render = node->renderer();
+ if (!render)
+ return true;
+
+ IntRect rect(render->absoluteClippedOverflowRect());
+ if (rect.isEmpty())
+ return true;
- return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine);
+ return !containerViewportRect.intersects(rect);
}
-void scrollIntoView(Element* element)
+bool scrollInDirection(Frame* frame, FocusDirection direction)
{
- // NOTE: Element's scrollIntoView method could had been used here, but
- // it is preferable to inflate |element|'s bounding rect a bit before
- // scrolling it for accurate reason.
- // Element's scrollIntoView method does not provide this flexibility.
- IntRect bounds = element->getRect();
- bounds.inflate(fudgeFactor());
- element->renderer()->enclosingLayer()->scrollRectToVisible(bounds);
+ ASSERT(frame && canScrollInDirection(direction, frame->document()));
+
+ if (frame && canScrollInDirection(direction, frame->document())) {
+ int dx = 0;
+ int dy = 0;
+ switch (direction) {
+ case FocusDirectionLeft:
+ dx = - Scrollbar::pixelsPerLineStep();
+ break;
+ case FocusDirectionRight:
+ dx = Scrollbar::pixelsPerLineStep();
+ break;
+ case FocusDirectionUp:
+ dy = - Scrollbar::pixelsPerLineStep();
+ break;
+ case FocusDirectionDown:
+ dy = Scrollbar::pixelsPerLineStep();
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+
+ frame->view()->scrollBy(IntSize(dx, dy));
+ return true;
+ }
+ return false;
}
-bool isInRootDocument(Node* node)
+bool scrollInDirection(Node* container, FocusDirection direction)
{
- if (!node)
+ if (container->isDocumentNode())
+ return scrollInDirection(static_cast<Document*>(container)->frame(), direction);
+
+ if (!container->renderBox())
return false;
- Document* rootDocument = node->document()->page()->mainFrame()->document();
- return node->document() == rootDocument;
+ if (container && canScrollInDirection(direction, container)) {
+ int dx = 0;
+ int dy = 0;
+ switch (direction) {
+ case FocusDirectionLeft:
+ dx = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollLeft());
+ break;
+ case FocusDirectionRight:
+ ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
+ dx = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
+ break;
+ case FocusDirectionUp:
+ dy = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollTop());
+ break;
+ case FocusDirectionDown:
+ ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
+ dy = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+
+ container->renderBox()->enclosingLayer()->scrollByRecursively(dx, dy);
+ return true;
+ }
+ return false;
}
static void deflateIfOverlapped(IntRect& a, IntRect& b)
@@ -514,57 +392,281 @@ static void deflateIfOverlapped(IntRect& a, IntRect& b)
b.inflate(deflateFactor);
}
-static bool checkNegativeCoordsForNode(Node* node, const IntRect& curRect)
+bool isScrollableContainerNode(const Node* node)
{
- ASSERT(node || node->renderer());
+ if (!node)
+ return false;
- if (curRect.x() >= 0 && curRect.y() >= 0)
- return true;
+ if (RenderObject* renderer = node->renderer()) {
+ return (renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea()
+ && node->hasChildNodes() && !node->isDocumentNode());
+ }
- bool canBeScrolled = false;
+ return false;
+}
- RenderObject* renderer = node->renderer();
- for (; renderer; renderer = renderer->parent()) {
- if (renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea()) {
- canBeScrolled = true;
- break;
+Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection direction, Node* node)
+{
+ ASSERT(node);
+ Node* parent = node;
+ do {
+ if (parent->isDocumentNode())
+ parent = static_cast<Document*>(parent)->document()->frame()->ownerElement();
+ else
+ parent = parent->parentNode();
+ } while (parent && !canScrollInDirection(direction, parent) && !parent->isDocumentNode());
+
+ return parent;
+}
+
+bool canScrollInDirection(FocusDirection direction, const Node* container)
+{
+ ASSERT(container);
+ if (container->isDocumentNode())
+ return canScrollInDirection(direction, static_cast<const Document*>(container)->frame());
+
+ if (!isScrollableContainerNode(container))
+ return false;
+
+ switch (direction) {
+ case FocusDirectionLeft:
+ return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
+ case FocusDirectionUp:
+ return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
+ case FocusDirectionRight:
+ return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
+ case FocusDirectionDown:
+ return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
+ default:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+}
+
+bool canScrollInDirection(FocusDirection direction, const Frame* frame)
+{
+ if (!frame->view())
+ return false;
+ ScrollbarMode verticalMode;
+ ScrollbarMode horizontalMode;
+ frame->view()->calculateScrollbarModesForLayout(horizontalMode, verticalMode);
+ if ((direction == FocusDirectionLeft || direction == FocusDirectionRight) && ScrollbarAlwaysOff == horizontalMode)
+ return false;
+ if ((direction == FocusDirectionUp || direction == FocusDirectionDown) && ScrollbarAlwaysOff == verticalMode)
+ return false;
+ IntSize size = frame->view()->contentsSize();
+ IntSize offset = frame->view()->scrollOffset();
+ IntRect rect = frame->view()->visibleContentRect(true);
+
+ switch (direction) {
+ case FocusDirectionLeft:
+ return offset.width() > 0;
+ case FocusDirectionUp:
+ return offset.height() > 0;
+ case FocusDirectionRight:
+ return rect.width() + offset.width() < size.width();
+ case FocusDirectionDown:
+ return rect.height() + offset.height() < size.height();
+ default:
+ ASSERT_NOT_REACHED();
+ return false;
+ }
+}
+
+static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& initialRect)
+{
+ IntRect rect = initialRect;
+ for (Frame* frame = initialFrame; frame; frame = frame->tree()->parent()) {
+ if (Element* element = static_cast<Element*>(frame->ownerElement())) {
+ do {
+ rect.move(element->offsetLeft(), element->offsetTop());
+ } while ((element = element->offsetParent()));
+ rect.move((-frame->view()->scrollOffset()));
}
}
+ return rect;
+}
- return canBeScrolled;
+IntRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
+{
+ ASSERT(node && node->renderer());
+
+ if (node->isDocumentNode())
+ return frameRectInAbsoluteCoordinates(static_cast<Document*>(node)->frame());
+ IntRect rect = rectToAbsoluteCoordinates(node->document()->frame(), node->getRect());
+
+ // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
+ // the rect of the focused element.
+ 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;
}
-bool isScrollableContainerNode(Node* node)
+IntRect frameRectInAbsoluteCoordinates(Frame* frame)
{
- if (!node)
- return false;
+ return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
+}
- if (RenderObject* renderer = node->renderer()) {
- return (renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea()
- && node->hasChildNodes() && !node->isDocumentNode());
+// This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect.
+// The line between those 2 points is the closest distance between the 2 rects.
+void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint)
+{
+ switch (direction) {
+ case FocusDirectionLeft:
+ exitPoint.setX(startingRect.x());
+ entryPoint.setX(potentialRect.right());
+ break;
+ case FocusDirectionUp:
+ exitPoint.setY(startingRect.y());
+ entryPoint.setY(potentialRect.bottom());
+ break;
+ case FocusDirectionRight:
+ exitPoint.setX(startingRect.right());
+ entryPoint.setX(potentialRect.x());
+ break;
+ case FocusDirectionDown:
+ exitPoint.setY(startingRect.bottom());
+ entryPoint.setY(potentialRect.y());
+ break;
+ default:
+ ASSERT_NOT_REACHED();
}
- return false;
+ switch (direction) {
+ case FocusDirectionLeft:
+ case FocusDirectionRight:
+ if (below(startingRect, potentialRect)) {
+ exitPoint.setY(startingRect.y());
+ entryPoint.setY(potentialRect.bottom());
+ } else if (below(potentialRect, startingRect)) {
+ exitPoint.setY(startingRect.bottom());
+ entryPoint.setY(potentialRect.y());
+ } else {
+ exitPoint.setY(max(startingRect.y(), potentialRect.y()));
+ entryPoint.setY(exitPoint.y());
+ }
+ break;
+ case FocusDirectionUp:
+ case FocusDirectionDown:
+ if (rightOf(startingRect, potentialRect)) {
+ exitPoint.setX(startingRect.x());
+ entryPoint.setX(potentialRect.right());
+ } else if (rightOf(potentialRect, startingRect)) {
+ exitPoint.setX(startingRect.right());
+ entryPoint.setX(potentialRect.x());
+ } else {
+ exitPoint.setX(max(startingRect.x(), potentialRect.x()));
+ entryPoint.setX(exitPoint.x());
+ }
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
}
-bool isNodeDeepDescendantOfDocument(Node* node, Document* baseDocument)
+void distanceDataForNode(FocusDirection direction, FocusCandidate& current, FocusCandidate& candidate)
{
- if (!node || !baseDocument)
- return false;
+ if (candidate.isNull())
+ return;
+ if (!candidate.node->renderer())
+ return;
+ IntRect nodeRect = candidate.rect;
+ IntRect currentRect = current.rect;
+ deflateIfOverlapped(currentRect, nodeRect);
- bool descendant = baseDocument == node->document();
+ if (!isRectInDirection(direction, currentRect, nodeRect))
+ return;
- Element* currentElement = static_cast<Element*>(node);
- while (!descendant) {
- Element* documentOwner = currentElement->document()->ownerElement();
- if (!documentOwner)
- break;
+ IntPoint exitPoint;
+ IntPoint entryPoint;
+ int sameAxisDistance = 0;
+ int otherAxisDistance = 0;
+ entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint);
+
+ switch (direction) {
+ case FocusDirectionLeft:
+ sameAxisDistance = exitPoint.x() - entryPoint.x();
+ otherAxisDistance = abs(exitPoint.y() - entryPoint.y());
+ break;
+ case FocusDirectionUp:
+ sameAxisDistance = exitPoint.y() - entryPoint.y();
+ otherAxisDistance = abs(exitPoint.x() - entryPoint.x());
+ break;
+ case FocusDirectionRight:
+ sameAxisDistance = entryPoint.x() - exitPoint.x();
+ otherAxisDistance = abs(entryPoint.y() - exitPoint.y());
+ break;
+ case FocusDirectionDown:
+ sameAxisDistance = entryPoint.y() - exitPoint.y();
+ otherAxisDistance = abs(entryPoint.x() - exitPoint.x());
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ return;
+ }
- descendant = documentOwner->document() == baseDocument;
- currentElement = documentOwner;
+ int x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x());
+ int y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y());
+
+ float euclidianDistance = sqrt((x + y) * 1.0f);
+
+ // Loosely based on http://www.w3.org/TR/WICD/#focus-handling
+ // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap)
+
+ float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance;
+ candidate.distance = roundf(distance);
+ IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
+ candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize);
+}
+
+bool canBeScrolledIntoView(FocusDirection direction, const FocusCandidate& candidate)
+{
+ ASSERT(candidate.node && candidate.isOffscreen);
+ IntRect candidateRect = candidate.rect;
+ for (Node* parentNode = candidate.node->parentNode(); parentNode; parentNode = parentNode->parentNode()) {
+ IntRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
+ if (!candidateRect.intersects(parentRect)) {
+ if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
+ || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
+ return false;
+ }
+ if (parentNode == candidate.enclosingScrollableBox)
+ return canScrollInDirection(direction, parentNode);
}
+ return true;
+}
- return descendant;
+// The starting rect is the rect of the focused node, in document coordinates.
+// Compose a virtual starting rect if there is no focused node or if it is off screen.
+// The virtual rect is the edge of the container or frame. We select which
+// edge depending on the direction of the navigation.
+IntRect virtualRectForDirection(FocusDirection direction, const IntRect& startingRect)
+{
+ IntRect virtualStartingRect = startingRect;
+ switch (direction) {
+ case FocusDirectionLeft:
+ virtualStartingRect.setX(virtualStartingRect.right());
+ virtualStartingRect.setWidth(0);
+ break;
+ case FocusDirectionUp:
+ virtualStartingRect.setY(virtualStartingRect.bottom());
+ virtualStartingRect.setHeight(0);
+ break;
+ case FocusDirectionRight:
+ virtualStartingRect.setWidth(0);
+ break;
+ case FocusDirectionDown:
+ virtualStartingRect.setHeight(0);
+ break;
+ default:
+ ASSERT_NOT_REACHED();
+ }
+
+ return virtualStartingRect;
}
+
} // namespace WebCore