diff options
Diffstat (limited to 'WebCore/page/FocusController.cpp')
-rw-r--r-- | WebCore/page/FocusController.cpp | 189 |
1 files changed, 122 insertions, 67 deletions
diff --git a/WebCore/page/FocusController.cpp b/WebCore/page/FocusController.cpp index 804341d..e9f180b 100644 --- a/WebCore/page/FocusController.cpp +++ b/WebCore/page/FocusController.cpp @@ -319,7 +319,7 @@ bool FocusController::advanceFocusDirectionally(FocusDirection direction, Keyboa // if |node| element is not in the viewport. if (hasOffscreenRect(node)) { Frame* frame = node->document()->view()->frame(); - scrollInDirection(frame, direction); + scrollInDirection(frame, direction, focusCandidate); return true; } @@ -341,103 +341,152 @@ bool FocusController::advanceFocusDirectionally(FocusDirection direction, Keyboa return true; } -// FIXME: Make this method more modular, and simpler to understand and maintain. -static void updateFocusCandidateIfCloser(Node* focusedNode, const FocusCandidate& candidate, FocusCandidate& closest) +static void updateFocusCandidateInSameContainer(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)) + if (closest.isNull()) { + closest = candidate; return; + } - if (candidate.alignment != None - || (closest.parentAlignment >= candidate.alignment - && closest.document() == candidate.document())) { + if (candidate.alignment == closest.alignment) { + if (candidate.distance < closest.distance) + closest = candidate; + return; + } + + if (candidate.alignment > closest.alignment) + closest = candidate; +} - // 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(); +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; } - // Bail out if candidate's distance is larger than that of the closest candidate. - if (candidate.distance >= closest.distance) + 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; + } - if (closest.isNull()) { + // 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 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())) + 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; + } } } void FocusController::findFocusableNodeInDirection(Node* outer, Node* focusedNode, FocusDirection direction, KeyboardEvent* event, - FocusCandidate& closestFocusCandidate, - const FocusCandidate& candidateParent) + FocusCandidate& closest, const FocusCandidate& candidateParent) { ASSERT(outer); ASSERT(candidateParent.isNull() || candidateParent.node->hasTagName(frameTag) - || candidateParent.node->hasTagName(iframeTag)); + || candidateParent.node->hasTagName(iframeTag) + || isScrollableContainerNode(candidateParent.node)); + + // Walk all the child nodes and update closest if we find a nearer node. + Node* node = outer; + while (node) { - // Walk all the child nodes and update closestFocusCandidate if we find a nearer node. - Node* candidate = outer; - while (candidate) { // Inner documents case. + if (node->isFrameOwnerElement()) { + deepFindFocusableNodeInDirection(node, focusedNode, direction, event, closest); + + // Scrollable block elements (e.g. <div>, etc) case. + } else if (isScrollableContainerNode(node)) { + deepFindFocusableNodeInDirection(node, focusedNode, direction, event, closest); + node = node->traverseNextSibling(); + continue; + + } else if (node != focusedNode && node->isKeyboardFocusable(event)) { + 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 (Document* document = static_cast<Document*>(outer->parent())) + candidate.enclosingScrollableBox = static_cast<Node*>(document->ownerElement()); - if (candidate->isFrameOwnerElement()) - deepFindFocusableNodeInDirection(candidate, focusedNode, direction, event, closestFocusCandidate); - else if (candidate != focusedNode && candidate->isKeyboardFocusable(event)) { - FocusCandidate currentFocusCandidate(candidate); + } else if (isScrollableContainerNode(outer->parent())) + candidate.enclosingScrollableBox = outer->parent(); // Get distance and alignment from current candidate. - distanceDataForNode(direction, focusedNode, currentFocusCandidate); + distanceDataForNode(direction, focusedNode, candidate); // Bail out if distance is maximum. - if (currentFocusCandidate.distance == maxDistance()) { - candidate = candidate->traverseNextNode(outer->parent()); + if (candidate.distance == maxDistance()) { + node = node->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); + updateFocusCandidateIfCloser(focusedNode, candidate, closest); } - candidate = candidate->traverseNextNode(outer->parent()); + node = node->traverseNextNode(outer->parent()); } } void FocusController::deepFindFocusableNodeInDirection(Node* container, Node* focusedNode, FocusDirection direction, KeyboardEvent* event, - FocusCandidate& closestFocusCandidate) + FocusCandidate& closest) { - ASSERT(container->hasTagName(frameTag) || container->hasTagName(iframeTag)); + ASSERT(container->hasTagName(frameTag) + || container->hasTagName(iframeTag) + || isScrollableContainerNode(container)); // Track if focusedNode is a descendant of the current container node being processed. bool descendantOfContainer = false; @@ -457,10 +506,15 @@ void FocusController::deepFindFocusableNodeInDirection(Node* container, Node* fo descendantOfContainer = innerDocument == focusedNode->document(); firstChild = innerDocument->firstChild(); + // Scrollable block elements (e.g. <div>, etc) + } else if (isScrollableContainerNode(container)) { + + firstChild = container->firstChild(); + descendantOfContainer = focusedNode->isDescendantOf(container); } if (descendantOfContainer) { - findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closestFocusCandidate); + findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closest); return; } @@ -474,8 +528,8 @@ void FocusController::deepFindFocusableNodeInDirection(Node* container, Node* fo return; // FIXME: Consider alignment? - if (candidateParent.distance < closestFocusCandidate.distance) - findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closestFocusCandidate, candidateParent); + if (candidateParent.distance < closest.distance) + findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closest, candidateParent); } static bool relinquishesEditingFocus(Node *node) @@ -540,20 +594,23 @@ bool FocusController::setFocusedNode(Node* node, PassRefPtr<Frame> newFocusedFra // FIXME: Might want to disable this check for caretBrowsing if (oldFocusedNode && oldFocusedNode->rootEditableElement() == oldFocusedNode && !relinquishesEditingFocus(oldFocusedNode)) return false; - + + // Set input method state before changing the focused node, so that the + // input method can still have a chance to finish the ongoing composition + // session. + m_page->editorClient()->setInputMethodState(node ? node->shouldUseInputMethod() : false); + clearSelectionIfNeeded(oldFocusedFrame.get(), newFocusedFrame.get(), node); - + if (!node) { if (oldDocument) oldDocument->setFocusedNode(0); - m_page->editorClient()->setInputMethodState(false); return true; } RefPtr<Document> newDocument = node->document(); if (newDocument && newDocument->focusedNode() == node) { - m_page->editorClient()->setInputMethodState(node->shouldUseInputMethod()); return true; } @@ -565,8 +622,6 @@ bool FocusController::setFocusedNode(Node* node, PassRefPtr<Frame> newFocusedFra if (newDocument) newDocument->setFocusedNode(node); - m_page->editorClient()->setInputMethodState(node->shouldUseInputMethod()); - return true; } |