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