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.cpp428
1 files changed, 179 insertions, 249 deletions
diff --git a/WebCore/page/FocusController.cpp b/WebCore/page/FocusController.cpp
index 2866019..5418c89 100644
--- a/WebCore/page/FocusController.cpp
+++ b/WebCore/page/FocusController.cpp
@@ -40,11 +40,13 @@
#include "Frame.h"
#include "FrameTree.h"
#include "FrameView.h"
+#include "HitTestResult.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLNames.h"
#include "KeyboardEvent.h"
#include "Page.h"
#include "Range.h"
+#include "RenderLayer.h"
#include "RenderObject.h"
#include "RenderWidget.h"
#include "SelectionController.h"
@@ -57,6 +59,7 @@ namespace WebCore {
using namespace HTMLNames;
using namespace std;
+static void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest);
static inline void dispatchEventsOnWindowAndFocusedNode(Document* document, bool focused)
{
// If we have a focused node we should dispatch blur on it before we blur the window.
@@ -289,255 +292,6 @@ bool FocusController::advanceFocusInDocumentOrder(FocusDirection direction, Keyb
return true;
}
-bool FocusController::advanceFocusDirectionally(FocusDirection direction, KeyboardEvent* event)
-{
- Frame* frame = focusedOrMainFrame();
- ASSERT(frame);
- Document* focusedDocument = frame->document();
- if (!focusedDocument)
- return false;
-
- Node* focusedNode = focusedDocument->focusedNode();
- if (!focusedNode) {
- // Just move to the first focusable node.
- FocusDirection tabDirection = (direction == FocusDirectionUp || direction == FocusDirectionLeft) ?
- FocusDirectionBackward : FocusDirectionForward;
- // 'initialFocus' is set to true so the chrome is not focused.
- return advanceFocusInDocumentOrder(tabDirection, event, true);
- }
-
- // Move up in the chain of nested frames.
- frame = frame->tree()->top();
-
- FocusCandidate focusCandidate;
- findFocusableNodeInDirection(frame->document()->firstChild(), focusedNode, direction, event, focusCandidate);
-
- Node* node = focusCandidate.node;
- if (!node || !node->isElementNode()) {
- // FIXME: May need a way to focus a document here.
- Frame* frame = focusedOrMainFrame();
- scrollInDirection(frame, direction);
- return false;
- }
-
- // In order to avoid crazy jump between links that are either far away from each other,
- // or just not currently visible, lets do a scroll in the given direction and bail out
- // if |node| element is not in the viewport.
- if (hasOffscreenRect(node)) {
- Frame* frame = node->document()->view()->frame();
- scrollInDirection(frame, direction, focusCandidate);
- return true;
- }
-
- Document* newDocument = node->document();
-
- if (newDocument != focusedDocument) {
- // Focus is going away from the originally focused document, so clear the focused node.
- focusedDocument->setFocusedNode(0);
- }
-
- if (newDocument)
- setFocusedFrame(newDocument->frame());
-
- Element* element = static_cast<Element*>(node);
- ASSERT(element);
-
- scrollIntoView(element);
- element->focus(false);
- return true;
-}
-
-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;
-}
-
-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;
- }
- }
-}
-
-void FocusController::findFocusableNodeInDirection(Node* outer, Node* focusedNode,
- FocusDirection direction, KeyboardEvent* event,
- FocusCandidate& closest, const FocusCandidate& candidateParent)
-{
- ASSERT(outer);
- ASSERT(candidateParent.isNull()
- || candidateParent.node->hasTagName(frameTag)
- || 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) {
-
- // 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->parentNode()))
- candidate.enclosingScrollableBox = static_cast<Node*>(document->ownerElement());
-
- } else if (isScrollableContainerNode(outer->parentNode()))
- candidate.enclosingScrollableBox = outer->parentNode();
-
- // 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->parentNode());
- continue;
- }
-
- updateFocusCandidateIfCloser(focusedNode, candidate, closest);
- }
-
- node = node->traverseNextNode(outer->parentNode());
- }
-}
-
-void FocusController::deepFindFocusableNodeInDirection(Node* container, Node* focusedNode,
- FocusDirection direction, KeyboardEvent* event,
- FocusCandidate& closest)
-{
- 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;
- Node* firstChild = 0;
-
- // Iframe or Frame.
- if (container->hasTagName(frameTag) || container->hasTagName(iframeTag)) {
-
- HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(container);
- if (!owner->contentFrame())
- return;
-
- Document* innerDocument = owner->contentFrame()->document();
- if (!innerDocument)
- return;
-
- descendantOfContainer = isNodeDeepDescendantOfDocument(focusedNode, innerDocument);
- 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, closest);
- return;
- }
-
- // Check if the current container element itself is a good candidate
- // to move focus to. If it is, then we traverse its inner nodes.
- FocusCandidate candidateParent = FocusCandidate(container);
- distanceDataForNode(direction, focusedNode, candidateParent);
-
- // Bail out if distance is maximum.
- if (candidateParent.distance == maxDistance())
- return;
-
- // FIXME: Consider alignment?
- if (candidateParent.distance < closest.distance)
- findFocusableNodeInDirection(firstChild, focusedNode, direction, event, closest, candidateParent);
-}
-
static bool relinquishesEditingFocus(Node *node)
{
ASSERT(node);
@@ -658,4 +412,180 @@ void FocusController::setActive(bool active)
dispatchEventsOnWindowAndFocusedNode(m_focusedFrame->document(), active);
}
+void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest)
+{
+ if (!candidate.node->isElementNode() || !candidate.node->renderer())
+ return;
+
+ // Ignore iframes that don't have a src attribute
+ if (candidate.node->isFrameOwnerElement() && !static_cast<HTMLFrameOwnerElement*>(candidate.node)->contentFrame())
+ return;
+
+ // Ignore off screen child nodes of containers that do not scroll (overflow:hidden)
+ if (candidate.isOffscreen && !canBeScrolledIntoView(direction, candidate))
+ return;
+
+ FocusCandidate current;
+ current.rect = startingRect;
+ distanceDataForNode(direction, current, candidate);
+ if (candidate.distance == maxDistance())
+ return;
+
+ if (candidate.isOffscreenAfterScrolling && candidate.alignment < Full)
+ return;
+
+ if (closest.isNull()) {
+ closest = candidate;
+ return;
+ }
+
+ IntRect intersectionRect = intersection(candidate.rect, closest.rect);
+ if (!intersectionRect.isEmpty()) {
+ // If 2 nodes are intersecting, do hit test to find which node in on top.
+ int x = intersectionRect.x() + intersectionRect.width() / 2;
+ int y = intersectionRect.y() + intersectionRect.height() / 2;
+ HitTestResult result = candidate.node->document()->page()->mainFrame()->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), false, true);
+ if (candidate.node->contains(result.innerNode())) {
+ closest = candidate;
+ return;
+ }
+ if (closest.node->contains(result.innerNode()))
+ return;
+ }
+
+ if (candidate.alignment == closest.alignment) {
+ if (candidate.distance < closest.distance)
+ closest = candidate;
+ return;
+ }
+
+ if (candidate.alignment > closest.alignment)
+ closest = candidate;
+}
+
+void FocusController::findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closest)
+{
+ ASSERT(container);
+ Node* focusedNode = (focusedFrame() && focusedFrame()->document()) ? focusedFrame()->document()->focusedNode() : 0;
+
+ Node* node = container->firstChild();
+ for (; node; node = (node->isFrameOwnerElement() || canScrollInDirection(direction, node)) ? node->traverseNextSibling(container) : node->traverseNextNode(container)) {
+ if (node == focusedNode)
+ continue;
+
+ if (!node->renderer())
+ continue;
+
+ if (!node->isKeyboardFocusable(event) && !node->isFrameOwnerElement() && !canScrollInDirection(direction, node))
+ continue;
+
+ FocusCandidate candidate(node, direction);
+ candidate.enclosingScrollableBox = container;
+ updateFocusCandidateIfNeeded(direction, startingRect, candidate, closest);
+ }
+}
+
+bool FocusController::advanceFocusDirectionallyInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event)
+{
+ if (!container || !container->document())
+ return false;
+
+ IntRect 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()) {
+ if (canScrollInDirection(direction, container)) {
+ // Nothing to focus, scroll if possible.
+ scrollInDirection(container, direction);
+ return true;
+ }
+ // Return false will cause a re-try, skipping this container.
+ return false;
+ }
+ if (focusCandidate.node->isFrameOwnerElement()) {
+ HTMLFrameOwnerElement* frameElement = static_cast<HTMLFrameOwnerElement*>(focusCandidate.node);
+ // 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.node->document(), direction);
+ return true;
+ }
+ // Navigate into a new frame.
+ IntRect 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.node, true), direction, event);
+ }
+ return true;
+ }
+ if (canScrollInDirection(direction, focusCandidate.node)) {
+ if (focusCandidate.isOffscreenAfterScrolling) {
+ scrollInDirection(focusCandidate.node, direction);
+ return true;
+ }
+ // Navigate into a new scrollable container.
+ IntRect startingRect;
+ Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
+ if (focusedNode && !hasOffscreenRect(focusedNode))
+ startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true);
+ return advanceFocusDirectionallyInContainer(focusCandidate.node, 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.node);
+ ASSERT(element);
+
+ element->focus(false);
+ return true;
+}
+
+bool FocusController::advanceFocusDirectionally(FocusDirection direction, KeyboardEvent* event)
+{
+ Frame* curFrame = focusedOrMainFrame();
+ ASSERT(curFrame);
+
+ Document* focusedDocument = curFrame->document();
+ if (!focusedDocument)
+ return false;
+
+ Node* focusedNode = focusedDocument->focusedNode();
+ Node* container = focusedDocument;
+
+ // Figure out the starting rect.
+ IntRect startingRect;
+ if (focusedNode && !hasOffscreenRect(focusedNode)) {
+ container = scrollableEnclosingBoxOrParentFrameForNodeInDirection(direction, focusedNode);
+ startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true /* ignore border */);
+ }
+
+ bool consumed = false;
+ do {
+ if (container->isDocumentNode())
+ static_cast<Document*>(container)->updateLayoutIgnorePendingStylesheets();
+ consumed = advanceFocusDirectionallyInContainer(container, startingRect, direction, event);
+ startingRect = nodeRectInAbsoluteCoordinates(container, true /* ignore border */);
+ container = scrollableEnclosingBoxOrParentFrameForNodeInDirection(direction, container);
+ } while (!consumed && container);
+
+ return consumed;
+}
+
} // namespace WebCore