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