Home | History | Annotate | Download | only in page
      1 /*
      2  * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
      3  * Copyright (C) 2009 Antonio Gomes <tonikitoo (at) webkit.org>
      4  *
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     20  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     21  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     22  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     23  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     24  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include "config.h"
     30 #include "core/page/SpatialNavigation.h"
     31 
     32 #include "core/HTMLNames.h"
     33 #include "core/frame/FrameView.h"
     34 #include "core/frame/LocalFrame.h"
     35 #include "core/frame/Settings.h"
     36 #include "core/html/HTMLAreaElement.h"
     37 #include "core/html/HTMLFrameOwnerElement.h"
     38 #include "core/html/HTMLImageElement.h"
     39 #include "core/page/FrameTree.h"
     40 #include "core/page/Page.h"
     41 #include "core/rendering/RenderLayer.h"
     42 #include "platform/geometry/IntRect.h"
     43 
     44 namespace WebCore {
     45 
     46 using namespace HTMLNames;
     47 
     48 static RectsAlignment alignmentForRects(FocusType, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize);
     49 static bool areRectsFullyAligned(FocusType, const LayoutRect&, const LayoutRect&);
     50 static bool areRectsPartiallyAligned(FocusType, const LayoutRect&, const LayoutRect&);
     51 static bool areRectsMoreThanFullScreenApart(FocusType, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize);
     52 static bool isRectInDirection(FocusType, const LayoutRect&, const LayoutRect&);
     53 static void deflateIfOverlapped(LayoutRect&, LayoutRect&);
     54 static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect&);
     55 static bool isScrollableNode(const Node*);
     56 
     57 FocusCandidate::FocusCandidate(Node* node, FocusType type)
     58     : visibleNode(0)
     59     , focusableNode(0)
     60     , enclosingScrollableBox(0)
     61     , distance(maxDistance())
     62     , parentDistance(maxDistance())
     63     , alignment(None)
     64     , parentAlignment(None)
     65     , isOffscreen(true)
     66     , isOffscreenAfterScrolling(true)
     67 {
     68     ASSERT(node);
     69     ASSERT(node->isElementNode());
     70 
     71     if (isHTMLAreaElement(*node)) {
     72         HTMLAreaElement& area = toHTMLAreaElement(*node);
     73         HTMLImageElement* image = area.imageElement();
     74         if (!image || !image->renderer())
     75             return;
     76 
     77         visibleNode = image;
     78         rect = virtualRectForAreaElementAndDirection(area, type);
     79     } else {
     80         if (!node->renderer())
     81             return;
     82 
     83         visibleNode = node;
     84         rect = nodeRectInAbsoluteCoordinates(node, true /* ignore border */);
     85     }
     86 
     87     focusableNode = node;
     88     isOffscreen = hasOffscreenRect(visibleNode);
     89     isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, type);
     90 }
     91 
     92 bool isSpatialNavigationEnabled(const LocalFrame* frame)
     93 {
     94     return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled());
     95 }
     96 
     97 static RectsAlignment alignmentForRects(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
     98 {
     99     // If we found a node in full alignment, but it is too far away, ignore it.
    100     if (areRectsMoreThanFullScreenApart(type, curRect, targetRect, viewSize))
    101         return None;
    102 
    103     if (areRectsFullyAligned(type, curRect, targetRect))
    104         return Full;
    105 
    106     if (areRectsPartiallyAligned(type, curRect, targetRect))
    107         return Partial;
    108 
    109     return None;
    110 }
    111 
    112 static inline bool isHorizontalMove(FocusType type)
    113 {
    114     return type == FocusTypeLeft || type == FocusTypeRight;
    115 }
    116 
    117 static inline LayoutUnit start(FocusType type, const LayoutRect& rect)
    118 {
    119     return isHorizontalMove(type) ? rect.y() : rect.x();
    120 }
    121 
    122 static inline LayoutUnit middle(FocusType type, const LayoutRect& rect)
    123 {
    124     LayoutPoint center(rect.center());
    125     return isHorizontalMove(type) ? center.y(): center.x();
    126 }
    127 
    128 static inline LayoutUnit end(FocusType type, const LayoutRect& rect)
    129 {
    130     return isHorizontalMove(type) ? rect.maxY() : rect.maxX();
    131 }
    132 
    133 // This method checks if rects |a| and |b| are fully aligned either vertically or
    134 // horizontally. In general, rects whose central point falls between the top or
    135 // bottom of each other are considered fully aligned.
    136 // Rects that match this criteria are preferable target nodes in move focus changing
    137 // operations.
    138 // * a = Current focused node's rect.
    139 // * b = Focus candidate node's rect.
    140 static bool areRectsFullyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b)
    141 {
    142     LayoutUnit aStart, bStart, aEnd, bEnd;
    143 
    144     switch (type) {
    145     case FocusTypeLeft:
    146         aStart = a.x();
    147         bEnd = b.x();
    148         break;
    149     case FocusTypeRight:
    150         aStart = b.x();
    151         bEnd = a.x();
    152         break;
    153     case FocusTypeUp:
    154         aStart = a.y();
    155         bEnd = b.y();
    156         break;
    157     case FocusTypeDown:
    158         aStart = b.y();
    159         bEnd = a.y();
    160         break;
    161     default:
    162         ASSERT_NOT_REACHED();
    163         return false;
    164     }
    165 
    166     if (aStart < bEnd)
    167         return false;
    168 
    169     aStart = start(type, a);
    170     bStart = start(type, b);
    171 
    172     LayoutUnit aMiddle = middle(type, a);
    173     LayoutUnit bMiddle = middle(type, b);
    174 
    175     aEnd = end(type, a);
    176     bEnd = end(type, b);
    177 
    178     // Picture of the totally aligned logic:
    179     //
    180     //     Horizontal    Vertical        Horizontal     Vertical
    181     //  ****************************  *****************************
    182     //  *  _          *   _ _ _ _  *  *         _   *      _ _    *
    183     //  * |_|     _   *  |_|_|_|_| *  *  _     |_|  *     |_|_|   *
    184     //  * |_|....|_|  *      .     *  * |_|....|_|  *       .     *
    185     //  * |_|    |_| (1)     .     *  * |_|    |_| (2)      .     *
    186     //  * |_|         *     _._    *  *        |_|  *    _ _._ _  *
    187     //  *             *    |_|_|   *  *             *   |_|_|_|_| *
    188     //  *             *            *  *             *             *
    189     //  ****************************  *****************************
    190 
    191     return (bMiddle >= aStart && bMiddle <= aEnd) // (1)
    192         || (aMiddle >= bStart && aMiddle <= bEnd); // (2)
    193 }
    194 
    195 // This method checks if rects |a| and |b| are partially aligned either vertically or
    196 // horizontally. In general, rects whose either of edges falls between the top or
    197 // bottom of each other are considered partially-aligned.
    198 // This is a separate set of conditions from "fully-aligned" and do not include cases
    199 // that satisfy the former.
    200 // * a = Current focused node's rect.
    201 // * b = Focus candidate node's rect.
    202 static bool areRectsPartiallyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b)
    203 {
    204     LayoutUnit aStart  = start(type, a);
    205     LayoutUnit bStart  = start(type, b);
    206     LayoutUnit aEnd = end(type, a);
    207     LayoutUnit bEnd = end(type, b);
    208 
    209     // Picture of the partially aligned logic:
    210     //
    211     //    Horizontal       Vertical
    212     // ********************************
    213     // *  _            *   _ _ _      *
    214     // * |_|           *  |_|_|_|     *
    215     // * |_|.... _     *      . .     *
    216     // * |_|    |_|    *      . .     *
    217     // * |_|....|_|    *      ._._ _  *
    218     // *        |_|    *      |_|_|_| *
    219     // *        |_|    *              *
    220     // *               *              *
    221     // ********************************
    222     //
    223     // ... and variants of the above cases.
    224     return (bStart >= aStart && bStart <= aEnd)
    225         || (bEnd >= aStart && bEnd <= aEnd);
    226 }
    227 
    228 static bool areRectsMoreThanFullScreenApart(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
    229 {
    230     ASSERT(isRectInDirection(type, curRect, targetRect));
    231 
    232     switch (type) {
    233     case FocusTypeLeft:
    234         return curRect.x() - targetRect.maxX() > viewSize.width();
    235     case FocusTypeRight:
    236         return targetRect.x() - curRect.maxX() > viewSize.width();
    237     case FocusTypeUp:
    238         return curRect.y() - targetRect.maxY() > viewSize.height();
    239     case FocusTypeDown:
    240         return targetRect.y() - curRect.maxY() > viewSize.height();
    241     default:
    242         ASSERT_NOT_REACHED();
    243         return true;
    244     }
    245 }
    246 
    247 // Return true if rect |a| is below |b|. False otherwise.
    248 // For overlapping rects, |a| is considered to be below |b|
    249 // if both edges of |a| are below the respective ones of |b|
    250 static inline bool below(const LayoutRect& a, const LayoutRect& b)
    251 {
    252     return a.y() >= b.maxY()
    253         || (a.y() >= b.y() && a.maxY() > b.maxY());
    254 }
    255 
    256 // Return true if rect |a| is on the right of |b|. False otherwise.
    257 // For overlapping rects, |a| is considered to be on the right of |b|
    258 // if both edges of |a| are on the right of the respective ones of |b|
    259 static inline bool rightOf(const LayoutRect& a, const LayoutRect& b)
    260 {
    261     return a.x() >= b.maxX()
    262         || (a.x() >= b.x() && a.maxX() > b.maxX());
    263 }
    264 
    265 static bool isRectInDirection(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect)
    266 {
    267     switch (type) {
    268     case FocusTypeLeft:
    269         return rightOf(curRect, targetRect);
    270     case FocusTypeRight:
    271         return rightOf(targetRect, curRect);
    272     case FocusTypeUp:
    273         return below(curRect, targetRect);
    274     case FocusTypeDown:
    275         return below(targetRect, curRect);
    276     default:
    277         ASSERT_NOT_REACHED();
    278         return false;
    279     }
    280 }
    281 
    282 // Checks if |node| is offscreen the visible area (viewport) of its container
    283 // document. In case it is, one can scroll in direction or take any different
    284 // desired action later on.
    285 bool hasOffscreenRect(Node* node, FocusType type)
    286 {
    287     // Get the FrameView in which |node| is (which means the current viewport if |node|
    288     // is not in an inner document), so we can check if its content rect is visible
    289     // before we actually move the focus to it.
    290     FrameView* frameView = node->document().view();
    291     if (!frameView)
    292         return true;
    293 
    294     ASSERT(!frameView->needsLayout());
    295 
    296     LayoutRect containerViewportRect = frameView->visibleContentRect();
    297     // We want to select a node if it is currently off screen, but will be
    298     // exposed after we scroll. Adjust the viewport to post-scrolling position.
    299     // If the container has overflow:hidden, we cannot scroll, so we do not pass direction
    300     // and we do not adjust for scrolling.
    301     switch (type) {
    302     case FocusTypeLeft:
    303         containerViewportRect.setX(containerViewportRect.x() - ScrollableArea::pixelsPerLineStep());
    304         containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep());
    305         break;
    306     case FocusTypeRight:
    307         containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep());
    308         break;
    309     case FocusTypeUp:
    310         containerViewportRect.setY(containerViewportRect.y() - ScrollableArea::pixelsPerLineStep());
    311         containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep());
    312         break;
    313     case FocusTypeDown:
    314         containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep());
    315         break;
    316     default:
    317         break;
    318     }
    319 
    320     RenderObject* render = node->renderer();
    321     if (!render)
    322         return true;
    323 
    324     LayoutRect rect(render->absoluteClippedOverflowRect());
    325     if (rect.isEmpty())
    326         return true;
    327 
    328     return !containerViewportRect.intersects(rect);
    329 }
    330 
    331 bool scrollInDirection(LocalFrame* frame, FocusType type)
    332 {
    333     ASSERT(frame);
    334 
    335     if (frame && canScrollInDirection(frame->document(), type)) {
    336         LayoutUnit dx = 0;
    337         LayoutUnit dy = 0;
    338         switch (type) {
    339         case FocusTypeLeft:
    340             dx = - ScrollableArea::pixelsPerLineStep();
    341             break;
    342         case FocusTypeRight:
    343             dx = ScrollableArea::pixelsPerLineStep();
    344             break;
    345         case FocusTypeUp:
    346             dy = - ScrollableArea::pixelsPerLineStep();
    347             break;
    348         case FocusTypeDown:
    349             dy = ScrollableArea::pixelsPerLineStep();
    350             break;
    351         default:
    352             ASSERT_NOT_REACHED();
    353             return false;
    354         }
    355 
    356         frame->view()->scrollBy(IntSize(dx, dy));
    357         return true;
    358     }
    359     return false;
    360 }
    361 
    362 bool scrollInDirection(Node* container, FocusType type)
    363 {
    364     ASSERT(container);
    365     if (container->isDocumentNode())
    366         return scrollInDirection(toDocument(container)->frame(), type);
    367 
    368     if (!container->renderBox())
    369         return false;
    370 
    371     if (canScrollInDirection(container, type)) {
    372         LayoutUnit dx = 0;
    373         LayoutUnit dy = 0;
    374         switch (type) {
    375         case FocusTypeLeft:
    376             dx = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollLeft());
    377             break;
    378         case FocusTypeRight:
    379             ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
    380             dx = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
    381             break;
    382         case FocusTypeUp:
    383             dy = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollTop());
    384             break;
    385         case FocusTypeDown:
    386             ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
    387             dy = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
    388             break;
    389         default:
    390             ASSERT_NOT_REACHED();
    391             return false;
    392         }
    393 
    394         container->renderBox()->scrollByRecursively(IntSize(dx, dy));
    395         return true;
    396     }
    397 
    398     return false;
    399 }
    400 
    401 static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b)
    402 {
    403     if (!a.intersects(b) || a.contains(b) || b.contains(a))
    404         return;
    405 
    406     LayoutUnit deflateFactor = -fudgeFactor();
    407 
    408     // Avoid negative width or height values.
    409     if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0))
    410         a.inflate(deflateFactor);
    411 
    412     if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0))
    413         b.inflate(deflateFactor);
    414 }
    415 
    416 bool isScrollableNode(const Node* node)
    417 {
    418     ASSERT(!node->isDocumentNode());
    419 
    420     if (!node)
    421         return false;
    422 
    423     if (RenderObject* renderer = node->renderer())
    424         return renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea() && node->hasChildren();
    425 
    426     return false;
    427 }
    428 
    429 Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusType type, Node* node)
    430 {
    431     ASSERT(node);
    432     Node* parent = node;
    433     do {
    434         // FIXME: Spatial navigation is broken for OOPI.
    435         if (parent->isDocumentNode())
    436             parent = toDocument(parent)->frame()->deprecatedLocalOwner();
    437         else
    438             parent = parent->parentOrShadowHostNode();
    439     } while (parent && !canScrollInDirection(parent, type) && !parent->isDocumentNode());
    440 
    441     return parent;
    442 }
    443 
    444 bool canScrollInDirection(const Node* container, FocusType type)
    445 {
    446     ASSERT(container);
    447     if (container->isDocumentNode())
    448         return canScrollInDirection(toDocument(container)->frame(), type);
    449 
    450     if (!isScrollableNode(container))
    451         return false;
    452 
    453     switch (type) {
    454     case FocusTypeLeft:
    455         return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
    456     case FocusTypeUp:
    457         return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
    458     case FocusTypeRight:
    459         return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
    460     case FocusTypeDown:
    461         return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
    462     default:
    463         ASSERT_NOT_REACHED();
    464         return false;
    465     }
    466 }
    467 
    468 bool canScrollInDirection(const LocalFrame* frame, FocusType type)
    469 {
    470     if (!frame->view())
    471         return false;
    472     ScrollbarMode verticalMode;
    473     ScrollbarMode horizontalMode;
    474     frame->view()->calculateScrollbarModesForLayoutAndSetViewportRenderer(horizontalMode, verticalMode);
    475     if ((type == FocusTypeLeft || type == FocusTypeRight) && ScrollbarAlwaysOff == horizontalMode)
    476         return false;
    477     if ((type == FocusTypeUp || type == FocusTypeDown) &&  ScrollbarAlwaysOff == verticalMode)
    478         return false;
    479     LayoutSize size = frame->view()->contentsSize();
    480     LayoutSize offset = frame->view()->scrollOffset();
    481     LayoutRect rect = frame->view()->visibleContentRect(IncludeScrollbars);
    482 
    483     switch (type) {
    484     case FocusTypeLeft:
    485         return offset.width() > 0;
    486     case FocusTypeUp:
    487         return offset.height() > 0;
    488     case FocusTypeRight:
    489         return rect.width() + offset.width() < size.width();
    490     case FocusTypeDown:
    491         return rect.height() + offset.height() < size.height();
    492     default:
    493         ASSERT_NOT_REACHED();
    494         return false;
    495     }
    496 }
    497 
    498 static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect& initialRect)
    499 {
    500     LayoutRect rect = initialRect;
    501     for (Frame* frame = initialFrame; frame; frame = frame->tree().parent()) {
    502         if (!frame->isLocalFrame())
    503             continue;
    504         // FIXME: Spatial navigation is broken for OOPI.
    505         if (Element* element = frame->deprecatedLocalOwner()) {
    506             do {
    507                 rect.move(element->offsetLeft(), element->offsetTop());
    508             } while ((element = element->offsetParent()));
    509             rect.move((-toLocalFrame(frame)->view()->scrollOffset()));
    510         }
    511     }
    512     return rect;
    513 }
    514 
    515 LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
    516 {
    517     ASSERT(node && node->renderer() && !node->document().view()->needsLayout());
    518 
    519     if (node->isDocumentNode())
    520         return frameRectInAbsoluteCoordinates(toDocument(node)->frame());
    521     LayoutRect rect = rectToAbsoluteCoordinates(node->document().frame(), node->boundingBox());
    522 
    523     // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
    524     // the rect of the focused element.
    525     if (ignoreBorder) {
    526         rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth());
    527         rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth());
    528         rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth());
    529     }
    530     return rect;
    531 }
    532 
    533 LayoutRect frameRectInAbsoluteCoordinates(LocalFrame* frame)
    534 {
    535     return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
    536 }
    537 
    538 // This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect.
    539 // The line between those 2 points is the closest distance between the 2 rects.
    540 // Takes care of overlapping rects, defining points so that the distance between them
    541 // is zero where necessary
    542 void entryAndExitPointsForDirection(FocusType type, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint)
    543 {
    544     switch (type) {
    545     case FocusTypeLeft:
    546         exitPoint.setX(startingRect.x());
    547         if (potentialRect.maxX() < startingRect.x())
    548             entryPoint.setX(potentialRect.maxX());
    549         else
    550             entryPoint.setX(startingRect.x());
    551         break;
    552     case FocusTypeUp:
    553         exitPoint.setY(startingRect.y());
    554         if (potentialRect.maxY() < startingRect.y())
    555             entryPoint.setY(potentialRect.maxY());
    556         else
    557             entryPoint.setY(startingRect.y());
    558         break;
    559     case FocusTypeRight:
    560         exitPoint.setX(startingRect.maxX());
    561         if (potentialRect.x() > startingRect.maxX())
    562             entryPoint.setX(potentialRect.x());
    563         else
    564             entryPoint.setX(startingRect.maxX());
    565         break;
    566     case FocusTypeDown:
    567         exitPoint.setY(startingRect.maxY());
    568         if (potentialRect.y() > startingRect.maxY())
    569             entryPoint.setY(potentialRect.y());
    570         else
    571             entryPoint.setY(startingRect.maxY());
    572         break;
    573     default:
    574         ASSERT_NOT_REACHED();
    575     }
    576 
    577     switch (type) {
    578     case FocusTypeLeft:
    579     case FocusTypeRight:
    580         if (below(startingRect, potentialRect)) {
    581             exitPoint.setY(startingRect.y());
    582             if (potentialRect.maxY() < startingRect.y())
    583                 entryPoint.setY(potentialRect.maxY());
    584             else
    585                 entryPoint.setY(startingRect.y());
    586         } else if (below(potentialRect, startingRect)) {
    587             exitPoint.setY(startingRect.maxY());
    588             if (potentialRect.y() > startingRect.maxY())
    589                 entryPoint.setY(potentialRect.y());
    590             else
    591                 entryPoint.setY(startingRect.maxY());
    592         } else {
    593             exitPoint.setY(max(startingRect.y(), potentialRect.y()));
    594             entryPoint.setY(exitPoint.y());
    595         }
    596         break;
    597     case FocusTypeUp:
    598     case FocusTypeDown:
    599         if (rightOf(startingRect, potentialRect)) {
    600             exitPoint.setX(startingRect.x());
    601             if (potentialRect.maxX() < startingRect.x())
    602                 entryPoint.setX(potentialRect.maxX());
    603             else
    604                 entryPoint.setX(startingRect.x());
    605         } else if (rightOf(potentialRect, startingRect)) {
    606             exitPoint.setX(startingRect.maxX());
    607             if (potentialRect.x() > startingRect.maxX())
    608                 entryPoint.setX(potentialRect.x());
    609             else
    610                 entryPoint.setX(startingRect.maxX());
    611         } else {
    612             exitPoint.setX(max(startingRect.x(), potentialRect.x()));
    613             entryPoint.setX(exitPoint.x());
    614         }
    615         break;
    616     default:
    617         ASSERT_NOT_REACHED();
    618     }
    619 }
    620 
    621 bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate)
    622 {
    623     if (firstCandidate.isNull() || secondCandidate.isNull())
    624         return false;
    625 
    626     if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer())
    627         return false;
    628 
    629     if (!firstCandidate.rect.intersects(secondCandidate.rect))
    630         return false;
    631 
    632     if (isHTMLAreaElement(*firstCandidate.focusableNode) || isHTMLAreaElement(*secondCandidate.focusableNode))
    633         return false;
    634 
    635     if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline())
    636         return false;
    637 
    638     if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock())
    639         return false;
    640 
    641     return true;
    642 }
    643 
    644 void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCandidate& candidate)
    645 {
    646     if (areElementsOnSameLine(current, candidate)) {
    647         if ((type == FocusTypeUp && current.rect.y() > candidate.rect.y()) || (type == FocusTypeDown && candidate.rect.y() > current.rect.y())) {
    648             candidate.distance = 0;
    649             candidate.alignment = Full;
    650             return;
    651         }
    652     }
    653 
    654     LayoutRect nodeRect = candidate.rect;
    655     LayoutRect currentRect = current.rect;
    656     deflateIfOverlapped(currentRect, nodeRect);
    657 
    658     if (!isRectInDirection(type, currentRect, nodeRect))
    659         return;
    660 
    661     LayoutPoint exitPoint;
    662     LayoutPoint entryPoint;
    663     entryAndExitPointsForDirection(type, currentRect, nodeRect, exitPoint, entryPoint);
    664 
    665     LayoutUnit xAxis = exitPoint.x() - entryPoint.x();
    666     LayoutUnit yAxis = exitPoint.y() - entryPoint.y();
    667 
    668     LayoutUnit navigationAxisDistance;
    669     LayoutUnit orthogonalAxisDistance;
    670 
    671     switch (type) {
    672     case FocusTypeLeft:
    673     case FocusTypeRight:
    674         navigationAxisDistance = xAxis.abs();
    675         orthogonalAxisDistance = yAxis.abs();
    676         break;
    677     case FocusTypeUp:
    678     case FocusTypeDown:
    679         navigationAxisDistance = yAxis.abs();
    680         orthogonalAxisDistance = xAxis.abs();
    681         break;
    682     default:
    683         ASSERT_NOT_REACHED();
    684         return;
    685     }
    686 
    687     double euclidianDistancePow2 = (xAxis * xAxis + yAxis * yAxis).toDouble();
    688     LayoutRect intersectionRect = intersection(currentRect, nodeRect);
    689     double overlap = (intersectionRect.width() * intersectionRect.height()).toDouble();
    690 
    691     // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling
    692     candidate.distance = sqrt(euclidianDistancePow2) + navigationAxisDistance+ orthogonalAxisDistance * 2 - sqrt(overlap);
    693 
    694     LayoutSize viewSize = candidate.visibleNode->document().page()->deprecatedLocalMainFrame()->view()->visibleContentRect().size();
    695     candidate.alignment = alignmentForRects(type, currentRect, nodeRect, viewSize);
    696 }
    697 
    698 bool canBeScrolledIntoView(FocusType type, const FocusCandidate& candidate)
    699 {
    700     ASSERT(candidate.visibleNode && candidate.isOffscreen);
    701     LayoutRect candidateRect = candidate.rect;
    702     for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) {
    703         LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
    704         if (!candidateRect.intersects(parentRect)) {
    705             if (((type == FocusTypeLeft || type == FocusTypeRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
    706                 || ((type == FocusTypeUp || type == FocusTypeDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
    707                 return false;
    708         }
    709         if (parentNode == candidate.enclosingScrollableBox)
    710             return canScrollInDirection(parentNode, type);
    711     }
    712     return true;
    713 }
    714 
    715 // The starting rect is the rect of the focused node, in document coordinates.
    716 // Compose a virtual starting rect if there is no focused node or if it is off screen.
    717 // The virtual rect is the edge of the container or frame. We select which
    718 // edge depending on the direction of the navigation.
    719 LayoutRect virtualRectForDirection(FocusType type, const LayoutRect& startingRect, LayoutUnit width)
    720 {
    721     LayoutRect virtualStartingRect = startingRect;
    722     switch (type) {
    723     case FocusTypeLeft:
    724         virtualStartingRect.setX(virtualStartingRect.maxX() - width);
    725         virtualStartingRect.setWidth(width);
    726         break;
    727     case FocusTypeUp:
    728         virtualStartingRect.setY(virtualStartingRect.maxY() - width);
    729         virtualStartingRect.setHeight(width);
    730         break;
    731     case FocusTypeRight:
    732         virtualStartingRect.setWidth(width);
    733         break;
    734     case FocusTypeDown:
    735         virtualStartingRect.setHeight(width);
    736         break;
    737     default:
    738         ASSERT_NOT_REACHED();
    739     }
    740 
    741     return virtualStartingRect;
    742 }
    743 
    744 LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement& area, FocusType type)
    745 {
    746     ASSERT(area.imageElement());
    747     // Area elements tend to overlap more than other focusable elements. We flatten the rect of the area elements
    748     // to minimize the effect of overlapping areas.
    749     LayoutRect rect = virtualRectForDirection(type, rectToAbsoluteCoordinates(area.document().frame(), area.computeRect(area.imageElement()->renderer())), 1);
    750     return rect;
    751 }
    752 
    753 HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate)
    754 {
    755     return candidate.isFrameOwnerElement() ? toHTMLFrameOwnerElement(candidate.visibleNode) : 0;
    756 };
    757 
    758 } // namespace WebCore
    759