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