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