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