WebCore/ChangeLog

 12010-11-15 Yael Aharon <yael.aharon@nokia.com>, Chang Shu <chang.shu@nokia.com>
 2
 3 Reviewed by NOBODY (OOPS!).
 4
 5 Spatial Navigation: issues with the node selection algorithm.
 6 https://bugs.webkit.org/show_bug.cgi?id=49382
 7
 8 Modify the Spatial Navigation algorithm, to better handle initial focus and
 9 navigation between frames.
 10 The new algorithm takes the rect of the focused node as the startingRect,
 11 instead of the node itself. That allows us to construct a virtual rect if
 12 there is no focused node, or if it is off the screen.
 13 The virtual rect is the edge of the container in the direction of the navigation.
 14
 15 With this patch, scrollable containers and frames will scroll regardless of weather
 16 they have focusable content. Users will be able to use arrow keys to view all the
 17 content of such a container. The only exception is if the container has style overflow:hidden.
 18 We will not scroll in that case.
 19
 20 With this patch, we handle z-index and positioning so that if there are 2 overlapping focusable nodes,
 21 we do a hit test and only the node on top can get focus.
 22
 23 hasOffScreenRect() was modified so that it can check if a node will be off-screen even after we scrolled
 24 its parent container.
 25
 26 * page/FocusController.cpp:
 27 (WebCore::FocusController::advanceFocus):
 28 (WebCore::FocusController::advanceFocusDirectionally):
 29 (WebCore::updateFocusCandidateIfNeeded):
 30 (WebCore::FocusController::findFocusCandidateInContainer):
 31 (WebCore::FocusController::navigateInContainer):
 32 (WebCore::FocusController::navigateIn4WayDirection):
 33 * page/FocusController.h:
 34 * page/FrameView.h:
 35 * page/SpatialNavigation.cpp:
 36 (WebCore::FocusCandidate::FocusCandidate):
 37 (WebCore::distanceDataForNode):
 38 (WebCore::alignmentForRects):
 39 (WebCore::areRectsMoreThanFullScreenApart):
 40 (WebCore::hasOffscreenRect):
 41 (WebCore::scrollInDirection):
 42 (WebCore::isScrollableContainerNode):
 43 (WebCore::scrollableOrFrameParentForNodeAndDirection):
 44 (WebCore::canScrollInDirection):
 45 (WebCore::rectToAbsoluteCoordinates):
 46 (WebCore::nodeRectInAbsoluteCoordinates):
 47 (WebCore::frameRectInAbsoluteCoordinates):
 48 (WebCore::entryAndExitPointsForDirection):
 49 (WebCore::canBeScrolledIntoView):
 50 * page/SpatialNavigation.h:
 51
1522010-11-15 Adele Peterson <adele@apple.com>
253
354 Reviewed by Darin Adler.
72053

WebCore/page/FocusController.cpp

4040#include "Frame.h"
4141#include "FrameTree.h"
4242#include "FrameView.h"
 43#include "HitTestResult.h"
4344#include "HTMLFrameOwnerElement.h"
4445#include "HTMLNames.h"
4546#include "KeyboardEvent.h"
4647#include "Page.h"
4748#include "Range.h"
 49#include "RenderLayer.h"
4850#include "RenderObject.h"
4951#include "RenderWidget.h"
5052#include "SelectionController.h"

@@namespace WebCore {
5759using namespace HTMLNames;
5860using namespace std;
5961
 62static void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest);
