1 /* 2 * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) 3 * Copyright (C) 2009 Antonio Gomes <tonikitoo (at) webkit.org> 4 * 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "core/page/SpatialNavigation.h" 31 32 #include "HTMLNames.h" 33 #include "core/dom/Node.h" 34 #include "core/html/HTMLAreaElement.h" 35 #include "core/html/HTMLImageElement.h" 36 #include "core/page/Frame.h" 37 #include "core/page/FrameTree.h" 38 #include "core/page/FrameView.h" 39 #include "core/page/Page.h" 40 #include "core/page/Settings.h" 41 #include "core/platform/graphics/IntRect.h" 42 #include "core/rendering/RenderLayer.h" 43 44 namespace WebCore { 45 46 static RectsAlignment alignmentForRects(FocusDirection, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize); 47 static bool areRectsFullyAligned(FocusDirection, const LayoutRect&, const LayoutRect&); 48 static bool areRectsPartiallyAligned(FocusDirection, const LayoutRect&, const LayoutRect&); 49 static bool areRectsMoreThanFullScreenApart(FocusDirection, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize); 50 static bool isRectInDirection(FocusDirection, const LayoutRect&, const LayoutRect&); 51 static void deflateIfOverlapped(LayoutRect&, LayoutRect&); 52 static LayoutRect rectToAbsoluteCoordinates(Frame* initialFrame, const LayoutRect&); 53 static void entryAndExitPointsForDirection(FocusDirection, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint); 54 static bool isScrollableNode(const Node*); 55 56 FocusCandidate::FocusCandidate(Node* node, FocusDirection direction) 57 : visibleNode(0) 58 , focusableNode(0) 59 , enclosingScrollableBox(0) 60 , distance(maxDistance()) 61 , parentDistance(maxDistance()) 62 , alignment(None) 63 , parentAlignment(None) 64 , isOffscreen(true) 65 , isOffscreenAfterScrolling(true) 66 { 67 ASSERT(node); 68 ASSERT(node->isElementNode()); 69 70 if (isHTMLAreaElement(node)) { 71 HTMLAreaElement* area = toHTMLAreaElement(node); 72 HTMLImageElement* image = area->imageElement(); 73 if (!image || !image->renderer()) 74 return; 75 76 visibleNode = image; 77 rect = virtualRectForAreaElementAndDirection(area, direction); 78 } else { 79 if (!node->renderer()) 80 return; 81 82 visibleNode = node; 83 rect = nodeRectInAbsoluteCoordinates(node, true /* ignore border */); 84 } 85 86 focusableNode = node; 87 isOffscreen = hasOffscreenRect(visibleNode); 88 isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, direction); 89 } 90 91 bool isSpatialNavigationEnabled(const Frame* frame) 92 { 93 return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled()); 94 } 95 96 static RectsAlignment alignmentForRects(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize) 97 { 98 // If we found a node in full alignment, but it is too far away, ignore it. 99 if (areRectsMoreThanFullScreenApart(direction, curRect, targetRect, viewSize)) 100 return None; 101 102 if (areRectsFullyAligned(direction, curRect, targetRect)) 103 return Full; 104 105 if (areRectsPartiallyAligned(direction, curRect, targetRect)) 106 return Partial; 107 108 return None; 109 } 110 111 static inline bool isHorizontalMove(FocusDirection direction) 112 { 113 return direction == FocusDirectionLeft || direction == FocusDirectionRight; 114 } 115 116 static inline LayoutUnit start(FocusDirection direction, const LayoutRect& rect) 117 { 118 return isHorizontalMove(direction) ? rect.y() : rect.x(); 119 } 120 121 static inline LayoutUnit middle(FocusDirection direction, const LayoutRect& rect) 122 { 123 LayoutPoint center(rect.center()); 124 return isHorizontalMove(direction) ? center.y(): center.x(); 125 } 126 127 static inline LayoutUnit end(FocusDirection direction, const LayoutRect& rect) 128 { 129 return isHorizontalMove(direction) ? rect.maxY() : rect.maxX(); 130 } 131 132 // This method checks if rects |a| and |b| are fully aligned either vertically or 133 // horizontally. In general, rects whose central point falls between the top or 134 // bottom of each other are considered fully aligned. 135 // Rects that match this criteria are preferable target nodes in move focus changing 136 // operations. 137 // * a = Current focused node's rect. 138 // * b = Focus candidate node's rect. 139 static bool areRectsFullyAligned(FocusDirection direction, const LayoutRect& a, const LayoutRect& b) 140 { 141 LayoutUnit aStart, bStart, aEnd, bEnd; 142 143 switch (direction) { 144 case FocusDirectionLeft: 145 aStart = a.x(); 146 bEnd = b.maxX(); 147 break; 148 case FocusDirectionRight: 149 aStart = b.x(); 150 bEnd = a.maxX(); 151 break; 152 case FocusDirectionUp: 153 aStart = a.y(); 154 bEnd = b.y(); 155 break; 156 case FocusDirectionDown: 157 aStart = b.y(); 158 bEnd = a.y(); 159 break; 160 default: 161 ASSERT_NOT_REACHED(); 162 return false; 163 } 164 165 if (aStart < bEnd) 166 return false; 167 168 aStart = start(direction, a); 169 bStart = start(direction, b); 170 171 LayoutUnit aMiddle = middle(direction, a); 172 LayoutUnit bMiddle = middle(direction, b); 173 174 aEnd = end(direction, a); 175 bEnd = end(direction, b); 176 177 // Picture of the totally aligned logic: 178 // 179 // Horizontal Vertical Horizontal Vertical 180 // **************************** ***************************** 181 // * _ * _ _ _ _ * * _ * _ _ * 182 // * |_| _ * |_|_|_|_| * * _ |_| * |_|_| * 183 // * |_|....|_| * . * * |_|....|_| * . * 184 // * |_| |_| (1) . * * |_| |_| (2) . * 185 // * |_| * _._ * * |_| * _ _._ _ * 186 // * * |_|_| * * * |_|_|_|_| * 187 // * * * * * * 188 // **************************** ***************************** 189 190 // Horizontal Vertical Horizontal Vertical 191 // **************************** ***************************** 192 // * _......_ * _ _ _ _ * * _ * _ _ _ _ * 193 // * |_| |_| * |_|_|_|_| * * |_| _ * |_|_|_|_| * 194 // * |_| |_| * . * * |_| |_| * . * 195 // * |_| (3) . * * |_|....|_| (4) . * 196 // * * ._ _ * * * _ _. * 197 // * * |_|_| * * * |_|_| * 198 // * * * * * * 199 // **************************** ***************************** 200 201 return ((bMiddle >= aStart && bMiddle <= aEnd) // (1) 202 || (aMiddle >= bStart && aMiddle <= bEnd) // (2) 203 || (bStart == aStart) // (3) 204 || (bEnd == aEnd)); // (4) 205 } 206 207 // This method checks if |start| and |dest| have a partial intersection, either 208 // horizontally or vertically. 209 // * a = Current focused node's rect. 210 // * b = Focus candidate node's rect. 211 static bool areRectsPartiallyAligned(FocusDirection direction, const LayoutRect& a, const LayoutRect& b) 212 { 213 LayoutUnit aStart = start(direction, a); 214 LayoutUnit bStart = start(direction, b); 215 LayoutUnit bMiddle = middle(direction, b); 216 LayoutUnit aEnd = end(direction, a); 217 LayoutUnit bEnd = end(direction, b); 218 219 // Picture of the partially aligned logic: 220 // 221 // Horizontal Vertical 222 // ******************************** 223 // * _ * _ _ _ * 224 // * |_| * |_|_|_| * 225 // * |_|.... _ * . . * 226 // * |_| |_| * . . * 227 // * |_|....|_| * ._._ _ * 228 // * |_| * |_|_|_| * 229 // * |_| * * 230 // * * * 231 // ******************************** 232 // 233 // ... and variants of the above cases. 234 return ((bStart >= aStart && bStart <= aEnd) 235 || (bEnd >= aStart && bEnd <= aEnd) 236 || (bMiddle >= aStart && bMiddle <= aEnd)); 237 } 238 239 static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize) 240 { 241 ASSERT(isRectInDirection(direction, curRect, targetRect)); 242 243 switch (direction) { 244 case FocusDirectionLeft: 245 return curRect.x() - targetRect.maxX() > viewSize.width(); 246 case FocusDirectionRight: 247 return targetRect.x() - curRect.maxX() > viewSize.width(); 248 case FocusDirectionUp: 249 return curRect.y() - targetRect.maxY() > viewSize.height(); 250 case FocusDirectionDown: 251 return targetRect.y() - curRect.maxY() > viewSize.height(); 252 default: 253 ASSERT_NOT_REACHED(); 254 return true; 255 } 256 } 257 258 // Return true if rect |a| is below |b|. False otherwise. 259 static inline bool below(const LayoutRect& a, const LayoutRect& b) 260 { 261 return a.y() > b.maxY(); 262 } 263 264 // Return true if rect |a| is on the right of |b|. False otherwise. 265 static inline bool rightOf(const LayoutRect& a, const LayoutRect& b) 266 { 267 return a.x() > b.maxX(); 268 } 269 270 static bool isRectInDirection(FocusDirection direction, const LayoutRect& curRect, const LayoutRect& targetRect) 271 { 272 switch (direction) { 273 case FocusDirectionLeft: 274 return targetRect.maxX() <= curRect.x(); 275 case FocusDirectionRight: 276 return targetRect.x() >= curRect.maxX(); 277 case FocusDirectionUp: 278 return targetRect.maxY() <= curRect.y(); 279 case FocusDirectionDown: 280 return targetRect.y() >= curRect.maxY(); 281 default: 282 ASSERT_NOT_REACHED(); 283 return false; 284 } 285 } 286 287 // Checks if |node| is offscreen the visible area (viewport) of its container 288 // document. In case it is, one can scroll in direction or take any different 289 // desired action later on. 290 bool hasOffscreenRect(Node* node, FocusDirection direction) 291 { 292 // Get the FrameView in which |node| is (which means the current viewport if |node| 293 // is not in an inner document), so we can check if its content rect is visible 294 // before we actually move the focus to it. 295 FrameView* frameView = node->document()->view(); 296 if (!frameView) 297 return true; 298 299 ASSERT(!frameView->needsLayout()); 300 301 LayoutRect containerViewportRect = frameView->visibleContentRect(); 302 // We want to select a node if it is currently off screen, but will be 303 // exposed after we scroll. Adjust the viewport to post-scrolling position. 304 // If the container has overflow:hidden, we cannot scroll, so we do not pass direction 305 // and we do not adjust for scrolling. 306 switch (direction) { 307 case FocusDirectionLeft: 308 containerViewportRect.setX(containerViewportRect.x() - ScrollableArea::pixelsPerLineStep()); 309 containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep()); 310 break; 311 case FocusDirectionRight: 312 containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep()); 313 break; 314 case FocusDirectionUp: 315 containerViewportRect.setY(containerViewportRect.y() - ScrollableArea::pixelsPerLineStep()); 316 containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep()); 317 break; 318 case FocusDirectionDown: 319 containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep()); 320 break; 321 default: 322 break; 323 } 324 325 RenderObject* render = node->renderer(); 326 if (!render) 327 return true; 328 329 LayoutRect rect(render->absoluteClippedOverflowRect()); 330 if (rect.isEmpty()) 331 return true; 332 333 return !containerViewportRect.intersects(rect); 334 } 335 336 bool scrollInDirection(Frame* frame, FocusDirection direction) 337 { 338 ASSERT(frame); 339 340 if (frame && canScrollInDirection(frame->document(), direction)) { 341 LayoutUnit dx = 0; 342 LayoutUnit dy = 0; 343 switch (direction) { 344 case FocusDirectionLeft: 345 dx = - ScrollableArea::pixelsPerLineStep(); 346 break; 347 case FocusDirectionRight: 348 dx = ScrollableArea::pixelsPerLineStep(); 349 break; 350 case FocusDirectionUp: 351 dy = - ScrollableArea::pixelsPerLineStep(); 352 break; 353 case FocusDirectionDown: 354 dy = ScrollableArea::pixelsPerLineStep(); 355 break; 356 default: 357 ASSERT_NOT_REACHED(); 358 return false; 359 } 360 361 frame->view()->scrollBy(IntSize(dx, dy)); 362 return true; 363 } 364 return false; 365 } 366 367 bool scrollInDirection(Node* container, FocusDirection direction) 368 { 369 ASSERT(container); 370 if (container->isDocumentNode()) 371 return scrollInDirection(toDocument(container)->frame(), direction); 372 373 if (!container->renderBox()) 374 return false; 375 376 if (canScrollInDirection(container, direction)) { 377 LayoutUnit dx = 0; 378 LayoutUnit dy = 0; 379 switch (direction) { 380 case FocusDirectionLeft: 381 dx = - min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollLeft()); 382 break; 383 case FocusDirectionRight: 384 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth())); 385 dx = min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth())); 386 break; 387 case FocusDirectionUp: 388 dy = - min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollTop()); 389 break; 390 case FocusDirectionDown: 391 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight())); 392 dy = min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight())); 393 break; 394 default: 395 ASSERT_NOT_REACHED(); 396 return false; 397 } 398 399 container->renderBox()->enclosingLayer()->scrollByRecursively(IntSize(dx, dy)); 400 return true; 401 } 402 403 return false; 404 } 405 406 static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b) 407 { 408 if (!a.intersects(b) || a.contains(b) || b.contains(a)) 409 return; 410 411 LayoutUnit deflateFactor = -fudgeFactor(); 412 413 // Avoid negative width or height values. 414 if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0)) 415 a.inflate(deflateFactor); 416 417 if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0)) 418 b.inflate(deflateFactor); 419 } 420 421 bool isScrollableNode(const Node* node) 422 { 423 ASSERT(!node->isDocumentNode()); 424 425 if (!node) 426 return false; 427 428 if (RenderObject* renderer = node->renderer()) 429 return renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea() && node->hasChildNodes(); 430 431 return false; 432 } 433 434 Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection direction, Node* node) 435 { 436 ASSERT(node); 437 Node* parent = node; 438 do { 439 if (parent->isDocumentNode()) 440 parent = toDocument(parent)->document()->frame()->ownerElement(); 441 else 442 parent = parent->parentNode(); 443 } while (parent && !canScrollInDirection(parent, direction) && !parent->isDocumentNode()); 444 445 return parent; 446 } 447 448 bool canScrollInDirection(const Node* container, FocusDirection direction) 449 { 450 ASSERT(container); 451 if (container->isDocumentNode()) 452 return canScrollInDirection(toDocument(container)->frame(), direction); 453 454 if (!isScrollableNode(container)) 455 return false; 456 457 switch (direction) { 458 case FocusDirectionLeft: 459 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0); 460 case FocusDirectionUp: 461 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0); 462 case FocusDirectionRight: 463 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth()); 464 case FocusDirectionDown: 465 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight()); 466 default: 467 ASSERT_NOT_REACHED(); 468 return false; 469 } 470 } 471 472 bool canScrollInDirection(const Frame* frame, FocusDirection direction) 473 { 474 if (!frame->view()) 475 return false; 476 ScrollbarMode verticalMode; 477 ScrollbarMode horizontalMode; 478 frame->view()->calculateScrollbarModesForLayout(horizontalMode, verticalMode); 479 if ((direction == FocusDirectionLeft || direction == FocusDirectionRight) && ScrollbarAlwaysOff == horizontalMode) 480 return false; 481 if ((direction == FocusDirectionUp || direction == FocusDirectionDown) && ScrollbarAlwaysOff == verticalMode) 482 return false; 483 LayoutSize size = frame->view()->contentsSize(); 484 LayoutSize offset = frame->view()->scrollOffset(); 485 LayoutRect rect = frame->view()->visibleContentRect(ScrollableArea::IncludeScrollbars); 486 487 switch (direction) { 488 case FocusDirectionLeft: 489 return offset.width() > 0; 490 case FocusDirectionUp: 491 return offset.height() > 0; 492 case FocusDirectionRight: 493 return rect.width() + offset.width() < size.width(); 494 case FocusDirectionDown: 495 return rect.height() + offset.height() < size.height(); 496 default: 497 ASSERT_NOT_REACHED(); 498 return false; 499 } 500 } 501 502 static LayoutRect rectToAbsoluteCoordinates(Frame* initialFrame, const LayoutRect& initialRect) 503 { 504 LayoutRect rect = initialRect; 505 for (Frame* frame = initialFrame; frame; frame = frame->tree()->parent()) { 506 if (Element* element = frame->ownerElement()) { 507 do { 508 rect.move(element->offsetLeft(), element->offsetTop()); 509 } while ((element = element->offsetParent())); 510 rect.move((-frame->view()->scrollOffset())); 511 } 512 } 513 return rect; 514 } 515 516 LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder) 517 { 518 ASSERT(node && node->renderer() && !node->document()->view()->needsLayout()); 519 520 if (node->isDocumentNode()) 521 return frameRectInAbsoluteCoordinates(toDocument(node)->frame()); 522 LayoutRect rect = rectToAbsoluteCoordinates(node->document()->frame(), node->boundingBox()); 523 524 // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating 525 // the rect of the focused element. 526 if (ignoreBorder) { 527 rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth()); 528 rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth()); 529 rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth()); 530 } 531 return rect; 532 } 533 534 LayoutRect frameRectInAbsoluteCoordinates(Frame* frame) 535 { 536 return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect()); 537 } 538 539 // This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect. 540 // The line between those 2 points is the closest distance between the 2 rects. 541 void entryAndExitPointsForDirection(FocusDirection direction, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint) 542 { 543 switch (direction) { 544 case FocusDirectionLeft: 545 exitPoint.setX(startingRect.x()); 546 entryPoint.setX(potentialRect.maxX()); 547 break; 548 case FocusDirectionUp: 549 exitPoint.setY(startingRect.y()); 550 entryPoint.setY(potentialRect.maxY()); 551 break; 552 case FocusDirectionRight: 553 exitPoint.setX(startingRect.maxX()); 554 entryPoint.setX(potentialRect.x()); 555 break; 556 case FocusDirectionDown: 557 exitPoint.setY(startingRect.maxY()); 558 entryPoint.setY(potentialRect.y()); 559 break; 560 default: 561 ASSERT_NOT_REACHED(); 562 } 563 564 switch (direction) { 565 case FocusDirectionLeft: 566 case FocusDirectionRight: 567 if (below(startingRect, potentialRect)) { 568 exitPoint.setY(startingRect.y()); 569 entryPoint.setY(potentialRect.maxY()); 570 } else if (below(potentialRect, startingRect)) { 571 exitPoint.setY(startingRect.maxY()); 572 entryPoint.setY(potentialRect.y()); 573 } else { 574 exitPoint.setY(max(startingRect.y(), potentialRect.y())); 575 entryPoint.setY(exitPoint.y()); 576 } 577 break; 578 case FocusDirectionUp: 579 case FocusDirectionDown: 580 if (rightOf(startingRect, potentialRect)) { 581 exitPoint.setX(startingRect.x()); 582 entryPoint.setX(potentialRect.maxX()); 583 } else if (rightOf(potentialRect, startingRect)) { 584 exitPoint.setX(startingRect.maxX()); 585 entryPoint.setX(potentialRect.x()); 586 } else { 587 exitPoint.setX(max(startingRect.x(), potentialRect.x())); 588 entryPoint.setX(exitPoint.x()); 589 } 590 break; 591 default: 592 ASSERT_NOT_REACHED(); 593 } 594 } 595 596 bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate) 597 { 598 if (firstCandidate.isNull() || secondCandidate.isNull()) 599 return false; 600 601 if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer()) 602 return false; 603 604 if (!firstCandidate.rect.intersects(secondCandidate.rect)) 605 return false; 606 607 if (isHTMLAreaElement(firstCandidate.focusableNode) || isHTMLAreaElement(secondCandidate.focusableNode)) 608 return false; 609 610 if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline()) 611 return false; 612 613 if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock()) 614 return false; 615 616 return true; 617 } 618 619 void distanceDataForNode(FocusDirection direction, const FocusCandidate& current, FocusCandidate& candidate) 620 { 621 if (areElementsOnSameLine(current, candidate)) { 622 if ((direction == FocusDirectionUp && current.rect.y() > candidate.rect.y()) || (direction == FocusDirectionDown && candidate.rect.y() > current.rect.y())) { 623 candidate.distance = 0; 624 candidate.alignment = Full; 625 return; 626 } 627 } 628 629 LayoutRect nodeRect = candidate.rect; 630 LayoutRect currentRect = current.rect; 631 deflateIfOverlapped(currentRect, nodeRect); 632 633 if (!isRectInDirection(direction, currentRect, nodeRect)) 634 return; 635 636 LayoutPoint exitPoint; 637 LayoutPoint entryPoint; 638 LayoutUnit sameAxisDistance = 0; 639 LayoutUnit otherAxisDistance = 0; 640 entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint); 641 642 switch (direction) { 643 case FocusDirectionLeft: 644 sameAxisDistance = exitPoint.x() - entryPoint.x(); 645 otherAxisDistance = absoluteValue(exitPoint.y() - entryPoint.y()); 646 break; 647 case FocusDirectionUp: 648 sameAxisDistance = exitPoint.y() - entryPoint.y(); 649 otherAxisDistance = absoluteValue(exitPoint.x() - entryPoint.x()); 650 break; 651 case FocusDirectionRight: 652 sameAxisDistance = entryPoint.x() - exitPoint.x(); 653 otherAxisDistance = absoluteValue(entryPoint.y() - exitPoint.y()); 654 break; 655 case FocusDirectionDown: 656 sameAxisDistance = entryPoint.y() - exitPoint.y(); 657 otherAxisDistance = absoluteValue(entryPoint.x() - exitPoint.x()); 658 break; 659 default: 660 ASSERT_NOT_REACHED(); 661 return; 662 } 663 664 float x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x()); 665 float y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y()); 666 667 float euclidianDistance = sqrt(x + y); 668 669 // Loosely based on http://www.w3.org/TR/WICD/#focus-handling 670 // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap) 671 672 float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance; 673 candidate.distance = roundf(distance); 674 LayoutSize viewSize = candidate.visibleNode->document()->page()->mainFrame()->view()->visibleContentRect().size(); 675 candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize); 676 } 677 678 bool canBeScrolledIntoView(FocusDirection direction, const FocusCandidate& candidate) 679 { 680 ASSERT(candidate.visibleNode && candidate.isOffscreen); 681 LayoutRect candidateRect = candidate.rect; 682 for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) { 683 LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode); 684 if (!candidateRect.intersects(parentRect)) { 685 if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN) 686 || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN)) 687 return false; 688 } 689 if (parentNode == candidate.enclosingScrollableBox) 690 return canScrollInDirection(parentNode, direction); 691 } 692 return true; 693 } 694 695 // The starting rect is the rect of the focused node, in document coordinates. 696 // Compose a virtual starting rect if there is no focused node or if it is off screen. 697 // The virtual rect is the edge of the container or frame. We select which 698 // edge depending on the direction of the navigation. 699 LayoutRect virtualRectForDirection(FocusDirection direction, const LayoutRect& startingRect, LayoutUnit width) 700 { 701 LayoutRect virtualStartingRect = startingRect; 702 switch (direction) { 703 case FocusDirectionLeft: 704 virtualStartingRect.setX(virtualStartingRect.maxX() - width); 705 virtualStartingRect.setWidth(width); 706 break; 707 case FocusDirectionUp: 708 virtualStartingRect.setY(virtualStartingRect.maxY() - width); 709 virtualStartingRect.setHeight(width); 710 break; 711 case FocusDirectionRight: 712 virtualStartingRect.setWidth(width); 713 break; 714 case FocusDirectionDown: 715 virtualStartingRect.setHeight(width); 716 break; 717 default: 718 ASSERT_NOT_REACHED(); 719 } 720 721 return virtualStartingRect; 722 } 723 724 LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement* area, FocusDirection direction) 725 { 726 ASSERT(area); 727 ASSERT(area->imageElement()); 728 // Area elements tend to overlap more than other focusable elements. We flatten the rect of the area elements 729 // to minimize the effect of overlapping areas. 730 LayoutRect rect = virtualRectForDirection(direction, rectToAbsoluteCoordinates(area->document()->frame(), area->computeRect(area->imageElement()->renderer())), 1); 731 return rect; 732 } 733 734 HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate) 735 { 736 return candidate.isFrameOwnerElement() ? static_cast<HTMLFrameOwnerElement*>(candidate.visibleNode) : 0; 737 }; 738 739 } // namespace WebCore 740