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