6063static inline void dispatchEventsOnWindowAndFocusedNode(Document* document, bool focused)
6164{
6265 // If we have a focused node we should dispatch blur on it before we blur the window.

@@bool FocusController::advanceFocus(Focus
175178 case FocusDirectionRight:
176179 case FocusDirectionUp:
177180 case FocusDirectionDown:
178  return advanceFocusDirectionally(direction, event);
 181 return navigateIn4WayDirection(direction, event);
179182 default:
180183 ASSERT_NOT_REACHED();
181184 }

@@bool FocusController::advanceFocusDirect
325328 // if |node| element is not in the viewport.
326329 if (hasOffscreenRect(node)) {
327330 Frame* frame = node->document()->view()->frame();
328  scrollInDirection(frame, direction, focusCandidate);
 331 scrollInDirection(frame, direction);
329332 return true;
330333 }
331334

@@void FocusController::setActive(bool act
658661 dispatchEventsOnWindowAndFocusedNode(m_focusedFrame->document(), active);
659662}
660663
 664void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest)
 665{
 666 if (!candidate.node->isElementNode() ||!candidate.node->renderer())
 667 return;
 668
 669 // Ignore empty frames
 670 if (candidate.node->isFrameOwnerElement() && !static_cast<HTMLFrameOwnerElement*>(candidate.node)->contentFrame())
 671 return;
 672
 673 // Ignore off screen child nodes of containers that do not scroll (overflow:hidden)
 674 if (hasOffscreenRect(candidate.node) && !canBeScrolledIntoView(direction, candidate))
 675 return;
 676
 677 FocusCandidate current;
 678 current.rect = startingRect;
 679 distanceDataForNode(direction, current, candidate);
 680 if (candidate.distance == maxDistance())
 681 return;
 682
 683 if (hasOffscreenRect(candidate.node, direction) && candidate.alignment < Full)
 684 return;
 685
 686 if (closest.isNull()) {
 687 closest = candidate;
 688 return;
 689 }
 690
 691 IntRect intersectionRect = intersection(nodeRectInAbsoluteCoordinates(candidate.node, true), nodeRectInAbsoluteCoordinates(closest.node, true));
 692 if (!intersectionRect.isEmpty()) {
 693 // If 2 nodes are intersecting, do hit test to find which node in on top.
 694 int x = intersectionRect.x() + intersectionRect.width() / 2;
 695 int y = intersectionRect.y() + intersectionRect.height() / 2;
 696 HitTestResult result = candidate.node->document()->page()->mainFrame()->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), false, true);
 697 if (candidate.node->contains(result.innerNode())) {
 698 closest = candidate;
 699 return;
 700 }
 701 if (closest.node->contains(result.innerNode()))
 702 return;
 703 }
 704
 705 if (candidate.alignment == closest.alignment) {
 706 if (candidate.distance < closest.distance)
 707 closest = candidate;
 708 return;
 709 }
 710
 711 if (candidate.alignment > closest.alignment)
 712 closest = candidate;
 713}
 714
 715void FocusController::findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closest)
 716{
 717 ASSERT(container);
 718 for (Node* node = container->firstChild(); node; node = (node->isFrameOwnerElement() || canScrollInDirection(direction, node)) ? node->traverseNextSibling(container) : node->traverseNextNode(container)) {
 719 if ((!focusedFrame() || !focusedFrame()->document() || node != focusedFrame()->document()->focusedNode()) && (node->isKeyboardFocusable(event) || node->isFrameOwnerElement() || canScrollInDirection(direction, node))) {
 720 FocusCandidate candidate(node);
 721 candidate.enclosingScrollableBox = container;
 722 updateFocusCandidateIfNeeded(direction, startingRect, candidate, closest);
 723 }
 724 }
 725}
 726
 727bool FocusController::navigateInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event)
 728{
 729 if (!container || !container->document())
 730 return false;
 731
 732 // The starting rect is the rect of the focused node, in document coordinates.
 733 // Compose a virtual starting rect if there is no focused node or if it is off screen.
 734 // The virtual rect is the edge of the container or frame. We select which
 735 // edge depending on the direction of the navigation.
 736 IntRect newStartingRect = startingRect;
 737
 738 if (startingRect.isEmpty()) {
 739 newStartingRect = nodeRectInAbsoluteCoordinates(container);
 740 switch (direction) {
 741 case FocusDirectionLeft:
 742 newStartingRect.setX(newStartingRect.right());
 743 newStartingRect.setWidth(0);
 744 break;
 745 case FocusDirectionUp:
 746 newStartingRect.setY(newStartingRect.bottom());
 747 newStartingRect.setHeight(0);
 748 break;
 749 case FocusDirectionRight:
 750 newStartingRect.setWidth(0);
 751 break;
 752 case FocusDirectionDown:
 753 newStartingRect.setHeight(0);
 754 break;
 755 default:
 756 ASSERT_NOT_REACHED();
 757 }
 758 }
 759
 760 // Find the closest node within current container in the direction of the navigation.
 761 FocusCandidate focusCandidate;
 762 findFocusCandidateInContainer(container, newStartingRect, direction, event, focusCandidate);
 763
 764 if (focusCandidate.isNull()) {
 765 if (canScrollInDirection(direction, container)) {
 766 // Nothing to focus, scroll if possible.
 767 scrollInDirection(container, direction);
 768 return true;
 769 }
 770 // Return false will cause a re-try, skipping this container.
 771 return false;
 772 }
 773 if (focusCandidate.node->isFrameOwnerElement()) {
 774 if (hasOffscreenRect(focusCandidate.node, direction)) {
 775 Frame* frame = focusCandidate.node->document()->view()->frame();
 776 scrollInDirection(frame->document(), direction);
 777 return true;
 778 }
 779 // Navigate into a new frame.
 780 IntRect rect;
 781 Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
 782 if (focusedNode && !hasOffscreenRect(focusedNode))
 783 rect = nodeRectInAbsoluteCoordinates(focusedNode, true);
 784 ASSERT(static_cast<HTMLFrameOwnerElement*>(focusCandidate.node)->contentFrame());
 785 static_cast<HTMLFrameOwnerElement*>(focusCandidate.node)->contentFrame()->document()->updateLayoutIgnorePendingStylesheets();
 786 if (!navigateInContainer(static_cast<HTMLFrameOwnerElement*>(focusCandidate.node)->contentFrame()->document(), rect, direction, event))
 787 // The new frame had nothing interesting, skip past it and try again.
 788 return navigateInContainer(container, nodeRectInAbsoluteCoordinates(focusCandidate.node, true), direction, event);
 789 return true;
 790 }
 791 if (canScrollInDirection(direction, focusCandidate.node)) {
 792 if (hasOffscreenRect(focusCandidate.node, direction)) {
 793 scrollInDirection(focusCandidate.node, direction);
 794 return true;
 795 }
 796 // Navigate into a new scrollable container.
 797 IntRect rect;
 798 Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
 799 if (focusedNode && !hasOffscreenRect(focusedNode))
 800 rect = nodeRectInAbsoluteCoordinates(focusedNode, true);
 801 return navigateInContainer(focusCandidate.node, rect, direction, event);
 802 }
 803 if (hasOffscreenRect(focusCandidate.node, direction)) {
 804 Node* container = focusCandidate.enclosingScrollableBox;
 805 scrollInDirection(container, direction);
 806 return true;
 807 }
 808
 809 // We found a new focus node, navigate to it.
 810 Element* element = static_cast<Element*>(focusCandidate.node);
 811 ASSERT(element);
 812
 813 element->focus(false);
 814 return true;
 815}
 816
 817bool FocusController::navigateIn4WayDirection(FocusDirection direction, KeyboardEvent* event)
 818{
 819 Frame* curFrame = focusedOrMainFrame();
 820 ASSERT(curFrame);
 821
 822 Document* focusedDocument = curFrame->document();
 823 if (!focusedDocument)
 824 return false;
 825
 826 Node* focusedNode = focusedDocument->focusedNode();
 827 Node* container = focusedDocument;
 828
 829 // Figure out the starting rect.
 830 IntRect startingRect;
 831 if (focusedNode && !hasOffscreenRect(focusedNode)) {
 832 container = scrollableOrFrameParentForNodeAndDirection(direction, focusedNode);
 833 startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true);
 834 }
 835
 836 bool consumed = false;
 837 do {
 838 if (container->isDocumentNode())
 839 static_cast<Document*>(container)->updateLayoutIgnorePendingStylesheets();
 840 consumed = navigateInContainer(container, startingRect, direction, event);
 841 startingRect = nodeRectInAbsoluteCoordinates(container, true);
 842 container = scrollableOrFrameParentForNodeAndDirection(direction, container);
 843 } while (!consumed && container);
 844 return consumed;
 845}
 846
