| /* |
| * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies) |
| * Copyright (C) 2009 Antonio Gomes <tonikitoo@webkit.org> |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/page/SpatialNavigation.h" |
| |
| #include "core/HTMLNames.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/HTMLAreaElement.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/HTMLImageElement.h" |
| #include "core/layout/LayoutBox.h" |
| #include "core/page/FrameTree.h" |
| #include "core/page/Page.h" |
| #include "platform/geometry/IntRect.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| static void deflateIfOverlapped(LayoutRect&, LayoutRect&); |
| static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect&); |
| static bool isScrollableNode(const Node*); |
| |
| FocusCandidate::FocusCandidate(Node* node, WebFocusType type) |
| : visibleNode(nullptr) |
| , focusableNode(nullptr) |
| , enclosingScrollableBox(nullptr) |
| , distance(maxDistance()) |
| , isOffscreen(true) |
| , isOffscreenAfterScrolling(true) |
| { |
| ASSERT(node); |
| ASSERT(node->isElementNode()); |
| |
| if (isHTMLAreaElement(*node)) { |
| HTMLAreaElement& area = toHTMLAreaElement(*node); |
| HTMLImageElement* image = area.imageElement(); |
| if (!image || !image->layoutObject()) |
| return; |
| |
| visibleNode = image; |
| rect = virtualRectForAreaElementAndDirection(area, type); |
| } else { |
| if (!node->layoutObject()) |
| return; |
| |
| visibleNode = node; |
| rect = nodeRectInAbsoluteCoordinates(node, true /* ignore border */); |
| } |
| |
| focusableNode = node; |
| isOffscreen = hasOffscreenRect(visibleNode); |
| isOffscreenAfterScrolling = hasOffscreenRect(visibleNode, type); |
| } |
| |
| bool isSpatialNavigationEnabled(const LocalFrame* frame) |
| { |
| return (frame && frame->settings() && frame->settings()->spatialNavigationEnabled()); |
| } |
| |
| bool spatialNavigationIgnoresEventHandlers(const LocalFrame* frame) |
| { |
| return (frame && frame->settings() && frame->settings()->deviceSupportsTouch()); |
| } |
| |
| static bool rectsIntersectOnOrthogonalAxis(WebFocusType type, const LayoutRect& a, const LayoutRect& b) |
| { |
| switch (type) { |
| case WebFocusTypeLeft: |
| case WebFocusTypeRight: |
| return a.maxY() > b.y() && a.y() < b.maxY(); |
| case WebFocusTypeUp: |
| case WebFocusTypeDown: |
| return a.maxX() > b.x() && a.x() < b.maxX(); |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| } |
| |
| // Return true if rect |a| is below |b|. False otherwise. |
| // For overlapping rects, |a| is considered to be below |b| |
| // if both edges of |a| are below the respective ones of |b| |
| static inline bool below(const LayoutRect& a, const LayoutRect& b) |
| { |
| return a.y() >= b.maxY() |
| || (a.y() >= b.y() && a.maxY() > b.maxY() && a.x() < b.maxX() && a.maxX() > b.x()); |
| } |
| |
| // Return true if rect |a| is on the right of |b|. False otherwise. |
| // For overlapping rects, |a| is considered to be on the right of |b| |
| // if both edges of |a| are on the right of the respective ones of |b| |
| static inline bool rightOf(const LayoutRect& a, const LayoutRect& b) |
| { |
| return a.x() >= b.maxX() |
| || (a.x() >= b.x() && a.maxX() > b.maxX() && a.y() < b.maxY() && a.maxY() > b.y()); |
| } |
| |
| static bool isRectInDirection(WebFocusType type, const LayoutRect& curRect, const LayoutRect& targetRect) |
| { |
| switch (type) { |
| case WebFocusTypeLeft: |
| return rightOf(curRect, targetRect); |
| case WebFocusTypeRight: |
| return rightOf(targetRect, curRect); |
| case WebFocusTypeUp: |
| return below(curRect, targetRect); |
| case WebFocusTypeDown: |
| return below(targetRect, curRect); |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| } |
| |
| // Checks if |node| is offscreen the visible area (viewport) of its container |
| // document. In case it is, one can scroll in direction or take any different |
| // desired action later on. |
| bool hasOffscreenRect(Node* node, WebFocusType type) |
| { |
| // Get the FrameView in which |node| is (which means the current viewport if |node| |
| // is not in an inner document), so we can check if its content rect is visible |
| // before we actually move the focus to it. |
| FrameView* frameView = node->document().view(); |
| if (!frameView) |
| return true; |
| |
| ASSERT(!frameView->needsLayout()); |
| |
| LayoutRect containerViewportRect(frameView->visibleContentRect()); |
| // We want to select a node if it is currently off screen, but will be |
| // exposed after we scroll. Adjust the viewport to post-scrolling position. |
| // If the container has overflow:hidden, we cannot scroll, so we do not pass direction |
| // and we do not adjust for scrolling. |
| int pixelsPerLineStep = ScrollableArea::pixelsPerLineStep(frameView->hostWindow()); |
| switch (type) { |
| case WebFocusTypeLeft: |
| containerViewportRect.setX(containerViewportRect.x() - pixelsPerLineStep); |
| containerViewportRect.setWidth(containerViewportRect.width() + pixelsPerLineStep); |
| break; |
| case WebFocusTypeRight: |
| containerViewportRect.setWidth(containerViewportRect.width() + pixelsPerLineStep); |
| break; |
| case WebFocusTypeUp: |
| containerViewportRect.setY(containerViewportRect.y() - pixelsPerLineStep); |
| containerViewportRect.setHeight(containerViewportRect.height() + pixelsPerLineStep); |
| break; |
| case WebFocusTypeDown: |
| containerViewportRect.setHeight(containerViewportRect.height() + pixelsPerLineStep); |
| break; |
| default: |
| break; |
| } |
| |
| LayoutObject* layoutObject = node->layoutObject(); |
| if (!layoutObject) |
| return true; |
| |
| LayoutRect rect(layoutObject->absoluteClippedOverflowRect()); |
| if (rect.isEmpty()) |
| return true; |
| |
| return !containerViewportRect.intersects(rect); |
| } |
| |
| bool scrollInDirection(LocalFrame* frame, WebFocusType type) |
| { |
| ASSERT(frame); |
| |
| if (frame && canScrollInDirection(frame->document(), type)) { |
| int dx = 0; |
| int dy = 0; |
| int pixelsPerLineStep = ScrollableArea::pixelsPerLineStep(frame->view()->hostWindow()); |
| switch (type) { |
| case WebFocusTypeLeft: |
| dx = - pixelsPerLineStep; |
| break; |
| case WebFocusTypeRight: |
| dx = pixelsPerLineStep; |
| break; |
| case WebFocusTypeUp: |
| dy = - pixelsPerLineStep; |
| break; |
| case WebFocusTypeDown: |
| dy = pixelsPerLineStep; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| frame->view()->scrollBy(IntSize(dx, dy), UserScroll); |
| return true; |
| } |
| return false; |
| } |
| |
| bool scrollInDirection(Node* container, WebFocusType type) |
| { |
| ASSERT(container); |
| if (container->isDocumentNode()) |
| return scrollInDirection(toDocument(container)->frame(), type); |
| |
| if (!container->layoutBox()) |
| return false; |
| |
| if (canScrollInDirection(container, type)) { |
| int dx = 0; |
| int dy = 0; |
| // TODO(leviw): Why are these values truncated (toInt) instead of rounding? |
| FrameView* frameView = container->document().view(); |
| int pixelsPerLineStep = ScrollableArea::pixelsPerLineStep(frameView ? frameView->hostWindow() : nullptr); |
| switch (type) { |
| case WebFocusTypeLeft: |
| dx = - std::min(pixelsPerLineStep, container->layoutBox()->scrollLeft().toInt()); |
| break; |
| case WebFocusTypeRight: |
| ASSERT(container->layoutBox()->scrollWidth() > (container->layoutBox()->scrollLeft() + container->layoutBox()->clientWidth())); |
| dx = std::min(pixelsPerLineStep, |
| (container->layoutBox()->scrollWidth() - (container->layoutBox()->scrollLeft() + container->layoutBox()->clientWidth())).toInt()); |
| break; |
| case WebFocusTypeUp: |
| dy = - std::min(pixelsPerLineStep, container->layoutBox()->scrollTop().toInt()); |
| break; |
| case WebFocusTypeDown: |
| ASSERT(container->layoutBox()->scrollHeight() - (container->layoutBox()->scrollTop() + container->layoutBox()->clientHeight())); |
| dy = std::min(pixelsPerLineStep, |
| (container->layoutBox()->scrollHeight() - (container->layoutBox()->scrollTop() + container->layoutBox()->clientHeight())).toInt()); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| container->layoutBox()->scrollByRecursively(IntSize(dx, dy)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b) |
| { |
| if (!a.intersects(b) || a.contains(b) || b.contains(a)) |
| return; |
| |
| LayoutUnit deflateFactor = LayoutUnit(-fudgeFactor()); |
| |
| // Avoid negative width or height values. |
| if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0)) |
| a.inflate(deflateFactor); |
| |
| if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0)) |
| b.inflate(deflateFactor); |
| } |
| |
| bool isScrollableNode(const Node* node) |
| { |
| ASSERT(!node->isDocumentNode()); |
| |
| if (!node) |
| return false; |
| |
| if (LayoutObject* layoutObject = node->layoutObject()) |
| return layoutObject->isBox() && toLayoutBox(layoutObject)->canBeScrolledAndHasScrollableArea() && node->hasChildren(); |
| |
| return false; |
| } |
| |
| Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(WebFocusType type, Node* node) |
| { |
| ASSERT(node); |
| Node* parent = node; |
| do { |
| // FIXME: Spatial navigation is broken for OOPI. |
| if (parent->isDocumentNode()) |
| parent = toDocument(parent)->frame()->deprecatedLocalOwner(); |
| else |
| parent = parent->parentOrShadowHostNode(); |
| } while (parent && !canScrollInDirection(parent, type) && !parent->isDocumentNode()); |
| |
| return parent; |
| } |
| |
| bool canScrollInDirection(const Node* container, WebFocusType type) |
| { |
| ASSERT(container); |
| if (container->isDocumentNode()) |
| return canScrollInDirection(toDocument(container)->frame(), type); |
| |
| if (!isScrollableNode(container)) |
| return false; |
| |
| switch (type) { |
| case WebFocusTypeLeft: |
| return (container->layoutObject()->style()->overflowX() != OHIDDEN && container->layoutBox()->scrollLeft() > 0); |
| case WebFocusTypeUp: |
| return (container->layoutObject()->style()->overflowY() != OHIDDEN && container->layoutBox()->scrollTop() > 0); |
| case WebFocusTypeRight: |
| return (container->layoutObject()->style()->overflowX() != OHIDDEN && container->layoutBox()->scrollLeft() + container->layoutBox()->clientWidth() < container->layoutBox()->scrollWidth()); |
| case WebFocusTypeDown: |
| return (container->layoutObject()->style()->overflowY() != OHIDDEN && container->layoutBox()->scrollTop() + container->layoutBox()->clientHeight() < container->layoutBox()->scrollHeight()); |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| } |
| |
| bool canScrollInDirection(const LocalFrame* frame, WebFocusType type) |
| { |
| if (!frame->view()) |
| return false; |
| ScrollbarMode verticalMode; |
| ScrollbarMode horizontalMode; |
| frame->view()->calculateScrollbarModes(horizontalMode, verticalMode); |
| if ((type == WebFocusTypeLeft || type == WebFocusTypeRight) && ScrollbarAlwaysOff == horizontalMode) |
| return false; |
| if ((type == WebFocusTypeUp || type == WebFocusTypeDown) && ScrollbarAlwaysOff == verticalMode) |
| return false; |
| LayoutSize size(frame->view()->contentsSize()); |
| LayoutSize offset(frame->view()->scrollOffset()); |
| LayoutRect rect(frame->view()->visibleContentRect(IncludeScrollbars)); |
| |
| switch (type) { |
| case WebFocusTypeLeft: |
| return offset.width() > 0; |
| case WebFocusTypeUp: |
| return offset.height() > 0; |
| case WebFocusTypeRight: |
| return rect.width() + offset.width() < size.width(); |
| case WebFocusTypeDown: |
| return rect.height() + offset.height() < size.height(); |
| default: |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| } |
| |
| static LayoutRect rectToAbsoluteCoordinates(LocalFrame* initialFrame, const LayoutRect& initialRect) |
| { |
| LayoutRect rect = initialRect; |
| for (Frame* frame = initialFrame; frame; frame = frame->tree().parent()) { |
| if (!frame->isLocalFrame()) |
| continue; |
| // FIXME: Spatial navigation is broken for OOPI. |
| Element* element = frame->deprecatedLocalOwner(); |
| if (element) { |
| do { |
| rect.move(element->offsetLeft(), element->offsetTop()); |
| LayoutObject* layoutObject = element->layoutObject(); |
| element = layoutObject ? layoutObject->offsetParent() : nullptr; |
| } while (element); |
| rect.move((-toLocalFrame(frame)->view()->scrollOffset())); |
| } |
| } |
| return rect; |
| } |
| |
| LayoutRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder) |
| { |
| ASSERT(node && node->layoutObject() && !node->document().view()->needsLayout()); |
| |
| if (node->isDocumentNode()) |
| return frameRectInAbsoluteCoordinates(toDocument(node)->frame()); |
| LayoutRect rect = rectToAbsoluteCoordinates(node->document().frame(), node->boundingBox()); |
| |
| // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating |
| // the rect of the focused element. |
| if (ignoreBorder) { |
| rect.move(node->layoutObject()->style()->borderLeftWidth(), node->layoutObject()->style()->borderTopWidth()); |
| rect.setWidth(rect.width() - node->layoutObject()->style()->borderLeftWidth() - node->layoutObject()->style()->borderRightWidth()); |
| rect.setHeight(rect.height() - node->layoutObject()->style()->borderTopWidth() - node->layoutObject()->style()->borderBottomWidth()); |
| } |
| return rect; |
| } |
| |
| LayoutRect frameRectInAbsoluteCoordinates(LocalFrame* frame) |
| { |
| return rectToAbsoluteCoordinates(frame, LayoutRect(frame->view()->visibleContentRect())); |
| } |
| |
| // This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect. |
| // The line between those 2 points is the closest distance between the 2 rects. |
| // Takes care of overlapping rects, defining points so that the distance between them |
| // is zero where necessary |
| void entryAndExitPointsForDirection(WebFocusType type, const LayoutRect& startingRect, const LayoutRect& potentialRect, LayoutPoint& exitPoint, LayoutPoint& entryPoint) |
| { |
| switch (type) { |
| case WebFocusTypeLeft: |
| exitPoint.setX(startingRect.x()); |
| if (potentialRect.maxX() < startingRect.x()) |
| entryPoint.setX(potentialRect.maxX()); |
| else |
| entryPoint.setX(startingRect.x()); |
| break; |
| case WebFocusTypeUp: |
| exitPoint.setY(startingRect.y()); |
| if (potentialRect.maxY() < startingRect.y()) |
| entryPoint.setY(potentialRect.maxY()); |
| else |
| entryPoint.setY(startingRect.y()); |
| break; |
| case WebFocusTypeRight: |
| exitPoint.setX(startingRect.maxX()); |
| if (potentialRect.x() > startingRect.maxX()) |
| entryPoint.setX(potentialRect.x()); |
| else |
| entryPoint.setX(startingRect.maxX()); |
| break; |
| case WebFocusTypeDown: |
| exitPoint.setY(startingRect.maxY()); |
| if (potentialRect.y() > startingRect.maxY()) |
| entryPoint.setY(potentialRect.y()); |
| else |
| entryPoint.setY(startingRect.maxY()); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| switch (type) { |
| case WebFocusTypeLeft: |
| case WebFocusTypeRight: |
| if (below(startingRect, potentialRect)) { |
| exitPoint.setY(startingRect.y()); |
| if (potentialRect.maxY() < startingRect.y()) |
| entryPoint.setY(potentialRect.maxY()); |
| else |
| entryPoint.setY(startingRect.y()); |
| } else if (below(potentialRect, startingRect)) { |
| exitPoint.setY(startingRect.maxY()); |
| if (potentialRect.y() > startingRect.maxY()) |
| entryPoint.setY(potentialRect.y()); |
| else |
| entryPoint.setY(startingRect.maxY()); |
| } else { |
| exitPoint.setY(max(startingRect.y(), potentialRect.y())); |
| entryPoint.setY(exitPoint.y()); |
| } |
| break; |
| case WebFocusTypeUp: |
| case WebFocusTypeDown: |
| if (rightOf(startingRect, potentialRect)) { |
| exitPoint.setX(startingRect.x()); |
| if (potentialRect.maxX() < startingRect.x()) |
| entryPoint.setX(potentialRect.maxX()); |
| else |
| entryPoint.setX(startingRect.x()); |
| } else if (rightOf(potentialRect, startingRect)) { |
| exitPoint.setX(startingRect.maxX()); |
| if (potentialRect.x() > startingRect.maxX()) |
| entryPoint.setX(potentialRect.x()); |
| else |
| entryPoint.setX(startingRect.maxX()); |
| } else { |
| exitPoint.setX(max(startingRect.x(), potentialRect.x())); |
| entryPoint.setX(exitPoint.x()); |
| } |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| } |
| |
| bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate) |
| { |
| if (firstCandidate.isNull() || secondCandidate.isNull()) |
| return false; |
| |
| if (!firstCandidate.visibleNode->layoutObject() || !secondCandidate.visibleNode->layoutObject()) |
| return false; |
| |
| if (!firstCandidate.rect.intersects(secondCandidate.rect)) |
| return false; |
| |
| if (isHTMLAreaElement(*firstCandidate.focusableNode) || isHTMLAreaElement(*secondCandidate.focusableNode)) |
| return false; |
| |
| if (!firstCandidate.visibleNode->layoutObject()->isLayoutInline() || !secondCandidate.visibleNode->layoutObject()->isLayoutInline()) |
| return false; |
| |
| if (firstCandidate.visibleNode->layoutObject()->containingBlock() != secondCandidate.visibleNode->layoutObject()->containingBlock()) |
| return false; |
| |
| return true; |
| } |
| |
| void distanceDataForNode(WebFocusType type, const FocusCandidate& current, FocusCandidate& candidate) |
| { |
| if (!isRectInDirection(type, current.rect, candidate.rect)) |
| return; |
| |
| if (areElementsOnSameLine(current, candidate)) { |
| if ((type == WebFocusTypeUp && current.rect.y() > candidate.rect.y()) || (type == WebFocusTypeDown && candidate.rect.y() > current.rect.y())) { |
| candidate.distance = 0; |
| return; |
| } |
| } |
| |
| LayoutRect nodeRect = candidate.rect; |
| LayoutRect currentRect = current.rect; |
| deflateIfOverlapped(currentRect, nodeRect); |
| |
| LayoutPoint exitPoint; |
| LayoutPoint entryPoint; |
| entryAndExitPointsForDirection(type, currentRect, nodeRect, exitPoint, entryPoint); |
| |
| LayoutUnit xAxis = (exitPoint.x() - entryPoint.x()).abs(); |
| LayoutUnit yAxis = (exitPoint.y() - entryPoint.y()).abs(); |
| |
| LayoutUnit navigationAxisDistance; |
| LayoutUnit weightedOrthogonalAxisDistance; |
| |
| // Bias and weights are put to the orthogonal axis distance calculation |
| // so aligned candidates would have advantage over partially-aligned ones |
| // and then over not-aligned candidates. The bias is given to not-aligned |
| // candidates with respect to size of the current rect. The weight for |
| // left/right direction is given a higher value to allow navigation on |
| // common horizonally-aligned elements. The hardcoded values are based on |
| // tests and experiments. |
| const int orthogonalWeightForLeftRight = 30; |
| const int orthogonalWeightForUpDown = 2; |
| int orthogonalBias = 0; |
| |
| switch (type) { |
| case WebFocusTypeLeft: |
| case WebFocusTypeRight: |
| navigationAxisDistance = xAxis; |
| if (!rectsIntersectOnOrthogonalAxis(type, currentRect, nodeRect)) |
| orthogonalBias = currentRect.height() / 2; |
| weightedOrthogonalAxisDistance = (yAxis + orthogonalBias) * orthogonalWeightForLeftRight; |
| break; |
| case WebFocusTypeUp: |
| case WebFocusTypeDown: |
| navigationAxisDistance = yAxis; |
| if (!rectsIntersectOnOrthogonalAxis(type, currentRect, nodeRect)) |
| orthogonalBias = currentRect.width() / 2; |
| weightedOrthogonalAxisDistance = (xAxis + orthogonalBias) * orthogonalWeightForUpDown; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| |
| double euclidianDistancePow2 = (xAxis * xAxis + yAxis * yAxis).toDouble(); |
| LayoutRect intersectionRect = intersection(currentRect, nodeRect); |
| double overlap = (intersectionRect.width() * intersectionRect.height()).toDouble(); |
| |
| // Distance calculation is based on http://www.w3.org/TR/WICD/#focus-handling |
| candidate.distance = sqrt(euclidianDistancePow2) + navigationAxisDistance + weightedOrthogonalAxisDistance - sqrt(overlap); |
| } |
| |
| bool canBeScrolledIntoView(WebFocusType type, const FocusCandidate& candidate) |
| { |
| ASSERT(candidate.visibleNode && candidate.isOffscreen); |
| LayoutRect candidateRect = candidate.rect; |
| for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) { |
| LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode); |
| if (!candidateRect.intersects(parentRect)) { |
| if (((type == WebFocusTypeLeft || type == WebFocusTypeRight) && parentNode->layoutObject()->style()->overflowX() == OHIDDEN) |
| || ((type == WebFocusTypeUp || type == WebFocusTypeDown) && parentNode->layoutObject()->style()->overflowY() == OHIDDEN)) |
| return false; |
| } |
| if (parentNode == candidate.enclosingScrollableBox) |
| return canScrollInDirection(parentNode, type); |
| } |
| return true; |
| } |
| |
| // The starting rect is the rect of the focused node, in document coordinates. |
| // Compose a virtual starting rect if there is no focused node or if it is off screen. |
| // The virtual rect is the edge of the container or frame. We select which |
| // edge depending on the direction of the navigation. |
| LayoutRect virtualRectForDirection(WebFocusType type, const LayoutRect& startingRect, LayoutUnit width) |
| { |
| LayoutRect virtualStartingRect = startingRect; |
| switch (type) { |
| case WebFocusTypeLeft: |
| virtualStartingRect.setX(virtualStartingRect.maxX() - width); |
| virtualStartingRect.setWidth(width); |
| break; |
| case WebFocusTypeUp: |
| virtualStartingRect.setY(virtualStartingRect.maxY() - width); |
| virtualStartingRect.setHeight(width); |
| break; |
| case WebFocusTypeRight: |
| virtualStartingRect.setWidth(width); |
| break; |
| case WebFocusTypeDown: |
| virtualStartingRect.setHeight(width); |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| return virtualStartingRect; |
| } |
| |
| LayoutRect virtualRectForAreaElementAndDirection(HTMLAreaElement& area, WebFocusType type) |
| { |
| ASSERT(area.imageElement()); |
| // Area elements tend to overlap more than other focusable elements. We flatten the rect of the area elements |
| // to minimize the effect of overlapping areas. |
| LayoutRect rect = virtualRectForDirection(type, rectToAbsoluteCoordinates(area.document().frame(), area.computeRect(area.imageElement()->layoutObject())), LayoutUnit(1)); |
| return rect; |
| } |
| |
| HTMLFrameOwnerElement* frameOwnerElement(FocusCandidate& candidate) |
| { |
| return candidate.isFrameOwnerElement() ? toHTMLFrameOwnerElement(candidate.visibleNode) : nullptr; |
| }; |
| |
| } // namespace blink |