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 "core/HTMLNames.h" 33 #include "core/frame/FrameView.h" 34 #include "core/frame/LocalFrame.h" 35 #include "core/frame/Settings.h" 36 #include "core/html/HTMLAreaElement.h" 37 #include "core/html/HTMLFrameOwnerElement.h" 38 #include "core/html/HTMLImageElement.h" 39 #include "core/page/FrameTree.h" 40 #include "core/page/Page.h" 41 #include "core/rendering/RenderLayer.h" 42 #include "platform/geometry/IntRect.h" 43 44 namespace blink { 45 46 using namespace HTMLNames; 47 48 static RectsAlignment alignmentForRects(FocusType, const LayoutRect&, const LayoutRect&, const LayoutSize& viewSize); 49 static bool areRectsFullyAligned(FocusType, const LayoutRect&, const LayoutRect&); 50 static bool areRectsPartiallyAligned(FocusType, const LayoutRect&, const LayoutRect&); 51 static bool areRectsMoreThanFullScreenApart(FocusType, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize); 52 static bool isRectInDirection(FocusType, const LayoutRect&, const LayoutRect&); 53 static void deflateIfOverlapped(LayoutRect&, LayoutRect&); 54 static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect&); 55 static bool isScrollableNode(const Node*); 56 57 FocusCandidate::FocusCandidate(Node* node, FocusType type) 58 : visibleNode(nullptr) 59 , focusableNode(nullptr) 60 , enclosingScrollableBox(nullptr) 61 , distance(maxDistance()) 62 , alignment(None) 63 , isOffscreen(true) 64 , isOffscreenAfterScrolling(true) 65 { 66 ASSERT(node); 67 ASSERT(node->isElementNode()); 68 69 if (isHTMLAreaElement(*node)) { 70 HTMLAreaElement& area = toHTMLAreaElement(*node); 71 HTMLImageElement* image = area.imageElement(); 72 if (!image || !image->renderer()) 73 return; 74 75 visibleNode = image; 76 rect = virtualRectForAreaElementAndDirection(area, type); 77 } else { 78 if (!node->renderer()) 79 return; 80 81 visibleNode = node; 82 rect = nodeRectInAbsoluteCoordinates(node, true /* ignore border */); 83 } 84 85 focusableNode = node; 86 isOffscreen = hasOffscreenRect(visibleNode); 87 isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, type); 88 } 89 90 bool isSpatialNavigationEnabled(const LocalFrame* frame) 91 { 92 return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled()); 93 } 94 95 static RectsAlignment alignmentForRects(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize) 96 { 97 // If we found a node in full alignment, but it is too far away, ignore it. 98 if (areRectsMoreThanFullScreenApart(type, curRect, targetRect, viewSize)) 99 return None; 100 101 if (areRectsFullyAligned(type, curRect, targetRect)) 102 return Full; 103 104 if (areRectsPartiallyAligned(type, curRect, targetRect)) 105 return Partial; 106 107 return None; 108 } 109 110 static inline bool isHorizontalMove(FocusType type) 111 { 112 return type == FocusTypeLeft || type == FocusTypeRight; 113 } 114 115 static inline LayoutUnit start(FocusType type, const LayoutRect& rect) 116 { 117 return isHorizontalMove(type) ? rect.y() : rect.x(); 118 } 119 120 static inline LayoutUnit middle(FocusType type, const LayoutRect& rect) 121 { 122 LayoutPoint center(rect.center()); 123 return isHorizontalMove(type) ? center.y(): center.x(); 124 } 125 126 static inline LayoutUnit end(FocusType type, const LayoutRect& rect) 127 { 128 return isHorizontalMove(type) ? rect.maxY() : rect.maxX(); 129 } 130 131 // This method checks if rects |a| and |b| are fully aligned either vertically or 132 // horizontally. In general, rects whose central point falls between the top or 133 // bottom of each other are considered fully aligned. 134 // Rects that match this criteria are preferable target nodes in move focus changing 135 // operations. 136 // * a = Current focused node's rect. 137 // * b = Focus candidate node's rect. 138 static bool areRectsFullyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b) 139 { 140 LayoutUnit aStart, bStart, aEnd, bEnd; 141 142 switch (type) { 143 case FocusTypeLeft: 144 aStart = a.x(); 145 bEnd = b.x(); 146 break; 147 case FocusTypeRight: 148 aStart = b.x(); 149 bEnd = a.x(); 150 break; 151 case FocusTypeUp: 152 aStart = a.y(); 153 bEnd = b.y(); 154 break; 155 case FocusTypeDown: 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(type, a); 168 bStart = start(type, b); 169 170 LayoutUnit aMiddle = middle(type, a); 171 LayoutUnit bMiddle = middle(type, b); 172 173 aEnd = end(type, a); 174 bEnd = end(type, 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 return (bMiddle >= aStart && bMiddle <= aEnd) // (1) 190 || (aMiddle >= bStart && aMiddle <= bEnd); // (2) 191 } 192 193 // This method checks if rects |a| and |b| are partially aligned either vertically or 194 // horizontally. In general, rects whose either of edges falls between the top or 195 // bottom of each other are considered partially-aligned. 196 // This is a separate set of conditions from "fully-aligned" and do not include cases 197 // that satisfy the former. 198 // * a = Current focused node's rect. 199 // * b = Focus candidate node's rect. 200 static bool areRectsPartiallyAligned(FocusType type, const LayoutRect& a, const LayoutRect& b) 201 { 202 LayoutUnit aStart = start(type, a); 203 LayoutUnit bStart = start(type, b); 204 LayoutUnit aEnd = end(type, a); 205 LayoutUnit bEnd = end(type, b); 206 207 // Picture of the partially aligned logic: 208 // 209 // Horizontal Vertical 210 // ******************************** 211 // * _ * _ _ _ * 212 // * |_| * |_|_|_| * 213 // * |_|.... _ * . . * 214 // * |_| |_| * . . * 215 // * |_|....|_| * ._._ _ * 216 // * |_| * |_|_|_| * 217 // * |_| * * 218 // * * * 219 // ******************************** 220 // 221 // ... and variants of the above cases. 222 return (bStart >= aStart && bStart <= aEnd) 223 || (bEnd >= aStart && bEnd <= aEnd); 224 } 225 226 static bool areRectsMoreThanFullScreenApart(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect, const LayoutSize& viewSize) 227 { 228 ASSERT(isRectInDirection(type, curRect, targetRect)); 229 230 switch (type) { 231 case FocusTypeLeft: 232 return curRect.x() - targetRect.maxX() > viewSize.width(); 233 case FocusTypeRight: 234 return targetRect.x() - curRect.maxX() > viewSize.width(); 235 case FocusTypeUp: 236 return curRect.y() - targetRect.maxY() > viewSize.height(); 237 case FocusTypeDown: 238 return targetRect.y() - curRect.maxY() > viewSize.height(); 239 default: 240 ASSERT_NOT_REACHED(); 241 return true; 242 } 243 } 244 245 // Return true if rect |a| is below |b|. False otherwise. 246 // For overlapping rects, |a| is considered to be below |b| 247 // if both edges of |a| are below the respective ones of |b| 248 static inline bool below(const LayoutRect& a, const LayoutRect& b) 249 { 250 return a.y() >= b.maxY() 251 || (a.y() >= b.y() && a.maxY() > b.maxY()); 252 } 253 254 // Return true if rect |a| is on the right of |b|. False otherwise. 255 // For overlapping rects, |a| is considered to be on the right of |b| 256 // if both edges of |a| are on the right of the respective ones of |b| 257 static inline bool rightOf(const LayoutRect& a, const LayoutRect& b) 258 { 259 return a.x() >= b.maxX() 260 || (a.x() >= b.x() && a.maxX() > b.maxX()); 261 } 262 263 static bool isRectInDirection(FocusType type, const LayoutRect& curRect, const LayoutRect& targetRect) 264 { 265 switch (type) { 266 case FocusTypeLeft: 267 return rightOf(curRect, targetRect); 268 case FocusTypeRight: 269 return rightOf(targetRect, curRect); 270 case FocusTypeUp: 271 return below(curRect, targetRect); 272 case FocusTypeDown: 273 return below(targetRect, curRect); 274 default: 275 ASSERT_NOT_REACHED(); 276 return false; 277 } 278 } 279 280 // Checks if |node| is offscreen the visible area (viewport) of its container 281 // document. In case it is, one can scroll in direction or take any different 282 // desired action later on. 283 bool hasOffscreenRect(Node* node, FocusType type) 284 { 285 // Get the FrameView in which |node| is (which means the current viewport if |node| 286 // is not in an inner document), so we can check if its content rect is visible 287 // before we actually move the focus to it. 288 FrameView* frameView = node->document().view(); 289 if (!frameView) 290 return true; 291 292 ASSERT(!frameView->needsLayout()); 293 294 LayoutRect containerViewportRect = frameView->visibleContentRect(); 295 // We want to select a node if it is currently off screen, but will be 296 // exposed after we scroll. Adjust the viewport to post-scrolling position. 297 // If the container has overflow:hidden, we cannot scroll, so we do not pass direction 298 // and we do not adjust for scrolling. 299 switch (type) { 300 case FocusTypeLeft: 301 containerViewportRect.setX(containerViewportRect.x() - ScrollableArea::pixelsPerLineStep()); 302 containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep()); 303 break; 304 case FocusTypeRight: 305 containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep()); 306 break; 307 case FocusTypeUp: 308 containerViewportRect.setY(containerViewportRect.y() - ScrollableArea::pixelsPerLineStep()); 309 containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep()); 310 break; 311 case FocusTypeDown: 312 containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep()); 313 break; 314 default: 315 break; 316 } 317 318 RenderObject* render = node->renderer(); 319 if (!render) 320 return true; 321 322 LayoutRect rect(render->absoluteClippedOverflowRect()); 323 if (rect.isEmpty()) 324 return true; 325 326 return !containerViewportRect.intersects(rect); 327 } 328 329 bool scrollInDirection(LocalFrame* frame, FocusType type) 330 { 331 ASSERT(frame); 332 333 if (frame && canScrollInDirection(frame->document(), type)) { 334 LayoutUnit dx = 0; 335 LayoutUnit dy = 0; 336 switch (type) { 337 case FocusTypeLeft: 338 dx = - ScrollableArea::pixelsPerLineStep(); 339 break; 340 case FocusTypeRight: 341 dx = ScrollableArea::pixelsPerLineStep(); 342 break; 343 case FocusTypeUp: 344 dy = - ScrollableArea::pixelsPerLineStep(); 345 break; 346 case FocusTypeDown: 347 dy = ScrollableArea::pixelsPerLineStep(); 348 break; 349 default: 350 ASSERT_NOT_REACHED(); 351 return false; 352 } 353 354 frame->view()->scrollBy(IntSize(dx, dy)); 355 return true; 356 } 357 return false; 358 } 359 360 bool scrollInDirection(Node* container, FocusType type) 361 { 362 ASSERT(container); 363 if (container->isDocumentNode()) 364 return scrollInDirection(toDocument(container)->frame(), type); 365 366 if (!container->renderBox()) 367 return false; 368 369 if (canScrollInDirection(container, type)) { 370 LayoutUnit dx = 0; 371 LayoutUnit dy = 0; 372 switch (type) { 373 case FocusTypeLeft: 374 dx = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollLeft()); 375 break; 376 case FocusTypeRight: 377 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth())); 378 dx = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth())); 379 break; 380 case FocusTypeUp: 381 dy = - std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollTop()); 382 break; 383 case FocusTypeDown: 384 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight())); 385 dy = std::min<LayoutUnit>(ScrollableArea::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight())); 386 break; 387 default: 388 ASSERT_NOT_REACHED(); 389 return false; 390 } 391 392 container->renderBox()->scrollByRecursively(IntSize(dx, dy)); 393 return true; 394 } 395 396 return false; 397 } 398 399 static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b) 400 { 401 if (!a.intersects(b) || a.contains(b) || b.contains(a)) 402 return; 403 404 LayoutUnit deflateFactor = -fudgeFactor(); 405 406 // Avoid negative width or height values. 407 if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0)) 408 a.inflate(deflateFactor); 409 410 if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0)) 411 b.inflate(deflateFactor); 412 } 413 414 bool isScrollableNode(const Node* node) 415 { 416 ASSERT(!node->isDocumentNode()); 417 418 if (!node) 419 return false; 420 421 if (RenderObject* renderer = node->renderer()) 422 return renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea() && node->hasChildren(); 423 424 return false; 425 } 426 427 Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusType type, Node* node) 428 { 429 ASSERT(node); 430 Node* parent = node; 431 do { 432 // FIXME: Spatial navigation is broken for OOPI. 433 if (parent->isDocumentNode()) 434 parent = toDocument(parent)->frame()->deprecatedLocalOwner(); 435 else 436 parent = parent->parentOrShadowHostNode(); 437 } while (parent && !canScrollInDirection(parent, type) && !parent->isDocumentNode()); 438 439 return parent; 440 } 441 442 bool canScrollInDirection(const Node* container, FocusType type) 443 { 444 ASSERT(container); 445 if (container->isDocumentNode()) 446 return canScrollInDirection(toDocument(container)->frame(), type); 447 448 if (!isScrollableNode(container)) 449 return false; 450 451 switch (type) { 452 case FocusTypeLeft: 453 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0); 454 case FocusTypeUp: 455 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0); 456 case FocusTypeRight: 457 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth()); 458 case FocusTypeDown: 459 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight()); 460 default: 461 ASSERT_NOT_REACHED(); 462 return false; 463 } 464 } 465 466 bool canScrollInDirection(const LocalFrame* frame, FocusType type) 467 { 468 if (!frame->view()) 469 return false; 470 ScrollbarMode verticalMode; 471 ScrollbarMode horizontalMode; 472 frame->view()->calculateScrollbarModesForLayoutAndSetViewportRenderer(horizontalMode, verticalMode); 473 if ((type == FocusTypeLeft || type == FocusTypeRight) && ScrollbarAlwaysOff == horizontalMode) 474 return false; 475 if ((type == FocusTypeUp || type == FocusTypeDown) && ScrollbarAlwaysOff == verticalMode) 476 return false; 477 LayoutSize size = frame->view()->contentsSize(); 478 LayoutSize offset = frame->view()->scrollOffset(); 479 LayoutRect rect = frame->view()->visibleContentRect(IncludeScrollbars); 480 481 switch (type) { 482 case FocusTypeLeft: 483 return offset.width() > 0; 484 case FocusTypeUp: 485 return offset.height() > 0; 486 case FocusTypeRight: 487 return rect.width() + offset.width() < size.width(); 488 case FocusTypeDown: 489 return rect.height() + offset.height() < size.height(); 490 default: 491 ASSERT_NOT_REACHED(); 492 return false; 493 } 494 } 495 496 static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect& initialRect) 497 { 498 LayoutRect rect = initialRect; 499 for (Frame* frame = initialFrame; frame; frame = frame->tree().parent()) { 500 if (!frame->isLocalFrame()) 501 continue; 502 // FIXME: Spatial navigation is broken for OOPI. 503 Element* element = frame->deprecatedLocalOwner(); 504 if (element) { 505 for (; element; element = element->offsetParent()) 506 rect.move(element->offsetLeft(), element->offsetTop()); 507 rect.move((-toLocalFrame(frame)->view()->scrollOffset())); 508 } 509 } 510 return rect; 511 } 512 513 LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder) 514 { 515 ASSERT(node && node->renderer() && !node->document().view()->needsLayout()); 516 517 if (node->isDocumentNode()) 518 return frameRectInAbsoluteCoordinates(toDocument(node)->frame()); 519 LayoutRect rect = rectToAbsoluteCoordinates(node->document().frame(), node->boundingBox()); 520 521 // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating 522 // the rect of the focused element. 523 if (ignoreBorder) { 524 rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth()); 525 rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth()); 526 rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth()); 527 } 528 return rect; 529 } 530 531 LayoutRect frameRectInAbsoluteCoordinates(LocalFrame* frame) 532 { 533 return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect()); 534 } 535 536 // This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect. 537 // The line between those 2 points is the closest distance between the 2 rects. 538 // Takes care of overlapping rects, defining points so that the distance between them 539 // is zero where necessary 540 void entryAndExitPointsForDirection(FocusType type, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint) 541 { 542 switch (type) { 543 case FocusTypeLeft: 544 exitPoint.setX(startingRect.x()); 545 if (potentialRect.maxX() < startingRect.x()) 546 entryPoint.setX(potentialRect.maxX()); 547 else 548 entryPoint.setX(startingRect.x()); 549 break; 550 case FocusTypeUp: 551 exitPoint.setY(startingRect.y()); 552 if (potentialRect.maxY() < startingRect.y()) 553 entryPoint.setY(potentialRect.maxY()); 554 else 555 entryPoint.setY(startingRect.y()); 556 break; 557 case FocusTypeRight: 558 exitPoint.setX(startingRect.maxX()); 559 if (potentialRect.x() > startingRect.maxX()) 560 entryPoint.setX(potentialRect.x()); 561 else 562 entryPoint.setX(startingRect.maxX()); 563 break; 564 case FocusTypeDown: 565 exitPoint.setY(startingRect.maxY()); 566 if (potentialRect.y() > startingRect.maxY()) 567 entryPoint.setY(potentialRect.y()); 568 else 569 entryPoint.setY(startingRect.maxY()); 570 break; 571 default: 572 ASSERT_NOT_REACHED(); 573 } 574 575 switch (type) { 576 case FocusTypeLeft: 577 case FocusTypeRight: 578 if (below(startingRect, potentialRect)) { 579 exitPoint.setY(startingRect.y()); 580 if (potentialRect.maxY() < startingRect.y()) 581 entryPoint.setY(potentialRect.maxY()); 582 else 583 entryPoint.setY(startingRect.y()); 584 } else if (below(potentialRect, startingRect)) { 585 exitPoint.setY(startingRect.maxY()); 586 if (potentialRect.y() > startingRect.maxY()) 587 entryPoint.setY(potentialRect.y()); 588 else 589 entryPoint.setY(startingRect.maxY()); 590 } else { 591 exitPoint.setY(max(startingRect.y(), potentialRect.y())); 592 entryPoint.setY(exitPoint.y()); 593 } 594 break; 595 case FocusTypeUp: 596 case FocusTypeDown: 597 if (rightOf(startingRect, potentialRect)) { 598 exitPoint.setX(startingRect.x()); 599 if (potentialRect.maxX() < startingRect.x()) 600 entryPoint.setX(potentialRect.maxX()); 601 else 602 entryPoint.setX(startingRect.x()); 603 } else if (rightOf(potentialRect, startingRect)) { 604 exitPoint.setX(startingRect.maxX()); 605 if (potentialRect.x() > startingRect.maxX()) 606 entryPoint.setX(potentialRect.x()); 607 else 608 entryPoint.setX(startingRect.maxX()); 609 } else { 610 exitPoint.setX(max(startingRect.x(), potentialRect.x())); 611 entryPoint.setX(exitPoint.x()); 612 } 613 break; 614 default: 615 ASSERT_NOT_REACHED(); 616 } 617 } 618 619 bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate) 620 { 621 if (firstCandidate.isNull() || secondCandidate.isNull()) 622 return false; 623 624 if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer()) 625 return false; 626 627 if (!firstCandidate.rect.intersects(secondCandidate.rect)) 628 return false; 629 630 if (isHTMLAreaElement(*firstCandidate.focusableNode) || isHTMLAreaElement(*secondCandidate.focusableNode)) 631 return false; 632 633 if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline()) 634 return false; 635 636 if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock()) 637 return false; 638 639 return true; 640 } 641 642 void distanceDataForNode(FocusType type, const FocusCandidate& current, FocusCandidate& candidate) 643 { 644 if (areElementsOnSameLine(current, candidate)) { 645 if ((type == FocusTypeUp && current.rect.y() > candidate.rect.y()) || (type == FocusTypeDown && candidate.rect.y() > current.rect.y())) { 646 candidate.distance = 0; 647 candidate.alignment = Full; 648 return; 649 } 650 } 651 652 LayoutRect nodeRect = candidate.rect; 653 LayoutRect currentRect = current.rect; 654 deflateIfOverlapped(currentRect, nodeRect); 655 656 if (!isRectInDirection(type, currentRect, nodeRect)) 657 return; 658 659 LayoutPoint exitPoint; 660 LayoutPoint entryPoint; 661 entryAndExitPointsForDirection(type, currentRect, nodeRect, exitPoint, entryPoint); 662 663 LayoutUnit xAxis = exitPoint.x() - entryPoint.x(); 664 LayoutUnit yAxis = exitPoint.y() - entryPoint.y(); 665 666 LayoutUnit navigationAxisDistance; 667 LayoutUnit orthogonalAxisDistance; 668 669 switch (type) { 670 case FocusTypeLeft: 671 case FocusTypeRight: 672 navigationAxisDistance = xAxis.abs(); 673 orthogonalAxisDistance = yAxis.abs(); 674 break; 675 case FocusTypeUp: 676 case FocusTypeDown: 677 navigationAxisDistance = yAxis.abs(); 678 orthogonalAxisDistance = xAxis.abs(); 679 break; 680 default: 681 ASSERT_NOT_REACHED(); 682 return; 683 } 684 685 double euclidianDistancePow2 = (xAxis * xAxis + yAxis * yAxis).toDouble(); 686 LayoutRect intersectionRect = intersection(currentRect, nodeRect); 687 double overlap = (intersectionRect.width() * intersectionRect.height()).toDouble(); 688 689 // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling 690 candidate.distance = sqrt(euclidianDistancePow2) + navigationAxisDistance+ orthogonalAxisDistance * 2 - sqrt(overlap); 691 692 LayoutSize viewSize = candidate.visibleNode->document().page()->deprecatedLocalMainFrame()->view()->visibleContentRect().size(); 693 candidate.alignment = alignmentForRects(type, currentRect, nodeRect, viewSize); 694 } 695 696 bool canBeScrolledIntoView(FocusType type, const FocusCandidate& candidate) 697 { 698 ASSERT(candidate.visibleNode && candidate.isOffscreen); 699 LayoutRect candidateRect = candidate.rect; 700 for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) { 701 LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode); 702 if (!candidateRect.intersects(parentRect)) { 703 if (((type == FocusTypeLeft || type == FocusTypeRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN) 704 || ((type == FocusTypeUp || type == FocusTypeDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN)) 705 return false; 706 } 707 if (parentNode == candidate.enclosingScrollableBox) 708 return canScrollInDirection(parentNode, type); 709 } 710 return true; 711 } 712 713 // The starting rect is the rect of the focused node, in document coordinates. 714 // Compose a virtual starting rect if there is no focused node or if it is off screen. 715 // The virtual rect is the edge of the container or frame. We select which 716 // edge depending on the direction of the navigation. 717 LayoutRect virtualRectForDirection(FocusType type, const LayoutRect& startingRect, LayoutUnit width) 718 { 719 LayoutRect virtualStartingRect = startingRect; 720 switch (type) { 721 case FocusTypeLeft: 722 virtualStartingRect.setX(virtualStartingRect.maxX() - width); 723 virtualStartingRect.setWidth(width); 724 break; 725 case FocusTypeUp: 726 virtualStartingRect.setY(virtualStartingRect.maxY() - width); 727 virtualStartingRect.setHeight(width); 728 break; 729 case FocusTypeRight: 730 virtualStartingRect.setWidth(width); 731 break; 732 case FocusTypeDown: 733 virtualStartingRect.setHeight(width); 734 break; 735 default: 736 ASSERT_NOT_REACHED(); 737 } 738 739 return virtualStartingRect; 740 } 741 742 LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement& area, FocusType type) 743 { 744 ASSERT(area.imageElement()); 745 // Area elements tend to overlap more than other focusable elements. We flatten the rect of the area elements 746 // to minimize the effect of overlapping areas. 747 LayoutRect rect = virtualRectForDirection(type, rectToAbsoluteCoordinates(area.document().frame(), area.computeRect(area.imageElement()->renderer())), 1); 748 return rect; 749 } 750 751 HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate) 752 { 753 return candidate.isFrameOwnerElement() ? toHTMLFrameOwnerElement(candidate.visibleNode) : 0; 754 }; 755 756 } // namespace blink 757