summaryrefslogtreecommitdiffstats
path: root/WebCore/page/FocusController.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/page/FocusController.cpp')
-rw-r--r--WebCore/page/FocusController.cpp189
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;
}