661847} // namespace WebCore
71960

WebCore/page/FocusController.h

@@private:
6868 const FocusCandidate& parentCandidate = FocusCandidate());
6969 void deepFindFocusableNodeInDirection(Node* container, Node* focused, FocusDirection, KeyboardEvent*, FocusCandidate&);
7070
 71 bool navigateInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*);
 72 bool navigateIn4WayDirection(FocusDirection, KeyboardEvent*);
 73 void findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*, FocusCandidate& closest);
 74
7175 Page* m_page;
7276 RefPtr<Frame> m_focusedFrame;
7377 bool m_isActive;
71960

WebCore/page/FrameView.h

@@public:
235235 bool isFrameViewScrollCorner(RenderScrollbarPart* scrollCorner) const { return m_scrollCorner == scrollCorner; }
236236 void invalidateScrollCorner();
237237
 238 void calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode);
 239
238240 // Normal delay
239241 static void setRepaintThrottlingDeferredRepaintDelay(double p);
240242 // Negative value would mean that first few repaints happen without a delay

@@private:
264266 bool hasFixedObjects() const { return m_fixedObjectCount > 0; }
265267
266268 void applyOverflowToViewport(RenderObject*, ScrollbarMode& hMode, ScrollbarMode& vMode);
267  void calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode);
268269
269270 void updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow);
270271
71960

