• 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 "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 
FocusCandidate(Node * node,FocusType type)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 
isSpatialNavigationEnabled(const LocalFrame * frame)92 bool isSpatialNavigationEnabled(const LocalFrame* frame)
93 {
94     return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled());
95 }
96 
alignmentForRects(FocusType type,const LayoutRect & curRect,const LayoutRect & targetRect,const LayoutSize & viewSize)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 
isHorizontalMove(FocusType type)112 static inline bool isHorizontalMove(FocusType type)
113 {
114     return type == FocusTypeLeft || type == FocusTypeRight;
115 }
116 
start(FocusType type,const LayoutRect & rect)117 static inline LayoutUnit start(FocusType type, const LayoutRect& rect)
118 {
119     return isHorizontalMove(type) ? rect.y() : rect.x();
120 }
121 
middle(FocusType type,const LayoutRect & rect)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 
end(FocusType type,const LayoutRect & rect)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.
areRectsFullyAligned(FocusType type,const LayoutRect & a,const LayoutRect & b)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.
areRectsPartiallyAligned(FocusType type,const LayoutRect & a,const LayoutRect & b)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 
areRectsMoreThanFullScreenApart(FocusType type,const LayoutRect & curRect,const LayoutRect & targetRect,const LayoutSize & viewSize)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|
below(const LayoutRect & a,const LayoutRect & 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|
rightOf(const LayoutRect & a,const LayoutRect & 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 
isRectInDirection(FocusType type,const LayoutRect & curRect,const LayoutRect & targetRect)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.
hasOffscreenRect(Node * node,FocusType type)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 
scrollInDirection(LocalFrame * frame,FocusType type)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 
scrollInDirection(Node * container,FocusType type)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 
deflateIfOverlapped(LayoutRect & a,LayoutRect & b)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 
isScrollableNode(const Node * node)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 
scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusType type,Node * node)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 
canScrollInDirection(const Node * container,FocusType type)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 
canScrollInDirection(const LocalFrame * frame,FocusType type)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 
rectToAbsoluteCoordinates(LocalFrame * initialFrame,const LayoutRect & initialRect)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 
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(LocalFrame * frame)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
entryAndExitPointsForDirection(FocusType type,const LayoutRect & startingRect,const LayoutRect & potentialRect,LayoutPoint & exitPoint,LayoutPoint & entryPoint)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 
areElementsOnSameLine(const FocusCandidate & firstCandidate,const FocusCandidate & secondCandidate)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 
distanceDataForNode(FocusType type,const FocusCandidate & current,FocusCandidate & candidate)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 
canBeScrolledIntoView(FocusType type,const FocusCandidate & candidate)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.
virtualRectForDirection(FocusType type,const LayoutRect & startingRect,LayoutUnit width)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 
virtualRectForAreaElementAndDirection(HTMLAreaElement & area,FocusType type)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 
frameOwnerElement(FocusCandidate & candidate)753 HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate)
754 {
755     return candidate.isFrameOwnerElement() ? toHTMLFrameOwnerElement(candidate.visibleNode) : 0;
756 };
757 
758 } // namespace WebCore
759