void FocusController::findFocusCandidateInContainer(Node* container, const LayoutRect& startingRect, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closest) { ASSERT(container); Node* focusedNode = (focusedFrame() && focusedFrame()->document()) ? focusedFrame()->document()->focusedNode() : 0; Node* node = container->firstChild(); FocusCandidate current; current.rect = startingRect; current.focusableNode = focusedNode; current.visibleNode = focusedNode; for (; node; node = (node->isFrameOwnerElement() || canScrollInDirection(node, direction)) ? NodeTraversal::nextSkippingChildren(node, container) : NodeTraversal::next(node, container)) { if (node == focusedNode) continue; if (!node->isElementNode()) continue; if (!node->isKeyboardFocusable(event) && !node->isFrameOwnerElement() && !canScrollInDirection(node, direction)) continue; FocusCandidate candidate = FocusCandidate(node, direction); if (candidate.isNull()) continue; candidate.enclosingScrollableBox = container; updateFocusCandidateIfNeeded(direction, current, candidate, closest); } }
static void updateFocusCandidateIfCloser(Node* focusedNode, Node* candidate, long long distance, FocusCandidate& closestFocusCandidate) { // Bail out if |distance| is bigger than the current closest candidate. if (distance >= closestFocusCandidate.distance) return; // If |focusedNode| and |candidate| are in the same document AND // current |closestFocusCandidadte| is not in an {i}frame that is // preferable to get focused. if (focusedNode->document() == candidate->document() && distance < closestFocusCandidate.parentDistance) { closestFocusCandidate.node = candidate; closestFocusCandidate.distance = distance; closestFocusCandidate.parentDistance = maxDistance(); } else if (focusedNode->document() != candidate->document()) { // If the |focusedNode| is in an inner document and the |candidate| is // in a different document, we only consider to change focus if there is // not another already good focusable candidate in the same document as // |focusedNode|. if (!((isInRootDocument(candidate) && !isInRootDocument(focusedNode)) && focusedNode->document() == closestFocusCandidate.document())) { closestFocusCandidate.node = candidate; closestFocusCandidate.distance = distance; } } }
void FocusController::findFocusableNodeInDirection(Node* outer, Node* focusedNode, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closestFocusCandidate, const FocusCandidate& candidateParent) { ASSERT(outer); ASSERT(candidateParent.isNull() || candidateParent.node->hasTagName(frameTag) || candidateParent.node->hasTagName(iframeTag)); // Walk all the child nodes and update closestFocusCandidate if we find a nearer node. Node* candidate = outer; while (candidate) { // Inner documents case. if (candidate->isFrameOwnerElement()) deepFindFocusableNodeInDirection(candidate, focusedNode, direction, event, closestFocusCandidate); else if (candidate != focusedNode && candidate->isKeyboardFocusable(event)) { FocusCandidate currentFocusCandidate(candidate); // Get distance and alignment from current candidate. distanceDataForNode(direction, focusedNode, currentFocusCandidate); // Bail out if distance is maximum. if (currentFocusCandidate.distance == maxDistance()) { candidate = candidate->traverseNextNode(outer->parent()); continue; } // If candidateParent is not null, it means that we are in a recursive call // from deepFineFocusableNodeInDirection (i.e. processing an element in an iframe), // and holds the distance and alignment data of the iframe element itself. if (!candidateParent.isNull()) { currentFocusCandidate.parentAlignment = candidateParent.alignment; currentFocusCandidate.parentDistance = candidateParent.distance; } updateFocusCandidateIfCloser(focusedNode, currentFocusCandidate, closestFocusCandidate); } candidate = candidate->traverseNextNode(outer->parent()); } }
static void updateFocusCandidateIfCloser(Node* focusedNode, const FocusCandidate& candidate, FocusCandidate& closest) { // First, check the common case: neither candidate nor closest are // inside scrollable content, then no need to care about enclosingScrollableBox // heuristics or parent{Distance,Alignment}, but only distance and alignment. if (!candidate.inScrollableContainer() && !closest.inScrollableContainer()) { updateFocusCandidateInSameContainer(candidate, closest); return; } bool sameContainer = candidate.document() == closest.document() && candidate.enclosingScrollableBox == closest.enclosingScrollableBox; // Second, if candidate and closest are in the same "container" (i.e. {i}frame or any // scrollable block element), we can handle them as common case. if (sameContainer) { updateFocusCandidateInSameContainer(candidate, closest); return; } // Last, we are considering moving to a candidate located in a different enclosing // scrollable box than closest. bool isInInnerDocument = !isInRootDocument(focusedNode); bool sameContainerAsCandidate = isInInnerDocument ? focusedNode->document() == candidate.document() : focusedNode->isDescendantOf(candidate.enclosingScrollableBox); bool sameContainerAsClosest = isInInnerDocument ? focusedNode->document() == closest.document() : focusedNode->isDescendantOf(closest.enclosingScrollableBox); // sameContainerAsCandidate and sameContainerAsClosest are mutually exclusive. ASSERT(!(sameContainerAsCandidate && sameContainerAsClosest)); if (sameContainerAsCandidate) { closest = candidate; return; } if (sameContainerAsClosest) { // Nothing to be done. return; } // NOTE: !sameContainerAsCandidate && !sameContainerAsClosest // If distance is shorter, and we are talking about scrollable container, // lets compare parent distance and alignment before anything. if (candidate.distance < closest.distance) { if (candidate.alignment >= closest.parentAlignment || candidate.parentAlignment == closest.parentAlignment) { closest = candidate; return; } } else if (candidate.parentDistance < closest.distance) { if (candidate.parentAlignment >= closest.alignment) { closest = candidate; return; } } }
bool areElementsOnSameLine(const FocusCandidate& firstCandidate, const FocusCandidate& secondCandidate) { if (firstCandidate.isNull() || secondCandidate.isNull()) return false; if (!firstCandidate.visibleNode->renderer() || !secondCandidate.visibleNode->renderer()) return false; if (!firstCandidate.rect.intersects(secondCandidate.rect)) return false; if (firstCandidate.focusableNode->hasTagName(HTMLNames::areaTag) || secondCandidate.focusableNode->hasTagName(HTMLNames::areaTag)) return false; if (!firstCandidate.visibleNode->renderer()->isRenderInline() || !secondCandidate.visibleNode->renderer()->isRenderInline()) return false; if (firstCandidate.visibleNode->renderer()->containingBlock() != secondCandidate.visibleNode->renderer()->containingBlock()) return false; return true; }
// FIXME: Make this method more modular, and simpler to understand and maintain. static void updateFocusCandidateIfCloser(Node* focusedNode, const FocusCandidate& candidate, FocusCandidate& closest) { bool sameDocument = candidate.document() == closest.document(); if (sameDocument) { if (closest.alignment > candidate.alignment || (closest.parentAlignment && candidate.alignment > closest.parentAlignment)) return; } else if (closest.alignment > candidate.alignment && (closest.parentAlignment && candidate.alignment > closest.parentAlignment)) return; if (candidate.alignment != None || (closest.parentAlignment >= candidate.alignment && closest.document() == candidate.document())) { // If we are now in an higher precedent case, lets reset the current closest's // distance so we force it to be bigger than any result we will get from // spatialDistance(). if (closest.alignment < candidate.alignment && closest.parentAlignment < candidate.alignment) closest.distance = maxDistance(); } // Bail out if candidate's distance is larger than that of the closest candidate. if (candidate.distance >= closest.distance) return; if (closest.isNull()) { closest = candidate; return; } // If the focused node and the candadate are in the same document and current // closest candidate is not in an {i}frame that is preferable to get focused ... if (focusedNode->document() == candidate.document() && candidate.distance < closest.parentDistance) closest = candidate; else if (focusedNode->document() != candidate.document()) { // If the focusedNode is in an inner document and candidate is in a // different document, we only consider to change focus if there is not // another already good focusable candidate in the same document as focusedNode. if (!((isInRootDocument(candidate.node) && !isInRootDocument(focusedNode)) && focusedNode->document() == closest.document())) closest = candidate; } }
static void updateFocusCandidateIfNeeded(FocusDirection direction, const FocusCandidate& current, FocusCandidate& candidate, FocusCandidate& closest) { ASSERT(candidate.visibleNode->isElementNode()); ASSERT(candidate.visibleNode->renderer()); // Ignore iframes that don't have a src attribute if (frameOwnerElement(candidate) && (!frameOwnerElement(candidate)->contentFrame() || candidate.rect.isEmpty())) return; // Ignore off screen child nodes of containers that do not scroll (overflow:hidden) if (candidate.isOffscreen && !canBeScrolledIntoView(direction, candidate)) return; distanceDataForNode(direction, current, candidate); if (candidate.distance == maxDistance()) return; if (candidate.isOffscreenAfterScrolling && candidate.alignment < Full) return; if (closest.isNull()) { closest = candidate; return; } LayoutRect intersectionRect = intersection(candidate.rect, closest.rect); if (!intersectionRect.isEmpty() && !areElementsOnSameLine(closest, candidate)) { // If 2 nodes are intersecting, do hit test to find which node in on top. LayoutUnit x = intersectionRect.x() + intersectionRect.width() / 2; LayoutUnit y = intersectionRect.y() + intersectionRect.height() / 2; HitTestResult result = candidate.visibleNode->document()->page()->mainFrame()->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), false, true); if (candidate.visibleNode->contains(result.innerNode())) { closest = candidate; return; } if (closest.visibleNode->contains(result.innerNode())) return; } if (candidate.alignment == closest.alignment) { if (candidate.distance < closest.distance) closest = candidate; return; } if (candidate.alignment > closest.alignment) closest = candidate; }
static void updateFocusCandidateInSameContainer(const FocusCandidate& candidate, FocusCandidate& closest) { if (closest.isNull()) { closest = candidate; return; } if (candidate.alignment == closest.alignment) { if (candidate.distance < closest.distance) closest = candidate; return; } if (candidate.alignment > closest.alignment) closest = candidate; }
void FocusController::findFocusableNodeInDirection(Node* outer, Node* focusedNode, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closest, const FocusCandidate& candidateParent) #endif { #if PLATFORM(WKC) CRASH_IF_STACK_OVERFLOW(WKC_STACK_MARGIN_DEFAULT); #endif ASSERT(outer); ASSERT(candidateParent.isNull() || candidateParent.node->isFrameOwnerElement() || isScrollableContainerNode(candidateParent.node)); // Walk all the child nodes and update closest if we find a nearer node. Node* node = outer; while (node) { // Inner documents case. if (node->isFrameOwnerElement()) { deepFindFocusableNodeInDirection(node, focusedNode, direction, event, closest, specificRect); // Scrollable block elements (e.g. <div>, etc) case. } else if (isScrollableContainerNode(node) && !node->renderer()->isTextArea()) { deepFindFocusableNodeInDirection(node, focusedNode, direction, event, closest, specificRect); node = node->traverseNextSibling(); continue; #if PLATFORM(WKC) } else if (node != focusedNode && node->isFocusable() && isNodeInSpecificRect(node, specificRect)) { #else } else if (node != focusedNode && node->isKeyboardFocusable(event)) { #endif FocusCandidate candidate(node); // There are two ways to identify we are in a recursive call from deepFindFocusableNodeInDirection // (i.e. processing an element in an iframe, frame or a scrollable block element): // 1) If candidateParent is not null, and it holds the distance and alignment data of the // parent container element itself; // 2) Parent of outer is <frame> or <iframe>; // 3) Parent is any other scrollable block element. if (!candidateParent.isNull()) { candidate.parentAlignment = candidateParent.alignment; candidate.parentDistance = candidateParent.distance; candidate.enclosingScrollableBox = candidateParent.node; } else if (!isInRootDocument(outer)) { #if PLATFORM(WKC) if (outer->parent() && outer->parent()->isDocumentNode()) { Document* document = static_cast<Document*>(outer->parent()); candidate.enclosingScrollableBox = static_cast<Node*>(document->ownerElement()); } #else if (Document* document = static_cast<Document*>(outer->parent())) candidate.enclosingScrollableBox = static_cast<Node*>(document->ownerElement()); #endif } else if (isScrollableContainerNode(outer->parent())) candidate.enclosingScrollableBox = outer->parent(); // Get distance and alignment from current candidate. distanceDataForNode(direction, focusedNode, candidate); // Bail out if distance is maximum. if (candidate.distance == maxDistance()) { node = node->traverseNextNode(outer->parent()); continue; } updateFocusCandidateIfCloser(focusedNode, candidate, closest); } node = node->traverseNextNode(outer->parent()); } }
bool FocusController::advanceFocusDirectionallyInContainer(Node* container, const LayoutRect& startingRect, FocusDirection direction, KeyboardEvent* event) { if (!container || !container->document()) return false; LayoutRect newStartingRect = startingRect; if (startingRect.isEmpty()) newStartingRect = virtualRectForDirection(direction, nodeRectInAbsoluteCoordinates(container)); // Find the closest node within current container in the direction of the navigation. FocusCandidate focusCandidate; findFocusCandidateInContainer(container, newStartingRect, direction, event, focusCandidate); if (focusCandidate.isNull()) { // Nothing to focus, scroll if possible. // NOTE: If no scrolling is performed (i.e. scrollInDirection returns false), the // spatial navigation algorithm will skip this container. return scrollInDirection(container, direction); } if (HTMLFrameOwnerElement* frameElement = frameOwnerElement(focusCandidate)) { // If we have an iframe without the src attribute, it will not have a contentFrame(). // We ASSERT here to make sure that // updateFocusCandidateIfNeeded() will never consider such an iframe as a candidate. ASSERT(frameElement->contentFrame()); if (focusCandidate.isOffscreenAfterScrolling) { scrollInDirection(focusCandidate.visibleNode->document(), direction); return true; } // Navigate into a new frame. LayoutRect rect; Node* focusedNode = focusedOrMainFrame()->document()->focusedNode(); if (focusedNode && !hasOffscreenRect(focusedNode)) rect = nodeRectInAbsoluteCoordinates(focusedNode, true /* ignore border */); frameElement->contentFrame()->document()->updateLayoutIgnorePendingStylesheets(); if (!advanceFocusDirectionallyInContainer(frameElement->contentFrame()->document(), rect, direction, event)) { // The new frame had nothing interesting, need to find another candidate. return advanceFocusDirectionallyInContainer(container, nodeRectInAbsoluteCoordinates(focusCandidate.visibleNode, true), direction, event); } return true; } if (canScrollInDirection(focusCandidate.visibleNode, direction)) { if (focusCandidate.isOffscreenAfterScrolling) { scrollInDirection(focusCandidate.visibleNode, direction); return true; } // Navigate into a new scrollable container. LayoutRect startingRect; Node* focusedNode = focusedOrMainFrame()->document()->focusedNode(); if (focusedNode && !hasOffscreenRect(focusedNode)) startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true); return advanceFocusDirectionallyInContainer(focusCandidate.visibleNode, startingRect, direction, event); } if (focusCandidate.isOffscreenAfterScrolling) { Node* container = focusCandidate.enclosingScrollableBox; scrollInDirection(container, direction); return true; } // We found a new focus node, navigate to it. Element* element = toElement(focusCandidate.focusableNode); ASSERT(element); element->focus(false); return true; }