WebCore/page/SpatialNavigation.cpp

@@namespace WebCore {
4343
4444static long long spatialDistance(FocusDirection, const IntRect&, const IntRect&);
4545static IntRect renderRectRelativeToRootDocument(RenderObject*);
46 static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&);
 46static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&, const IntSize& viewSize);
4747static bool areRectsFullyAligned(FocusDirection, const IntRect&, const IntRect&);
4848static bool areRectsPartiallyAligned(FocusDirection, const IntRect&, const IntRect&);
 49static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize);
4950static bool isRectInDirection(FocusDirection, const IntRect&, const IntRect&);
5051static void deflateIfOverlapped(IntRect&, IntRect&);
5152static bool checkNegativeCoordsForNode(Node*, const IntRect&);
 53static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& rect);
 54static void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint);
 55
 56
 57FocusCandidate::FocusCandidate(Node* n)
 58 : node(n)
 59 , enclosingScrollableBox(0)
 60 , distance(maxDistance())
 61 , parentDistance(maxDistance())
 62 , alignment(None)
 63 , parentAlignment(None)
 64 , rect(nodeRectInAbsoluteCoordinates(n, true))
 65{
 66}
5267
5368bool isSpatialNavigationEnabled(const Frame* frame)
5469{

@@void distanceDataForNode(FocusDirection
102117 // The distance between two nodes is not to be considered alone when evaluating/looking
103118 // for the best focus candidate node. Alignment of rects can be also a good point to be
104119 // considered in order to make the algorithm to behavior in a more intuitive way.
105  candidate.alignment = alignmentForRects(direction, curRect, targetRect);
 120 IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
 121 candidate.alignment = alignmentForRects(direction, curRect, targetRect, viewSize);
106122 candidate.distance = spatialDistance(direction, curRect, targetRect);
107123}
108124

@@static IntRect renderRectRelativeToRootD
136152 return rect;
137153}
138154
139 static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
 155static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
140156{
 157 // If we found a node in full alignment, but it is too far away, ignore it.
 158 if (areRectsMoreThanFullScreenApart(direction, curRect, targetRect, viewSize))
 159 return None;
 160
141161 if (areRectsFullyAligned(direction, curRect, targetRect))
142162 return Full;
143163

@@static bool areRectsPartiallyAligned(Foc
277297 || (bEnd >= aStart && bEnd <= aEnd));
278298}
279299
 300static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
 301{
 302 switch (direction) {
 303 case FocusDirectionLeft:
 304 return curRect.x() < targetRect.right() || curRect.x() - targetRect.right() > viewSize.width();
 305 case FocusDirectionRight:
 306 return targetRect.x() < curRect.right() || targetRect.x() - curRect.right() > viewSize.width();
 307 case FocusDirectionUp:
 308 return curRect.y() < targetRect.bottom() || curRect.y() - targetRect.bottom() > viewSize.height();
 309 case FocusDirectionDown:
 310 return targetRect.y() < curRect.bottom() || targetRect.y() - curRect.bottom() > viewSize.height();
 311 default:
 312 ASSERT_NOT_REACHED();
 313 return true;
 314 }
 315}
 316
