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