blob: d38f043a9059c2385bf3c95665e4139117aa0b54 [file] [log] [blame]
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +00001/*
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 *
mjs@apple.com92047332014-03-15 04:08:27 +000016 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000017 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
mjs@apple.com92047332014-03-15 04:08:27 +000019 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000020 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "SpatialNavigation.h"
31
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000032#include "FrameTree.h"
33#include "FrameView.h"
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000034#include "HTMLAreaElement.h"
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000035#include "HTMLImageElement.h"
36#include "HTMLMapElement.h"
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000037#include "HTMLNames.h"
cdumez@apple.coma92c4d62016-05-20 01:50:51 +000038#include "HTMLSelectElement.h"
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000039#include "IntRect.h"
darin@apple.comade301a2013-09-27 15:09:01 +000040#include "MainFrame.h"
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000041#include "Node.h"
42#include "Page.h"
yael.aharon@nokia.com4a4c87b2011-02-18 21:05:38 +000043#include "RenderInline.h"
chang.shu@nokia.com2883b772010-11-07 09:24:27 +000044#include "RenderLayer.h"
45#include "Settings.h"
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000046
47namespace WebCore {
48
eae@chromium.org4ecf4182011-08-18 00:49:01 +000049static bool areRectsFullyAligned(FocusDirection, const LayoutRect&, const LayoutRect&);
50static bool areRectsPartiallyAligned(FocusDirection, const LayoutRect&, const LayoutRect&);
51static bool areRectsMoreThanFullScreenApart(FocusDirection, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize);
52static bool isRectInDirection(FocusDirection, const LayoutRect&, const LayoutRect&);
53static void deflateIfOverlapped(LayoutRect&, LayoutRect&);
54static LayoutRect rectToAbsoluteCoordinates(Frame* initialFrame, const LayoutRect&);
55static void entryAndExitPointsForDirection(FocusDirection, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint);
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +000056static bool isScrollableNode(const Node*);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +000057
tonikitoo@webkit.org237c1ea2010-12-13 05:52:40 +000058FocusCandidate::FocusCandidate(Node* node, FocusDirection direction)
commit-queue@webkit.org7ee9b0d2015-06-05 01:07:51 +000059 : visibleNode(nullptr)
60 , focusableNode(nullptr)
61 , enclosingScrollableBox(nullptr)
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000062 , distance(maxDistance())
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000063 , alignment(None)
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000064 , isOffscreen(true)
65 , isOffscreenAfterScrolling(true)
66{
cdumez@apple.coma9c60c92014-10-02 19:39:41 +000067 ASSERT(is<Element>(node));
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +000068
cdumez@apple.coma9c60c92014-10-02 19:39:41 +000069 if (is<HTMLAreaElement>(*node)) {
cdumez@apple.com72754ba2014-09-23 22:03:15 +000070 HTMLAreaElement& area = downcast<HTMLAreaElement>(*node);
71 HTMLImageElement* image = area.imageElement();
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000072 if (!image || !image->renderer())
73 return;
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000074
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000075 visibleNode = image;
cdumez@apple.com72754ba2014-09-23 22:03:15 +000076 rect = virtualRectForAreaElementAndDirection(&area, direction);
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000077 } else {
tonikitoo@webkit.org237c1ea2010-12-13 05:52:40 +000078 if (!node->renderer())
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000079 return;
80
tonikitoo@webkit.org237c1ea2010-12-13 05:52:40 +000081 visibleNode = node;
82 rect = nodeRectInAbsoluteCoordinates(node, true /* ignore border */);
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000083 }
84
tonikitoo@webkit.org237c1ea2010-12-13 05:52:40 +000085 focusableNode = node;
tonikitoo@webkit.orga2bb1222010-12-09 18:53:47 +000086 isOffscreen = hasOffscreenRect(visibleNode);
87 isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, direction);
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +000088}
89
chang.shu@nokia.com2883b772010-11-07 09:24:27 +000090bool isSpatialNavigationEnabled(const Frame* frame)
91{
akling@apple.com17523502013-08-17 10:58:40 +000092 return (frame && frame->settings().spatialNavigationEnabled());
chang.shu@nokia.com2883b772010-11-07 09:24:27 +000093}
94
eae@chromium.org4ecf4182011-08-18 00:49:01 +000095static RectsAlignment alignmentForRects(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +000096{
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +000097 // 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
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000101 if (areRectsFullyAligned(direction, curRect, targetRect))
102 return Full;
103
104 if (areRectsPartiallyAligned(direction, curRect, targetRect))
105 return Partial;
106
107 return None;
108}
109
110static inline bool isHorizontalMove(FocusDirection direction)
111{
112 return direction == FocusDirectionLeft || direction == FocusDirectionRight;
113}
114
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000115static inline LayoutUnit start(FocusDirection direction, const LayoutRect& rect)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000116{
117 return isHorizontalMove(direction) ? rect.y() : rect.x();
118}
119
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000120static inline LayoutUnit middle(FocusDirection direction, const LayoutRect& rect)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000121{
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000122 LayoutPoint center(rect.center());
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000123 return isHorizontalMove(direction) ? center.y(): center.x();
124}
125
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000126static inline LayoutUnit end(FocusDirection direction, const LayoutRect& rect)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000127{
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000128 return isHorizontalMove(direction) ? rect.maxY() : rect.maxX();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000129}
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.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000138static bool areRectsFullyAligned(FocusDirection direction, const LayoutRect& a, const LayoutRect& b)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000139{
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000140 LayoutUnit aStart, bStart, aEnd, bEnd;
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000141
142 switch (direction) {
143 case FocusDirectionLeft:
144 aStart = a.x();
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000145 bEnd = b.maxX();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000146 break;
147 case FocusDirectionRight:
148 aStart = b.x();
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000149 bEnd = a.maxX();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000150 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
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000170 LayoutUnit aMiddle = middle(direction, a);
171 LayoutUnit bMiddle = middle(direction, b);
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000172
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.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000210static bool areRectsPartiallyAligned(FocusDirection direction, const LayoutRect& a, const LayoutRect& b)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000211{
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000212 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);
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000217
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)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000234 || (bMiddle >= aStart && bMiddle <= aEnd)
235 || (bEnd >= aStart && bEnd <= aEnd));
236}
237
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000238static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000239{
240 ASSERT(isRectInDirection(direction, curRect, targetRect));
241
242 switch (direction) {
243 case FocusDirectionLeft:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000244 return curRect.x() - targetRect.maxX() > viewSize.width();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000245 case FocusDirectionRight:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000246 return targetRect.x() - curRect.maxX() > viewSize.width();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000247 case FocusDirectionUp:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000248 return curRect.y() - targetRect.maxY() > viewSize.height();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000249 case FocusDirectionDown:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000250 return targetRect.y() - curRect.maxY() > viewSize.height();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000251 default:
252 ASSERT_NOT_REACHED();
253 return true;
254 }
255}
256
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000257// Return true if rect |a| is below |b|. False otherwise.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000258static inline bool below(const LayoutRect& a, const LayoutRect& b)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000259{
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000260 return a.y() > b.maxY();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000261}
262
263// Return true if rect |a| is on the right of |b|. False otherwise.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000264static inline bool rightOf(const LayoutRect& a, const LayoutRect& b)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000265{
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000266 return a.x() > b.maxX();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000267}
268
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000269static bool isRectInDirection(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000270{
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000271 switch (direction) {
272 case FocusDirectionLeft:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000273 return targetRect.maxX() <= curRect.x();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000274 case FocusDirectionRight:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000275 return targetRect.x() >= curRect.maxX();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000276 case FocusDirectionUp:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000277 return targetRect.maxY() <= curRect.y();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000278 case FocusDirectionDown:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000279 return targetRect.y() >= curRect.maxY();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000280 default:
281 ASSERT_NOT_REACHED();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000282 return false;
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000283 }
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000284}
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.
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000289bool hasOffscreenRect(Node* node, FocusDirection direction)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000290{
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.
akling@apple.com622b1a42013-08-30 14:30:12 +0000294 FrameView* frameView = node->document().view();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000295 if (!frameView)
296 return true;
297
hyatt@apple.coma61b8a32011-04-06 18:20:52 +0000298 ASSERT(!frameView->needsLayout());
299
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000300 LayoutRect containerViewportRect = frameView->visibleContentRect();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000301 // 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() - Scrollbar::pixelsPerLineStep());
308 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
309 break;
310 case FocusDirectionRight:
311 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
312 break;
313 case FocusDirectionUp:
314 containerViewportRect.setY(containerViewportRect.y() - Scrollbar::pixelsPerLineStep());
315 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
316 break;
317 case FocusDirectionDown:
318 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
319 break;
320 default:
321 break;
322 }
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000323
324 RenderObject* render = node->renderer();
325 if (!render)
326 return true;
327
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000328 LayoutRect rect(render->absoluteClippedOverflowRect());
tonikitoo@webkit.org453ceea2010-04-15 19:33:59 +0000329 if (rect.isEmpty())
330 return true;
331
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000332 return !containerViewportRect.intersects(rect);
333}
334
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000335bool scrollInDirection(Frame* frame, FocusDirection direction)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000336{
tonikitoo@webkit.orgaf84fda02010-12-14 06:51:52 +0000337 ASSERT(frame);
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000338
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000339 if (frame && canScrollInDirection(frame->document(), direction)) {
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000340 LayoutUnit dx = 0;
341 LayoutUnit dy = 0;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000342 switch (direction) {
343 case FocusDirectionLeft:
344 dx = - Scrollbar::pixelsPerLineStep();
345 break;
346 case FocusDirectionRight:
347 dx = Scrollbar::pixelsPerLineStep();
348 break;
349 case FocusDirectionUp:
350 dy = - Scrollbar::pixelsPerLineStep();
351 break;
352 case FocusDirectionDown:
353 dy = Scrollbar::pixelsPerLineStep();
354 break;
355 default:
356 ASSERT_NOT_REACHED();
357 return false;
358 }
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000359
leviw@chromium.orga0fe0852011-11-12 02:53:52 +0000360 frame->view()->scrollBy(IntSize(dx, dy));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000361 return true;
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000362 }
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000363 return false;
364}
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000365
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000366bool scrollInDirection(Node* container, FocusDirection direction)
367{
tonikitoo@webkit.orgaf84fda02010-12-14 06:51:52 +0000368 ASSERT(container);
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000369 if (is<Document>(*container))
cdumez@apple.comb69c7942014-09-29 22:23:20 +0000370 return scrollInDirection(downcast<Document>(*container).frame(), direction);
tonikitoo@webkit.org8f86fed2010-06-17 00:57:15 +0000371
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000372 if (!container->renderBox())
373 return false;
374
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000375 if (canScrollInDirection(container, direction)) {
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000376 LayoutUnit dx = 0;
377 LayoutUnit dy = 0;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000378 switch (direction) {
379 case FocusDirectionLeft:
kseo@webkit.org2acf0f92013-07-30 13:49:06 +0000380 dx = - std::min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollLeft());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000381 break;
382 case FocusDirectionRight:
383 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
kseo@webkit.org2acf0f92013-07-30 13:49:06 +0000384 dx = std::min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000385 break;
386 case FocusDirectionUp:
kseo@webkit.org2acf0f92013-07-30 13:49:06 +0000387 dy = - std::min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollTop());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000388 break;
389 case FocusDirectionDown:
390 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
kseo@webkit.org2acf0f92013-07-30 13:49:06 +0000391 dy = std::min<LayoutUnit>(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000392 break;
393 default:
394 ASSERT_NOT_REACHED();
395 return false;
396 }
397
jchaffraix@webkit.orgf51b0642012-06-20 17:24:24 +0000398 container->renderBox()->enclosingLayer()->scrollByRecursively(IntSize(dx, dy));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000399 return true;
400 }
tonikitoo@webkit.orgaf84fda02010-12-14 06:51:52 +0000401
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000402 return false;
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000403}
404
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000405static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b)
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000406{
407 if (!a.intersects(b) || a.contains(b) || b.contains(a))
408 return;
409
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000410 LayoutUnit deflateFactor = -fudgeFactor();
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000411
412 // Avoid negative width or height values.
tonikitoo@webkit.org346f8c02010-05-09 03:05:46 +0000413 if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0))
414 a.inflate(deflateFactor);
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000415
tonikitoo@webkit.org346f8c02010-05-09 03:05:46 +0000416 if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0))
417 b.inflate(deflateFactor);
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000418}
419
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000420bool isScrollableNode(const Node* node)
tonikitoo@webkit.orgf5586a52010-06-14 19:10:59 +0000421{
422 if (!node)
423 return false;
darin@apple.com7043e1b2016-06-02 04:01:35 +0000424 ASSERT(!node->isDocumentNode());
425 auto* renderer = node->renderer();
426 return is<RenderBox>(renderer) && downcast<RenderBox>(*renderer).canBeScrolledAndHasScrollableArea() && node->hasChildNodes();
tonikitoo@webkit.orgf5586a52010-06-14 19:10:59 +0000427}
428
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000429Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection direction, Node* node)
430{
431 ASSERT(node);
432 Node* parent = node;
433 do {
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000434 if (is<Document>(*parent))
cdumez@apple.comb69c7942014-09-29 22:23:20 +0000435 parent = downcast<Document>(*parent).document().frame()->ownerElement();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000436 else
437 parent = parent->parentNode();
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000438 } while (parent && !canScrollInDirection(parent, direction) && !is<Document>(*parent));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000439
440 return parent;
441}
442
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000443bool canScrollInDirection(const Node* container, FocusDirection direction)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000444{
445 ASSERT(container);
commit-queue@webkit.orgd62060e2013-07-19 19:21:09 +0000446
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000447 if (is<HTMLSelectElement>(*container))
commit-queue@webkit.orgd62060e2013-07-19 19:21:09 +0000448 return false;
449
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000450 if (is<Document>(*container))
cdumez@apple.comb69c7942014-09-29 22:23:20 +0000451 return canScrollInDirection(downcast<Document>(*container).frame(), direction);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000452
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000453 if (!isScrollableNode(container))
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000454 return false;
455
456 switch (direction) {
457 case FocusDirectionLeft:
akling@apple.com827be9c2013-10-29 02:58:43 +0000458 return (container->renderer()->style().overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000459 case FocusDirectionUp:
akling@apple.com827be9c2013-10-29 02:58:43 +0000460 return (container->renderer()->style().overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000461 case FocusDirectionRight:
akling@apple.com827be9c2013-10-29 02:58:43 +0000462 return (container->renderer()->style().overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000463 case FocusDirectionDown:
akling@apple.com827be9c2013-10-29 02:58:43 +0000464 return (container->renderer()->style().overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000465 default:
466 ASSERT_NOT_REACHED();
467 return false;
468 }
469}
470
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000471bool canScrollInDirection(const Frame* frame, FocusDirection direction)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000472{
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;
bdakin@apple.com9d3bf062013-03-28 00:42:35 +0000482 LayoutSize size = frame->view()->totalContentsSize();
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000483 LayoutPoint scrollPosition = frame->view()->scrollPosition();
benjamin@webkit.org25bc57c2014-03-10 21:25:24 +0000484 LayoutRect rect = frame->view()->unobscuredContentRectIncludingScrollbars();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000485
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000486 // FIXME: wrong in RTL documents.
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000487 switch (direction) {
488 case FocusDirectionLeft:
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000489 return scrollPosition.x() > 0;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000490 case FocusDirectionUp:
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000491 return scrollPosition.y() > 0;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000492 case FocusDirectionRight:
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000493 return rect.width() + scrollPosition.x() < size.width();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000494 case FocusDirectionDown:
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000495 return rect.height() + scrollPosition.y() < size.height();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000496 default:
497 ASSERT_NOT_REACHED();
498 return false;
499 }
500}
501
mmaxfield@apple.com6ebeece2014-12-09 21:03:54 +0000502// FIXME: This is completely broken. This should be deleted and callers should be calling ScrollView::contentsToWindow() instead.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000503static LayoutRect rectToAbsoluteCoordinates(Frame* initialFrame, const LayoutRect& initialRect)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000504{
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000505 LayoutRect rect = initialRect;
darin@apple.comfed4d162013-08-25 02:28:06 +0000506 for (Frame* frame = initialFrame; frame; frame = frame->tree().parent()) {
inferno@chromium.org9aa9e8d2013-03-14 16:08:06 +0000507 if (Element* element = frame->ownerElement()) {
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000508 do {
509 rect.move(element->offsetLeft(), element->offsetTop());
510 } while ((element = element->offsetParent()));
simon.fraser@apple.comdb0e0672015-12-30 00:29:36 +0000511 rect.moveBy((-frame->view()->scrollPosition()));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000512 }
513 }
514 return rect;
515}
516
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000517LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000518{
akling@apple.com622b1a42013-08-30 14:30:12 +0000519 ASSERT(node && node->renderer() && !node->document().view()->needsLayout());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000520
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000521 if (is<Document>(*node))
cdumez@apple.comb69c7942014-09-29 22:23:20 +0000522 return frameRectInAbsoluteCoordinates(downcast<Document>(*node).frame());
mmaxfield@apple.com6ebeece2014-12-09 21:03:54 +0000523
mmaxfield@apple.com182fd5c2014-12-15 20:15:05 +0000524 LayoutRect rect;
525 if (RenderObject* renderer = node->renderer())
526 rect = rectToAbsoluteCoordinates(node->document().frame(), renderer->absoluteBoundingBoxRect());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000527
528 // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
529 // the rect of the focused element.
530 if (ignoreBorder) {
akling@apple.com827be9c2013-10-29 02:58:43 +0000531 rect.move(node->renderer()->style().borderLeftWidth(), node->renderer()->style().borderTopWidth());
532 rect.setWidth(rect.width() - node->renderer()->style().borderLeftWidth() - node->renderer()->style().borderRightWidth());
533 rect.setHeight(rect.height() - node->renderer()->style().borderTopWidth() - node->renderer()->style().borderBottomWidth());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000534 }
535 return rect;
536}
537
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000538LayoutRect frameRectInAbsoluteCoordinates(Frame* frame)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000539{
540 return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
541}
542
543// This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect.
544// The line between those 2 points is the closest distance between the 2 rects.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000545void entryAndExitPointsForDirection(FocusDirection direction, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000546{
547 switch (direction) {
548 case FocusDirectionLeft:
549 exitPoint.setX(startingRect.x());
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000550 entryPoint.setX(potentialRect.maxX());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000551 break;
552 case FocusDirectionUp:
553 exitPoint.setY(startingRect.y());
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000554 entryPoint.setY(potentialRect.maxY());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000555 break;
556 case FocusDirectionRight:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000557 exitPoint.setX(startingRect.maxX());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000558 entryPoint.setX(potentialRect.x());
559 break;
560 case FocusDirectionDown:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000561 exitPoint.setY(startingRect.maxY());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000562 entryPoint.setY(potentialRect.y());
563 break;
564 default:
565 ASSERT_NOT_REACHED();
566 }
567
568 switch (direction) {
569 case FocusDirectionLeft:
570 case FocusDirectionRight:
571 if (below(startingRect, potentialRect)) {
572 exitPoint.setY(startingRect.y());
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000573 entryPoint.setY(potentialRect.maxY());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000574 } else if (below(potentialRect, startingRect)) {
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000575 exitPoint.setY(startingRect.maxY());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000576 entryPoint.setY(potentialRect.y());
577 } else {
andersca@apple.comff9adb82013-10-25 01:15:36 +0000578 exitPoint.setY(std::max(startingRect.y(), potentialRect.y()));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000579 entryPoint.setY(exitPoint.y());
580 }
581 break;
582 case FocusDirectionUp:
583 case FocusDirectionDown:
584 if (rightOf(startingRect, potentialRect)) {
585 exitPoint.setX(startingRect.x());
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000586 entryPoint.setX(potentialRect.maxX());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000587 } else if (rightOf(potentialRect, startingRect)) {
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000588 exitPoint.setX(startingRect.maxX());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000589 entryPoint.setX(potentialRect.x());
590 } else {
andersca@apple.comff9adb82013-10-25 01:15:36 +0000591 exitPoint.setX(std::max(startingRect.x(), potentialRect.x()));
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000592 entryPoint.setX(exitPoint.x());
593 }
594 break;
595 default:
596 ASSERT_NOT_REACHED();
597 }
598}
599
yael.aharon@nokia.com4a4c87b2011-02-18 21:05:38 +0000600bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate)
601{
602 if (firstCandidate.isNull() || secondCandidate.isNull())
603 return false;
604
605 if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer())
606 return false;
607
608 if (!firstCandidate.rect.intersects(secondCandidate.rect))
609 return false;
610
cdumez@apple.coma9c60c92014-10-02 19:39:41 +0000611 if (is<HTMLAreaElement>(*firstCandidate.focusableNode) || is<HTMLAreaElement>(*secondCandidate.focusableNode))
yael.aharon@nokia.com4a4c87b2011-02-18 21:05:38 +0000612 return false;
613
614 if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline())
615 return false;
616
617 if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock())
618 return false;
619
620 return true;
621}
622
commit-queue@webkit.org3a7e0bb2013-08-05 13:09:19 +0000623// Consider only those nodes as candidate which are exactly in the focus-direction.
624// e.g. If we are moving down then the nodes that are above current focused node should be considered as invalid.
625bool isValidCandidate(FocusDirection direction, const FocusCandidate& current, FocusCandidate& candidate)
626{
627 LayoutRect currentRect = current.rect;
628 LayoutRect candidateRect = candidate.rect;
629
630 switch (direction) {
631 case FocusDirectionLeft:
632 return candidateRect.x() < currentRect.maxX();
633 case FocusDirectionUp:
634 return candidateRect.y() < currentRect.maxY();
635 case FocusDirectionRight:
636 return candidateRect.maxX() > currentRect.x();
637 case FocusDirectionDown:
638 return candidateRect.maxY() > currentRect.y();
639 default:
640 ASSERT_NOT_REACHED();
641 }
642 return false;
643}
644
tonikitoo@webkit.orgd13935e2010-12-14 07:17:23 +0000645void distanceDataForNode(FocusDirection direction, const FocusCandidate& current, FocusCandidate& candidate)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000646{
yael.aharon@nokia.com4a4c87b2011-02-18 21:05:38 +0000647 if (areElementsOnSameLine(current, candidate)) {
648 if ((direction == FocusDirectionUp && current.rect.y() > candidate.rect.y()) || (direction == FocusDirectionDown && candidate.rect.y() > current.rect.y())) {
649 candidate.distance = 0;
650 candidate.alignment = Full;
651 return;
652 }
653 }
654
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000655 LayoutRect nodeRect = candidate.rect;
656 LayoutRect currentRect = current.rect;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000657 deflateIfOverlapped(currentRect, nodeRect);
658
659 if (!isRectInDirection(direction, currentRect, nodeRect))
660 return;
661
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000662 LayoutPoint exitPoint;
663 LayoutPoint entryPoint;
664 LayoutUnit sameAxisDistance = 0;
665 LayoutUnit otherAxisDistance = 0;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000666 entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint);
667
668 switch (direction) {
669 case FocusDirectionLeft:
670 sameAxisDistance = exitPoint.x() - entryPoint.x();
leviw@chromium.org3957b452012-05-01 00:06:37 +0000671 otherAxisDistance = absoluteValue(exitPoint.y() - entryPoint.y());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000672 break;
673 case FocusDirectionUp:
674 sameAxisDistance = exitPoint.y() - entryPoint.y();
leviw@chromium.org3957b452012-05-01 00:06:37 +0000675 otherAxisDistance = absoluteValue(exitPoint.x() - entryPoint.x());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000676 break;
677 case FocusDirectionRight:
678 sameAxisDistance = entryPoint.x() - exitPoint.x();
leviw@chromium.org3957b452012-05-01 00:06:37 +0000679 otherAxisDistance = absoluteValue(entryPoint.y() - exitPoint.y());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000680 break;
681 case FocusDirectionDown:
682 sameAxisDistance = entryPoint.y() - exitPoint.y();
leviw@chromium.org3957b452012-05-01 00:06:37 +0000683 otherAxisDistance = absoluteValue(entryPoint.x() - exitPoint.x());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000684 break;
685 default:
686 ASSERT_NOT_REACHED();
687 return;
688 }
689
eae@chromium.orgfdb2f382012-03-19 21:05:07 +0000690 float x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x());
691 float y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y());
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000692
eae@chromium.orgfdb2f382012-03-19 21:05:07 +0000693 float euclidianDistance = sqrt(x + y);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000694
695 // Loosely based on http://www.w3.org/TR/WICD/#focus-handling
696 // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap)
697
698 float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance;
699 candidate.distance = roundf(distance);
akling@apple.com622b1a42013-08-30 14:30:12 +0000700 LayoutSize viewSize = candidate.visibleNode->document().page()->mainFrame().view()->visibleContentRect().size();
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000701 candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize);
702}
703
yael.aharon@nokia.coma886edf2010-11-23 12:57:17 +0000704bool canBeScrolledIntoView(FocusDirection direction, const FocusCandidate& candidate)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000705{
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000706 ASSERT(candidate.visibleNode && candidate.isOffscreen);
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000707 LayoutRect candidateRect = candidate.rect;
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000708 for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) {
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000709 LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000710 if (!candidateRect.intersects(parentRect)) {
akling@apple.com827be9c2013-10-29 02:58:43 +0000711 if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style().overflowX() == OHIDDEN)
712 || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style().overflowY() == OHIDDEN))
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000713 return false;
714 }
715 if (parentNode == candidate.enclosingScrollableBox)
tonikitoo@webkit.org3d623722010-12-28 21:57:11 +0000716 return canScrollInDirection(parentNode, direction);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000717 }
718 return true;
719}
720
721// The starting rect is the rect of the focused node, in document coordinates.
722// Compose a virtual starting rect if there is no focused node or if it is off screen.
723// The virtual rect is the edge of the container or frame. We select which
724// edge depending on the direction of the navigation.
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000725LayoutRect virtualRectForDirection(FocusDirection direction, const LayoutRect& startingRect, LayoutUnit width)
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000726{
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000727 LayoutRect virtualStartingRect = startingRect;
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000728 switch (direction) {
729 case FocusDirectionLeft:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000730 virtualStartingRect.setX(virtualStartingRect.maxX() - width);
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000731 virtualStartingRect.setWidth(width);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000732 break;
733 case FocusDirectionUp:
hyatt@apple.comaa0cba02011-02-01 21:39:47 +0000734 virtualStartingRect.setY(virtualStartingRect.maxY() - width);
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000735 virtualStartingRect.setHeight(width);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000736 break;
737 case FocusDirectionRight:
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000738 virtualStartingRect.setWidth(width);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000739 break;
740 case FocusDirectionDown:
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000741 virtualStartingRect.setHeight(width);
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000742 break;
743 default:
744 ASSERT_NOT_REACHED();
745 }
746
747 return virtualStartingRect;
748}
749
eae@chromium.org4ecf4182011-08-18 00:49:01 +0000750LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement* area, FocusDirection direction)
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000751{
tonikitoo@webkit.org237c1ea2010-12-13 05:52:40 +0000752 ASSERT(area);
753 ASSERT(area->imageElement());
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000754 // Area elements tend to overlap more than other focusable elements. We flatten the rect of the area elements
755 // to minimize the effect of overlapping areas.
akling@apple.com622b1a42013-08-30 14:30:12 +0000756 LayoutRect rect = virtualRectForDirection(direction, rectToAbsoluteCoordinates(area->document().frame(), area->computeRect(area->imageElement()->renderer())), 1);
yael.aharon@nokia.comf1b5ab42010-12-07 19:04:10 +0000757 return rect;
758}
759
tonikitoo@webkit.org36a7cfb2010-12-14 06:51:41 +0000760HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate)
761{
cdumez@apple.comcd131532014-09-27 01:32:34 +0000762 return candidate.isFrameOwnerElement() ? downcast<HTMLFrameOwnerElement>(candidate.visibleNode) : nullptr;
763}
yael.aharon@nokia.com0a818c22010-11-22 13:49:09 +0000764
tonikitoo@webkit.org660f8ff2010-03-04 20:19:44 +0000765} // namespace WebCore