280317// Return true if rect |a| is below |b|. False otherwise.
281318static inline bool below(const IntRect& a, const IntRect& b)
282319{

@@static bool isRectInDirection(FocusDirec
425462// Checks if |node| is offscreen the visible area (viewport) of its container
426463// document. In case it is, one can scroll in direction or take any different
427464// desired action later on.
428 bool hasOffscreenRect(Node* node)
 465bool hasOffscreenRect(Node* node, FocusDirection direction)
429466{
430467 // Get the FrameView in which |node| is (which means the current viewport if |node|
431468 // is not in an inner document), so we can check if its content rect is visible

@@bool hasOffscreenRect(Node* node)
435472 return true;
436473
437474 IntRect containerViewportRect = frameView->visibleContentRect();
 475 // We want to select a node if it is currently off screen, but will be
 476 // exposed after we scroll. Adjust the viewport to post-scrolling position.
 477 switch (direction) {
 478 case FocusDirectionLeft:
 479 containerViewportRect.setX(containerViewportRect.x() - Scrollbar::pixelsPerLineStep());
 480 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
 481 break;
 482 case FocusDirectionRight:
 483 containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
 484 break;
 485 case FocusDirectionUp:
 486 containerViewportRect.setY(containerViewportRect.y() - Scrollbar::pixelsPerLineStep());
 487 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
 488 break;
 489 case FocusDirectionDown:
 490 containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
 491 break;
 492 default:
 493 break;
 494 }
438495
439496 RenderObject* render = node->renderer();
440497 if (!render)

@@bool hasOffscreenRect(Node* node)
447504 return !containerViewportRect.intersects(rect);
448505}
449506
450 // In a bottom-up way, this method tries to scroll |frame| in a given direction
451 // |direction|, going up in the frame tree hierarchy in case it does not succeed.
452 bool scrollInDirection(Frame* frame, FocusDirection direction, const FocusCandidate& candidate)
 507bool scrollInDirection(Frame* frame, FocusDirection direction)
453508{
454  if (!frame)
455  return false;
 509 ASSERT(frame && canScrollInDirection(direction, frame->document()));
456510
457  ScrollDirection scrollDirection;
 511 if (frame && canScrollInDirection(direction, frame->document())) {
 512 int dx = 0;
 513 int dy = 0;
 514 switch (direction) {
 515 case FocusDirectionLeft:
 516 dx = - Scrollbar::pixelsPerLineStep();
 517 break;
 518 case FocusDirectionRight:
 519 dx = Scrollbar::pixelsPerLineStep();
 520 break;
 521 case FocusDirectionUp:
 522 dy = - Scrollbar::pixelsPerLineStep();
 523 break;
 524 case FocusDirectionDown:
 525 dy = Scrollbar::pixelsPerLineStep();
 526 break;
 527 default:
 528 ASSERT_NOT_REACHED();
 529 }
458530
459  switch (direction) {
460  case FocusDirectionLeft:
461  scrollDirection = ScrollLeft;
462  break;
463  case FocusDirectionRight:
464  scrollDirection = ScrollRight;
465  break;
466  case FocusDirectionUp:
467  scrollDirection = ScrollUp;
468  break;
469  case FocusDirectionDown:
470  scrollDirection = ScrollDown;
471  break;
472  default:
473  return false;
 531 frame->view()->scrollBy(IntSize(dx, dy));
 532 return true;
474533 }
 534 return false;
 535}
 536
 537bool scrollInDirection(Node* container, FocusDirection direction)
 538{
 539 if (container->isDocumentNode())
 540 return scrollInDirection(static_cast<Document*>(container)->frame(), direction);
475541
476  if (!candidate.isNull() && isScrollableContainerNode(candidate.enclosingScrollableBox))
477  return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine, candidate.enclosingScrollableBox);
 542 if (!container->renderBox())
 543 return false;
478544
479  return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine);
 545 if (container && canScrollInDirection(direction, container)) {
 546 int dx = 0;
 547 int dy = 0;
 548 switch (direction) {
 549 case FocusDirectionLeft:
 550 dx = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollLeft());
 551 break;
 552 case FocusDirectionRight:
 553 ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
 554 dx = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
 555 break;
 556 case FocusDirectionUp:
 557 dy = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollTop());
 558 break;
 559 case FocusDirectionDown:
 560 ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
 561 dy = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
 562 break;
 563 default:
 564 ASSERT_NOT_REACHED();
 565 }
 566
 567 container->renderBox()->enclosingLayer()->scrollByRecursively(dx, dy);
 568
 569 return true;
 570 }
 571 return false;
