blob: 60e14b3ebd059915d8164c4f7f485de2d5e15b14 [file] [log] [blame]
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +01001/*
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
Torne (Richard Coles)5d92fed2014-06-20 14:52:37 +010032#include "core/HTMLNames.h"
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000033#include "core/frame/FrameView.h"
34#include "core/frame/LocalFrame.h"
35#include "core/frame/Settings.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010036#include "core/html/HTMLAreaElement.h"
Torne (Richard Coles)76c265b2014-06-25 10:31:22 +010037#include "core/html/HTMLFrameOwnerElement.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010038#include "core/html/HTMLImageElement.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010039#include "core/page/FrameTree.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010040#include "core/page/Page.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010041#include "core/rendering/RenderLayer.h"
Torne (Richard Coles)1e202182013-10-18 15:46:42 +010042#include "platform/geometry/IntRect.h"
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010043
44namespace WebCore {
45
Torne (Richard Coles)09380292014-02-21 12:17:33 +000046using namespace HTMLNames;
47
48static RectsAlignment alignmentForRects(FocusType, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize);
49static bool areRectsFullyAligned(FocusType, const LayoutRect&, const LayoutRect&);
50static bool areRectsPartiallyAligned(FocusType, const LayoutRect&, const LayoutRect&);
51static bool areRectsMoreThanFullScreenApart(FocusType, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize);
52static bool isRectInDirection(FocusType, const LayoutRect&, const LayoutRect&);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010053static void deflateIfOverlapped(LayoutRect&, LayoutRect&);
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000054static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect&);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010055static bool isScrollableNode(const Node*);
56
Torne (Richard Coles)09380292014-02-21 12:17:33 +000057FocusCandidate::FocusCandidate(Node* node, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010058 : 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
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000071 if (isHTMLAreaElement(*node)) {
72 HTMLAreaElement& area = toHTMLAreaElement(*node);
73 HTMLImageElement* image = area.imageElement();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010074 if (!image || !image->renderer())
75 return;
76
77 visibleNode = image;
Torne (Richard Coles)09380292014-02-21 12:17:33 +000078 rect = virtualRectForAreaElementAndDirection(area, type);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010079 } 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);
Torne (Richard Coles)09380292014-02-21 12:17:33 +000089 isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, type);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010090}
91
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +000092bool isSpatialNavigationEnabled(const LocalFrame* frame)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010093{
94 return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled());
95}
96
Torne (Richard Coles)09380292014-02-21 12:17:33 +000097static RectsAlignment alignmentForRects(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +010098{
99 // If we found a node in full alignment, but it is too far away, ignore it.
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000100 if (areRectsMoreThanFullScreenApart(type, curRect, targetRect, viewSize))
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100101 return None;
102
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000103 if (areRectsFullyAligned(type, curRect, targetRect))
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100104 return Full;
105
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000106 if (areRectsPartiallyAligned(type, curRect, targetRect))
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100107 return Partial;
108
109 return None;
110}
111
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000112static inline bool isHorizontalMove(FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100113{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000114 return type == FocusTypeLeft || type == FocusTypeRight;
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100115}
116
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000117static inline LayoutUnit start(FocusType type, const LayoutRect& rect)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100118{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000119 return isHorizontalMove(type) ? rect.y() : rect.x();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100120}
121
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000122static inline LayoutUnit middle(FocusType type, const LayoutRect& rect)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100123{
124 LayoutPoint center(rect.center());
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000125 return isHorizontalMove(type) ? center.y(): center.x();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100126}
127
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000128static inline LayoutUnit end(FocusType type, const LayoutRect& rect)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100129{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000130 return isHorizontalMove(type) ? rect.maxY() : rect.maxX();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100131}
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.
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000140static bool areRectsFullyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100141{
142 LayoutUnit aStart, bStart, aEnd, bEnd;
143
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000144 switch (type) {
145 case FocusTypeLeft:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100146 aStart = a.x();
Ben Murdocha9984bf2014-04-10 11:22:39 +0100147 bEnd = b.x();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100148 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000149 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100150 aStart = b.x();
Ben Murdocha9984bf2014-04-10 11:22:39 +0100151 bEnd = a.x();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100152 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000153 case FocusTypeUp:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100154 aStart = a.y();
155 bEnd = b.y();
156 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000157 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100158 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
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000169 aStart = start(type, a);
170 bStart = start(type, b);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100171
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000172 LayoutUnit aMiddle = middle(type, a);
173 LayoutUnit bMiddle = middle(type, b);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100174
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000175 aEnd = end(type, a);
176 bEnd = end(type, b);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100177
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
Ben Murdocha9984bf2014-04-10 11:22:39 +0100191 return (bMiddle >= aStart && bMiddle <= aEnd) // (1)
192 || (aMiddle >= bStart && aMiddle <= bEnd); // (2)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100193}
194
Ben Murdocha9984bf2014-04-10 11:22:39 +0100195// 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.
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100200// * a = Current focused node's rect.
201// * b = Focus candidate node's rect.
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000202static bool areRectsPartiallyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100203{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000204 LayoutUnit aStart = start(type, a);
205 LayoutUnit bStart = start(type, b);
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000206 LayoutUnit aEnd = end(type, a);
207 LayoutUnit bEnd = end(type, b);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100208
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.
Ben Murdocha9984bf2014-04-10 11:22:39 +0100224 return (bStart >= aStart && bStart <= aEnd)
225 || (bEnd >= aStart && bEnd <= aEnd);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100226}
227
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000228static bool areRectsMoreThanFullScreenApart(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100229{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000230 ASSERT(isRectInDirection(type, curRect, targetRect));
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100231
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000232 switch (type) {
233 case FocusTypeLeft:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100234 return curRect.x() - targetRect.maxX() > viewSize.width();
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000235 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100236 return targetRect.x() - curRect.maxX() > viewSize.width();
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000237 case FocusTypeUp:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100238 return curRect.y() - targetRect.maxY() > viewSize.height();
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000239 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100240 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.
Ben Murdocha9984bf2014-04-10 11:22:39 +0100248// For overlapping rects, |a| is considered to be below |b|
249// if both edges of |a| are below the respective ones of |b|
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100250static inline bool below(const LayoutRect& a, const LayoutRect& b)
251{
Ben Murdocha9984bf2014-04-10 11:22:39 +0100252 return a.y() >= b.maxY()
253 || (a.y() >= b.y() && a.maxY() > b.maxY());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100254}
255
256// Return true if rect |a| is on the right of |b|. False otherwise.
Ben Murdocha9984bf2014-04-10 11:22:39 +0100257// 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|
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100259static inline bool rightOf(const LayoutRect& a, const LayoutRect& b)
260{
Ben Murdocha9984bf2014-04-10 11:22:39 +0100261 return a.x() >= b.maxX()
262 || (a.x() >= b.x() && a.maxX() > b.maxX());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100263}
264
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000265static bool isRectInDirection(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100266{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000267 switch (type) {
268 case FocusTypeLeft:
Ben Murdocha9984bf2014-04-10 11:22:39 +0100269 return rightOf(curRect, targetRect);
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000270 case FocusTypeRight:
Ben Murdocha9984bf2014-04-10 11:22:39 +0100271 return rightOf(targetRect, curRect);
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000272 case FocusTypeUp:
Ben Murdocha9984bf2014-04-10 11:22:39 +0100273 return below(curRect, targetRect);
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000274 case FocusTypeDown:
Ben Murdocha9984bf2014-04-10 11:22:39 +0100275 return below(targetRect, curRect);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100276 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.
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000285bool hasOffscreenRect(Node* node, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100286{
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.
Torne (Richard Coles)8abfc582013-09-12 12:10:38 +0100290 FrameView* frameView = node->document().view();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100291 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.
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000301 switch (type) {
302 case FocusTypeLeft:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100303 containerViewportRect.setX(containerViewportRect.x() - ScrollableArea::pixelsPerLineStep());
304 containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100305 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000306 case FocusTypeRight:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100307 containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100308 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000309 case FocusTypeUp:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100310 containerViewportRect.setY(containerViewportRect.y() - ScrollableArea::pixelsPerLineStep());
311 containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100312 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000313 case FocusTypeDown:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100314 containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100315 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
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000331bool scrollInDirection(LocalFrame* frame, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100332{
333 ASSERT(frame);
334
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000335 if (frame && canScrollInDirection(frame->document(), type)) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100336 LayoutUnit dx = 0;
337 LayoutUnit dy = 0;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000338 switch (type) {
339 case FocusTypeLeft:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100340 dx = - ScrollableArea::pixelsPerLineStep();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100341 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000342 case FocusTypeRight:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100343 dx = ScrollableArea::pixelsPerLineStep();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100344 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000345 case FocusTypeUp:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100346 dy = - ScrollableArea::pixelsPerLineStep();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100347 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000348 case FocusTypeDown:
Torne (Richard Coles)f5e4ad52013-08-05 13:57:57 +0100349 dy = ScrollableArea::pixelsPerLineStep();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100350 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
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000362bool scrollInDirection(Node* container, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100363{
364 ASSERT(container);
365 if (container->isDocumentNode())
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000366 return scrollInDirection(toDocument(container)->frame(), type);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100367
368 if (!container->renderBox())
369 return false;
370
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000371 if (canScrollInDirection(container, type)) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100372 LayoutUnit dx = 0;
373 LayoutUnit dy = 0;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000374 switch (type) {
375 case FocusTypeLeft:
Torne (Richard Coles)e1f1df52013-08-23 16:39:30 +0100376 dx = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollLeft());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100377 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000378 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100379 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
Torne (Richard Coles)e1f1df52013-08-23 16:39:30 +0100380 dx = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100381 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000382 case FocusTypeUp:
Torne (Richard Coles)e1f1df52013-08-23 16:39:30 +0100383 dy = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollTop());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100384 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000385 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100386 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
Torne (Richard Coles)e1f1df52013-08-23 16:39:30 +0100387 dy = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100388 break;
389 default:
390 ASSERT_NOT_REACHED();
391 return false;
392 }
393
Torne (Richard Coles)bfe35902013-10-22 16:41:51 +0100394 container->renderBox()->scrollByRecursively(IntSize(dx, dy));
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100395 return true;
396 }
397
398 return false;
399}
400
401static 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
416bool isScrollableNode(const Node* node)
417{
418 ASSERT(!node->isDocumentNode());
419
420 if (!node)
421 return false;
422
423 if (RenderObject* renderer = node->renderer())
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000424 return renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea() && node->hasChildren();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100425
426 return false;
427}
428
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000429Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusType type, Node* node)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100430{
431 ASSERT(node);
432 Node* parent = node;
433 do {
Torne (Richard Coles)f6b7aed2014-06-09 12:01:17 +0100434 // FIXME: Spatial navigation is broken for OOPI.
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100435 if (parent->isDocumentNode())
Torne (Richard Coles)f6b7aed2014-06-09 12:01:17 +0100436 parent = toDocument(parent)->frame()->deprecatedLocalOwner();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100437 else
Torne (Richard Coles)c0e19a62013-08-30 15:15:11 +0100438 parent = parent->parentOrShadowHostNode();
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000439 } while (parent && !canScrollInDirection(parent, type) && !parent->isDocumentNode());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100440
441 return parent;
442}
443
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000444bool canScrollInDirection(const Node* container, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100445{
446 ASSERT(container);
447 if (container->isDocumentNode())
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000448 return canScrollInDirection(toDocument(container)->frame(), type);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100449
450 if (!isScrollableNode(container))
451 return false;
452
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000453 switch (type) {
454 case FocusTypeLeft:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100455 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000456 case FocusTypeUp:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100457 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000458 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100459 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000460 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100461 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
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000468bool canScrollInDirection(const LocalFrame* frame, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100469{
470 if (!frame->view())
471 return false;
472 ScrollbarMode verticalMode;
473 ScrollbarMode horizontalMode;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000474 frame->view()->calculateScrollbarModesForLayoutAndSetViewportRenderer(horizontalMode, verticalMode);
475 if ((type == FocusTypeLeft || type == FocusTypeRight) && ScrollbarAlwaysOff == horizontalMode)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100476 return false;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000477 if ((type == FocusTypeUp || type == FocusTypeDown) && ScrollbarAlwaysOff == verticalMode)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100478 return false;
479 LayoutSize size = frame->view()->contentsSize();
480 LayoutSize offset = frame->view()->scrollOffset();
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000481 LayoutRect rect = frame->view()->visibleContentRect(IncludeScrollbars);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100482
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000483 switch (type) {
484 case FocusTypeLeft:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100485 return offset.width() > 0;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000486 case FocusTypeUp:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100487 return offset.height() > 0;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000488 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100489 return rect.width() + offset.width() < size.width();
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000490 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100491 return rect.height() + offset.height() < size.height();
492 default:
493 ASSERT_NOT_REACHED();
494 return false;
495 }
496}
497
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000498static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect& initialRect)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100499{
500 LayoutRect rect = initialRect;
Torne (Richard Coles)f6b7aed2014-06-09 12:01:17 +0100501 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()) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100506 do {
507 rect.move(element->offsetLeft(), element->offsetTop());
508 } while ((element = element->offsetParent()));
Torne (Richard Coles)f6b7aed2014-06-09 12:01:17 +0100509 rect.move((-toLocalFrame(frame)->view()->scrollOffset()));
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100510 }
511 }
512 return rect;
513}
514
515LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
516{
Torne (Richard Coles)8abfc582013-09-12 12:10:38 +0100517 ASSERT(node && node->renderer() && !node->document().view()->needsLayout());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100518
519 if (node->isDocumentNode())
520 return frameRectInAbsoluteCoordinates(toDocument(node)->frame());
Torne (Richard Coles)8abfc582013-09-12 12:10:38 +0100521 LayoutRect rect = rectToAbsoluteCoordinates(node->document().frame(), node->boundingBox());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100522
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
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000533LayoutRect frameRectInAbsoluteCoordinates(LocalFrame* frame)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100534{
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.
Ben Murdocha9984bf2014-04-10 11:22:39 +0100540// Takes care of overlapping rects, defining points so that the distance between them
541// is zero where necessary
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000542void entryAndExitPointsForDirection(FocusType type, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100543{
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000544 switch (type) {
545 case FocusTypeLeft:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100546 exitPoint.setX(startingRect.x());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100547 if (potentialRect.maxX() < startingRect.x())
548 entryPoint.setX(potentialRect.maxX());
549 else
550 entryPoint.setX(startingRect.x());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100551 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000552 case FocusTypeUp:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100553 exitPoint.setY(startingRect.y());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100554 if (potentialRect.maxY() < startingRect.y())
555 entryPoint.setY(potentialRect.maxY());
556 else
557 entryPoint.setY(startingRect.y());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100558 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000559 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100560 exitPoint.setX(startingRect.maxX());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100561 if (potentialRect.x() > startingRect.maxX())
562 entryPoint.setX(potentialRect.x());
563 else
564 entryPoint.setX(startingRect.maxX());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100565 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000566 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100567 exitPoint.setY(startingRect.maxY());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100568 if (potentialRect.y() > startingRect.maxY())
569 entryPoint.setY(potentialRect.y());
570 else
571 entryPoint.setY(startingRect.maxY());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100572 break;
573 default:
574 ASSERT_NOT_REACHED();
575 }
576
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000577 switch (type) {
578 case FocusTypeLeft:
579 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100580 if (below(startingRect, potentialRect)) {
581 exitPoint.setY(startingRect.y());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100582 if (potentialRect.maxY() < startingRect.y())
583 entryPoint.setY(potentialRect.maxY());
584 else
585 entryPoint.setY(startingRect.y());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100586 } else if (below(potentialRect, startingRect)) {
587 exitPoint.setY(startingRect.maxY());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100588 if (potentialRect.y() > startingRect.maxY())
589 entryPoint.setY(potentialRect.y());
590 else
591 entryPoint.setY(startingRect.maxY());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100592 } else {
593 exitPoint.setY(max(startingRect.y(), potentialRect.y()));
594 entryPoint.setY(exitPoint.y());
595 }
596 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000597 case FocusTypeUp:
598 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100599 if (rightOf(startingRect, potentialRect)) {
600 exitPoint.setX(startingRect.x());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100601 if (potentialRect.maxX() < startingRect.x())
602 entryPoint.setX(potentialRect.maxX());
603 else
604 entryPoint.setX(startingRect.x());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100605 } else if (rightOf(potentialRect, startingRect)) {
606 exitPoint.setX(startingRect.maxX());
Ben Murdocha9984bf2014-04-10 11:22:39 +0100607 if (potentialRect.x() > startingRect.maxX())
608 entryPoint.setX(potentialRect.x());
609 else
610 entryPoint.setX(startingRect.maxX());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100611 } 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
621bool 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
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000632 if (isHTMLAreaElement(*firstCandidate.focusableNode) || isHTMLAreaElement(*secondCandidate.focusableNode))
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100633 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
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000644void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCandidate& candidate)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100645{
646 if (areElementsOnSameLine(current, candidate)) {
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000647 if ((type == FocusTypeUp && current.rect.y() > candidate.rect.y()) || (type == FocusTypeDown && candidate.rect.y() > current.rect.y())) {
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100648 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
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000658 if (!isRectInDirection(type, currentRect, nodeRect))
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100659 return;
660
661 LayoutPoint exitPoint;
662 LayoutPoint entryPoint;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000663 entryAndExitPointsForDirection(type, currentRect, nodeRect, exitPoint, entryPoint);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100664
Ben Murdocha9984bf2014-04-10 11:22:39 +0100665 LayoutUnit xAxis = exitPoint.x() - entryPoint.x();
666 LayoutUnit yAxis = exitPoint.y() - entryPoint.y();
667
668 LayoutUnit navigationAxisDistance;
669 LayoutUnit orthogonalAxisDistance;
670
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000671 switch (type) {
672 case FocusTypeLeft:
Ben Murdocha9984bf2014-04-10 11:22:39 +0100673 case FocusTypeRight:
674 navigationAxisDistance = xAxis.abs();
675 orthogonalAxisDistance = yAxis.abs();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100676 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000677 case FocusTypeUp:
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000678 case FocusTypeDown:
Ben Murdocha9984bf2014-04-10 11:22:39 +0100679 navigationAxisDistance = yAxis.abs();
680 orthogonalAxisDistance = xAxis.abs();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100681 break;
682 default:
683 ASSERT_NOT_REACHED();
684 return;
685 }
686
Ben Murdocha9984bf2014-04-10 11:22:39 +0100687 double euclidianDistancePow2 = (xAxis * xAxis + yAxis * yAxis).toDouble();
688 LayoutRect intersectionRect = intersection(currentRect, nodeRect);
689 double overlap = (intersectionRect.width() * intersectionRect.height()).toDouble();
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100690
Ben Murdocha9984bf2014-04-10 11:22:39 +0100691 // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling
692 candidate.distance = sqrt(euclidianDistancePow2) + navigationAxisDistance+ orthogonalAxisDistance * 2 - sqrt(overlap);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100693
Torne (Richard Coles)5d92fed2014-06-20 14:52:37 +0100694 LayoutSize viewSize = candidate.visibleNode->document().page()->deprecatedLocalMainFrame()->view()->visibleContentRect().size();
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000695 candidate.alignment = alignmentForRects(type, currentRect, nodeRect, viewSize);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100696}
697
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000698bool canBeScrolledIntoView(FocusType type, const FocusCandidate& candidate)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100699{
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)) {
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000705 if (((type == FocusTypeLeft || type == FocusTypeRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
706 || ((type == FocusTypeUp || type == FocusTypeDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100707 return false;
708 }
709 if (parentNode == candidate.enclosingScrollableBox)
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000710 return canScrollInDirection(parentNode, type);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100711 }
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.
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000719LayoutRect virtualRectForDirection(FocusType type, const LayoutRect& startingRect, LayoutUnit width)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100720{
721 LayoutRect virtualStartingRect = startingRect;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000722 switch (type) {
723 case FocusTypeLeft:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100724 virtualStartingRect.setX(virtualStartingRect.maxX() - width);
725 virtualStartingRect.setWidth(width);
726 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000727 case FocusTypeUp:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100728 virtualStartingRect.setY(virtualStartingRect.maxY() - width);
729 virtualStartingRect.setHeight(width);
730 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000731 case FocusTypeRight:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100732 virtualStartingRect.setWidth(width);
733 break;
Torne (Richard Coles)09380292014-02-21 12:17:33 +0000734 case FocusTypeDown:
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100735 virtualStartingRect.setHeight(width);
736 break;
737 default:
738 ASSERT_NOT_REACHED();
739 }
740
741 return virtualStartingRect;
742}
743
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000744LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement& area, FocusType type)
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100745{
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000746 ASSERT(area.imageElement());
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100747 // 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.
Torne (Richard Coles)d5428f32014-03-18 10:21:16 +0000749 LayoutRect rect = virtualRectForDirection(type, rectToAbsoluteCoordinates(area.document().frame(), area.computeRect(area.imageElement()->renderer())), 1);
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100750 return rect;
751}
752
753HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate)
754{
Torne (Richard Coles)e1f1df52013-08-23 16:39:30 +0100755 return candidate.isFrameOwnerElement() ? toHTMLFrameOwnerElement(candidate.visibleNode) : 0;
Torne (Richard Coles)53e740f2013-05-09 18:38:43 +0100756};
757
758} // namespace WebCore