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