480572}
481573
482574void scrollIntoView(Element* element)

@@static bool checkNegativeCoordsForNode(N
534626 return canBeScrolled;
535627}
536628
537 bool isScrollableContainerNode(Node* node)
 629bool isScrollableContainerNode(const Node* node)
538630{
539631 if (!node)
540632 return false;

@@bool isNodeDeepDescendantOfDocument(Node
567659 return descendant;
568660}
569661
 662Node* scrollableOrFrameParentForNodeAndDirection(FocusDirection direction, Node* node)
 663{
 664 ASSERT(node);
 665 Node* parent = node;
 666 do {
 667 if (parent->isDocumentNode()) // This is true only if node is a document node.
 668 parent = static_cast<Document*>(parent)->document()->frame()->ownerElement();
 669 else
 670 parent = parent->parentNode();
 671 } while (parent && !canScrollInDirection(direction, parent) && !parent->isDocumentNode());
 672 return parent;
 673}
 674
 675bool canScrollInDirection(FocusDirection direction, const Node* container)
 676{
 677 ASSERT(container);
 678 if (container->isDocumentNode())
 679 return canScrollInDirection(direction, static_cast<const Document*>(container)->frame());
 680
 681 if (!isScrollableContainerNode(container))
 682 return false;
 683
 684 switch (direction) {
 685 case FocusDirectionLeft:
 686 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
 687 case FocusDirectionUp:
 688 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
 689 case FocusDirectionRight:
 690 return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
 691 case FocusDirectionDown:
 692 return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
 693 default:
 694 ASSERT_NOT_REACHED();
 695 return false;
 696 }
 697}
 698
 699bool canScrollInDirection(FocusDirection direction, const Frame* frame)
 700{
 701 if (!frame->view())
 702 return false;
 703 ScrollbarMode verticalMode;
 704 ScrollbarMode horizontalMode;
 705 frame->view()->calculateScrollbarModesForLayout(horizontalMode, verticalMode);
 706 if ((direction == FocusDirectionLeft || direction == FocusDirectionRight) && ScrollbarAlwaysOff == horizontalMode)
 707 return false;
 708 if ((direction == FocusDirectionUp || direction == FocusDirectionDown) && ScrollbarAlwaysOff == verticalMode)
 709 return false;
 710 IntSize size = frame->view()->contentsSize();
 711 IntSize offset = frame->view()->scrollOffset();
 712 IntRect rect = frame->view()->visibleContentRect(true);
 713
 714 switch (direction) {
 715 case FocusDirectionLeft:
 716 return offset.width() > 0;
 717 case FocusDirectionUp:
 718 return offset.height() > 0;
 719 case FocusDirectionRight:
 720 return rect.width() + offset.width() < size.width();
 721 case FocusDirectionDown:
 722 return rect.height() + offset.height() < size.height();
 723 default:
 724 ASSERT_NOT_REACHED();
 725 }
 726 return false;
 727}
 728
 729static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& initialRect)
 730{
 731 IntRect rect = initialRect;
 732 for (Frame* frame = initialFrame; frame; frame = frame->tree()->parent()) {
 733 if (Element* element = static_cast<Element*>(frame->ownerElement())) {
 734 do {
 735 rect.move(element->offsetLeft(), element->offsetTop());
 736 } while ((element = element->offsetParent()));
 737 rect.move((-frame->view()->scrollOffset()));
 738 }
 739 }
 740 return rect;
 741}
 742
 743IntRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
 744{
 745 ASSERT(node && node->renderer());
 746
 747 if (node->isDocumentNode())
 748 return frameRectInAbsoluteCoordinates(static_cast<Document*>(node)->frame());
 749 IntRect rect = rectToAbsoluteCoordinates(node->document()->frame(), node->getRect());
 750
 751 // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
 752 // the rect of the focused element.
 753 if (ignoreBorder) {
 754 rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth());
 755 rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth());
 756 rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth());
 757 }
 758 return rect;
 759}
 760
 761IntRect frameRectInAbsoluteCoordinates(Frame* frame)
 762{
 763 return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
 764}
 765
 766// Exit point is the closest point in the startingRect, depending on the direction of the navigation.
 767// Entry point is the closest point in the candidate node, depending on the direction of the navigation.
 768void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint)
 769{
 770 switch (direction) {
 771 case FocusDirectionLeft:
 772 exitPoint.setX(startingRect.x());
 773 entryPoint.setX(potentialRect.right());
 774 break;
 775 case FocusDirectionUp:
 776 exitPoint.setY(startingRect.y());
 777 entryPoint.setY(potentialRect.bottom());
 778 break;
 779 case FocusDirectionRight:
 780 exitPoint.setX(startingRect.right());
 781 entryPoint.setX(potentialRect.x());
 782 break;
 783 case FocusDirectionDown:
 784 exitPoint.setY(startingRect.bottom());
 785 entryPoint.setY(potentialRect.y());
 786 break;
 787 default:
 788 ASSERT_NOT_REACHED();
 789 }
 790
 791 switch (direction) {
 792 case FocusDirectionLeft:
 793 case FocusDirectionRight:
 794 if (below(startingRect, potentialRect)) {
 795 exitPoint.setY(startingRect.y());
 796 entryPoint.setY(potentialRect.bottom());
 797 } else if (below(potentialRect, startingRect)) {
 798 exitPoint.setY(startingRect.bottom());
 799 entryPoint.setY(potentialRect.y());
 800 } else {
 801 exitPoint.setY(max(startingRect.y(), potentialRect.y()));
 802 entryPoint.setY(exitPoint.y());
 803 }
 804 break;
 805 case FocusDirectionUp:
 806 case FocusDirectionDown:
 807 if (rightOf(startingRect, potentialRect)) {
 808 exitPoint.setX(startingRect.x());
 809 entryPoint.setX(potentialRect.right());
 810 } else if (rightOf(potentialRect, startingRect)) {
 811 exitPoint.setX(startingRect.right());
 812 entryPoint.setX(potentialRect.x());
 813 } else {
 814 exitPoint.setX(max(startingRect.x(), potentialRect.x()));
 815 entryPoint.setX(exitPoint.x());
 816 }
 817 break;
 818 default:
 819 ASSERT_NOT_REACHED();
 820 }
 821}
 822
 823void distanceDataForNode(FocusDirection direction, FocusCandidate& current, FocusCandidate& candidate)
 824{
 825 if (candidate.isNull())
 826 return;
 827 if (!candidate.node->renderer())
 828 return;
 829 IntRect nodeRect = nodeRectInAbsoluteCoordinates(candidate.node, true);
 830 IntRect currentRect = current.rect;
 831 deflateIfOverlapped(currentRect, nodeRect);
 832
 833 switch (direction) {
 834 case FocusDirectionLeft:
 835 if (nodeRect.right() > currentRect.x())
 836 return;
 837 break;
 838 case FocusDirectionUp:
 839 if (nodeRect.bottom() > currentRect.y())
 840 return;
 841 break;
 842 case FocusDirectionRight:
 843 if (nodeRect.x() < currentRect.right())
 844 return;
 845 break;
 846 case FocusDirectionDown:
 847 if (nodeRect.y() < currentRect.bottom())
 848 return;
 849 break;
 850 default:
 851 ASSERT_NOT_REACHED();
 852 }
 853
 854 IntPoint exitPoint;
 855 IntPoint entryPoint;
 856 int sameAxisDist = 0;
 857 int otherAxisDist = 0;
 858 entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint);
 859
 860 switch (direction) {
 861 case FocusDirectionLeft:
 862 sameAxisDist = exitPoint.x() - entryPoint.x();
 863 otherAxisDist = abs(exitPoint.y() - entryPoint.y());
 864 break;
 865 case FocusDirectionUp:
 866 sameAxisDist = exitPoint.y() - entryPoint.y();
 867 otherAxisDist = abs(exitPoint.x() - entryPoint.x());
 868 break;
 869 case FocusDirectionRight:
 870 sameAxisDist = entryPoint.x() - exitPoint.x();
 871 otherAxisDist = abs(entryPoint.y() - exitPoint.y());
 872 break;
 873 case FocusDirectionDown:
 874 sameAxisDist = entryPoint.y() - exitPoint.y();
 875 otherAxisDist = abs(entryPoint.x() - exitPoint.x());
 876 break;
 877 default:
 878 ASSERT_NOT_REACHED();
 879 }
 880
 881 int x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x());
 882 int y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y());
 883
 884 float euclidianDistance = sqrt((x + y) * 1.0f);
 885
 886 // Loosely based on http://www.w3.org/TR/WICD/#focus-handling
 887 // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap)
 888
 889 float distance = euclidianDistance + sameAxisDist + 2 * otherAxisDist;
 890 candidate.distance = roundf(distance);
 891 IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
 892 candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize);
 893}
 894
 895bool canBeScrolledIntoView(FocusDirection direction, FocusCandidate& candidate)
 896{
 897 ASSERT(candidate.node && hasOffscreenRect(candidate.node));
 898 IntRect candidateRect = nodeRectInAbsoluteCoordinates(candidate.node);
 899 for (Node* parentNode = candidate.node->parent(); parentNode; parentNode = parentNode->parent()) {
 900 IntRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
 901 if (!candidateRect.intersects(parentRect)) {
 902 if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
 903 || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
 904 return false;
 905 }
 906 if (parentNode == candidate.enclosingScrollableBox)
 907 return canScrollInDirection(direction, parentNode);
 908 }
 909 return true;
 910}
 911
570912} // namespace WebCore
71960

WebCore/page/SpatialNavigation.h

2222#define SpatialNavigation_h
2323
2424#include "FocusDirection.h"
 25#include "IntRect.h"
2526#include "Node.h"
2627
2728#include <limits>

@@struct FocusCandidate {
107108 {
108109 }
109110
110  FocusCandidate(Node* n)
111  : node(n)
112  , enclosingScrollableBox(0)
113  , distance(maxDistance())
114  , parentDistance(maxDistance())
115  , alignment(None)
116  , parentAlignment(None)
117  {
118  }
119 
 111 FocusCandidate(Node* n);
120112 bool isNull() const { return !node; }
121113 bool inScrollableContainer() const { return node && enclosingScrollableBox; }
122114 Document* document() const { return node ? node->document() : 0; }

@@struct FocusCandidate {
127119 long long parentDistance;
128120 RectsAlignment alignment;
129121 RectsAlignment parentAlignment;
 122 IntRect rect;
130123};
131124
132125void distanceDataForNode(FocusDirection direction, Node* start, FocusCandidate& candidate);
133 bool scrollInDirection(Frame*, FocusDirection, const FocusCandidate& candidate = FocusCandidate());
 126bool scrollInDirection(Frame*, FocusDirection);
 127bool scrollInDirection(Node* container, FocusDirection);
134128void scrollIntoView(Element*);
135 bool hasOffscreenRect(Node*);
 129bool hasOffscreenRect(Node*, FocusDirection direction = FocusDirectionNone);
136130bool isInRootDocument(Node*);
137 bool isScrollableContainerNode(Node*);
 131bool isScrollableContainerNode(const Node*);
138132bool isNodeDeepDescendantOfDocument(Node*, Document*);
139 
 133Node* scrollableOrFrameParentForNodeAndDirection(FocusDirection, Node* node);
 134bool canScrollInDirection(FocusDirection, const Node* container);
 135bool canScrollInDirection(FocusDirection, const Frame*);
 136IntRect nodeRectInAbsoluteCoordinates(Node*, bool ignoreBorder = false);
 137IntRect frameRectInAbsoluteCoordinates(Frame*);
 138void distanceDataForNode(FocusDirection, FocusCandidate& current, FocusCandidate& candidate);
 139bool canBeScrolledIntoView(FocusDirection, FocusCandidate&);
140140} // namspace WebCore
141141
142142#endif // SpatialNavigation_h
71960