Changeset 61134 in webkit


Ignore:
Timestamp:
Jun 14, 2010 12:10:59 PM (14 years ago)
Author:
tonikitoo@webkit.org
Message:

Spatial Navigation: make it work with focusable elements in overflow content
https://bugs.webkit.org/show_bug.cgi?id=36463

Reviewed by Simon Fraser and Kenneth Christiansen.
Patch by Antonio Gomes <tonikitoo@webkit.org>

WebCore:

This patch addresses the problem with Spatial Navigation. It currently does not
properly traverse scrollable contents, including scrollable div's. For this to work,
a new class member called scrollableEnclosingBox was introduced to FocusCandidate class which
keeps track of the current scrollable box Node wrapping a FocusCandidate.

To make use of enclosingScrollableBox of FocusCandidate, the DOM traversal routine
(FocusController::findNextFocusableInDirection) was changed as follows: when it
encounters a scrollable Node, each focusable node which is 'inner' keeps track of
the container reference. By the time a sibling of the scrollable Node is encountered,
there is no need to track this reference any more and the traversal algorithm continues
normally.

The common case is obviously that there is no scrollable container wrapping it.

updateFocusCandiditeIfCloser logic was also adapted to fit the need of the
newly introduced enclosingScrollableBox class member, getting simpler and more
easily maintainable.

Tests: fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html

fast/events/spatial-navigation/snav-clipped-overflow-content.html

  • page/FocusController.cpp:

(WebCore::updateFocusCandidateInSameContainer):
(WebCore::updateFocusCandidateIfCloser):
(WebCore::FocusController::findFocusableNodeInDirection):
(WebCore::FocusController::deepFindFocusableNodeInDirection):

  • page/SpatialNavigation.cpp:

(WebCore::isScrollableContainerNode):

  • page/SpatialNavigation.h:

(WebCore::FocusCandidate::FocusCandidate):
(WebCore::FocusCandidate::inScrollableContainer):

LayoutTests:

  • fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content-expected.txt: Added.
  • fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html: Added.
  • fast/events/spatial-navigation/snav-clipped-overflow-content-expected.txt: Added.
  • fast/events/spatial-navigation/snav-clipped-overflow-content.html: Added.
Location:
trunk
Files:
4 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r61132 r61134  
     12010-06-14  Antonio Gomes  <tonikitoo@webkit.org>
     2
     3        Reviewed by Simon Fraser.
     4
     5        Spatial Navigation: make it work with focusable elements in overflow content
     6        https://bugs.webkit.org/show_bug.cgi?id=36463
     7
     8        * fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content-expected.txt: Added.
     9        * fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html: Added.
     10        * fast/events/spatial-navigation/snav-clipped-overflow-content-expected.txt: Added.
     11        * fast/events/spatial-navigation/snav-clipped-overflow-content.html: Added.
     12
    1132010-06-14  Chris Fleizach  <cfleizach@apple.com>
    214
  • trunk/WebCore/ChangeLog

    r61133 r61134  
     12010-06-14  Antonio Gomes  <tonikitoo@webkit.org>
     2
     3        Reviewed by Simon Fraser and Kenneth Christiansen.
     4
     5        Spatial Navigation: make it work with focusable elements in overflow content
     6        https://bugs.webkit.org/show_bug.cgi?id=36463
     7
     8        This patch addresses the problem with Spatial Navigation. It currently does not
     9        properly traverse scrollable contents, including scrollable div's. For this to work,
     10        a new class member called scrollableEnclosingBox was introduced to FocusCandidate class which
     11        keeps track of the current scrollable box Node wrapping a FocusCandidate.
     12
     13        To make use of enclosingScrollableBox of FocusCandidate, the DOM traversal routine
     14        (FocusController::findNextFocusableInDirection) was changed as follows: when it
     15        encounters a scrollable Node, each focusable node which is 'inner' keeps track of
     16        the container reference. By the time a sibling of the scrollable Node is encountered,
     17        there is no need to track this reference any more and the traversal algorithm continues
     18        normally.
     19
     20        The common case is obviously that there is no scrollable container wrapping it.
     21
     22        updateFocusCandiditeIfCloser logic was also adapted to fit the need of the
     23        newly introduced enclosingScrollableBox class member, getting simpler and more
     24        easily maintainable.
     25
     26        Tests: fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html
     27               fast/events/spatial-navigation/snav-clipped-overflow-content.html
     28
     29        * page/FocusController.cpp:
     30        (WebCore::updateFocusCandidateInSameContainer):
     31        (WebCore::updateFocusCandidateIfCloser):
     32        (WebCore::FocusController::findFocusableNodeInDirection):
     33        (WebCore::FocusController::deepFindFocusableNodeInDirection):
     34        * page/SpatialNavigation.cpp:
     35        (WebCore::isScrollableContainerNode):
     36        * page/SpatialNavigation.h:
     37        (WebCore::FocusCandidate::FocusCandidate):
     38        (WebCore::FocusCandidate::isInScrollableContainer):
     39
    1402010-06-14  Jian Li  <jianli@chromium.org>
    241
  • trunk/WebCore/page/FocusController.cpp

    r60873 r61134  
    342342}
    343343
    344 // FIXME: Make this method more modular, and simpler to understand and maintain.
    345 static void updateFocusCandidateIfCloser(Node* focusedNode, const FocusCandidate& candidate, FocusCandidate& closest)
    346 {
    347     bool sameDocument = candidate.document() == closest.document();
    348     if (sameDocument) {
    349         if (closest.alignment > candidate.alignment
    350          || (closest.parentAlignment && candidate.alignment > closest.parentAlignment))
    351             return;
    352     } else if (closest.alignment > candidate.alignment
    353             && (closest.parentAlignment && candidate.alignment > closest.parentAlignment))
    354         return;
    355 
    356     if (candidate.alignment != None
    357      || (closest.parentAlignment >= candidate.alignment
    358      && closest.document() == candidate.document())) {
    359 
    360         // If we are now in an higher precedent case, lets reset the current closest's
    361         // distance so we force it to be bigger than any result we will get from
    362         // spatialDistance().
    363         if (closest.alignment < candidate.alignment
    364          && closest.parentAlignment < candidate.alignment)
    365             closest.distance = maxDistance();
    366     }
    367 
    368     // Bail out if candidate's distance is larger than that of the closest candidate.
    369     if (candidate.distance >= closest.distance)
    370         return;
    371 
     344static void updateFocusCandidateInSameContainer(const FocusCandidate& candidate, FocusCandidate& closest)
     345{
    372346    if (closest.isNull()) {
    373347        closest = candidate;
     
    375349    }
    376350
    377     // If the focused node and the candadate are in the same document and current
    378     // closest candidate is not in an {i}frame that is preferable to get focused ...
    379     if (focusedNode->document() == candidate.document()
    380         && candidate.distance < closest.parentDistance)
     351    if (candidate.alignment == closest.alignment) {
     352        if (candidate.distance < closest.distance)
     353            closest = candidate;
     354        return;
     355    }
     356
     357    if (candidate.alignment > closest.alignment)
    381358        closest = candidate;
    382     else if (focusedNode->document() != candidate.document()) {
    383         // If the focusedNode is in an inner document and candidate is in a
    384         // different document, we only consider to change focus if there is not
    385         // another already good focusable candidate in the same document as focusedNode.
    386         if (!((isInRootDocument(candidate.node) && !isInRootDocument(focusedNode))
    387             && focusedNode->document() == closest.document()))
     359}
     360
     361static void updateFocusCandidateIfCloser(Node* focusedNode, const FocusCandidate& candidate, FocusCandidate& closest)
     362{
     363    // First, check the common case: neither candidate nor closest are
     364    // inside scrollable content, then no need to care about enclosingScrollableBox
     365    // heuristics or parent{Distance,Alignment}, but only distance and alignment.
     366    if (!candidate.inScrollableContainer() && !closest.inScrollableContainer()) {
     367        updateFocusCandidateInSameContainer(candidate, closest);
     368        return;
     369    }
     370
     371    bool sameContainer = candidate.document() == closest.document() && candidate.enclosingScrollableBox == closest.enclosingScrollableBox;
     372
     373    // Second, if candidate and closest are in the same "container" (i.e. {i}frame or any
     374    // scrollable block element), we can handle them as common case.
     375    if (sameContainer) {
     376        updateFocusCandidateInSameContainer(candidate, closest);
     377        return;
     378    }
     379
     380    // Last, we are considering moving to a candidate located in a different enclosing
     381    // scrollable box than closest.
     382    bool isInInnerDocument = !isInRootDocument(focusedNode);
     383
     384    bool sameContainerAsCandidate = isInInnerDocument ? focusedNode->document() == candidate.document() :
     385        focusedNode->isDescendantOf(candidate.enclosingScrollableBox);
     386
     387    bool sameContainerAsClosest = isInInnerDocument ? focusedNode->document() == closest.document() :
     388        focusedNode->isDescendantOf(closest.enclosingScrollableBox);
     389
     390    // sameContainerAsCandidate and sameContainerAsClosest are mutually exclusive.
     391    ASSERT(!(sameContainerAsCandidate && sameContainerAsClosest));
     392
     393    if (sameContainerAsCandidate) {
     394        closest = candidate;
     395        return;
     396    }
     397
     398    if (sameContainerAsClosest) {
     399        // Nothing to be done.
     400        return;
     401    }
     402
     403    // NOTE: !sameContainerAsCandidate && !sameContainerAsClosest
     404    // If distance is shorter, and we are talking about scrollable container,
     405    // lets compare parent distance and alignment before anything.
     406    if (candidate.distance < closest.distance) {
     407        if (candidate.alignment >= closest.parentAlignment
     408         || candidate.parentAlignment == closest.parentAlignment) {
    388409            closest = candidate;
     410            return;
     411        }
     412
     413    } else if (candidate.parentDistance < closest.distance) {
     414        if (candidate.parentAlignment >= closest.alignment) {
     415            closest = candidate;
     416            return;
     417        }
    389418    }
    390419}
     
    398427    ASSERT(candidateParent.isNull()
    399428        || candidateParent.node->hasTagName(frameTag)
    400         || candidateParent.node->hasTagName(iframeTag));
     429        || candidateParent.node->hasTagName(iframeTag)
     430        || isScrollableContainerNode(candidateParent.node));
    401431
    402432    // Walk all the child nodes and update closestFocusCandidate if we find a nearer node.
    403433    Node* candidate = outer;
    404434    while (candidate) {
     435
    405436        // Inner documents case.
    406 
    407         if (candidate->isFrameOwnerElement())
     437        if (candidate->isFrameOwnerElement()) {
    408438            deepFindFocusableNodeInDirection(candidate, focusedNode, direction, event, closestFocusCandidate);
    409         else if (candidate != focusedNode && candidate->isKeyboardFocusable(event)) {
     439
     440        // Scrollable block elements (e.g. <div>, etc) case.
     441        } else if (isScrollableContainerNode(candidate)) {
     442            deepFindFocusableNodeInDirection(candidate, focusedNode, direction, event, closestFocusCandidate);
     443            candidate = candidate->traverseNextSibling();
     444            continue;
     445
     446        } else if (candidate != focusedNode && candidate->isKeyboardFocusable(event)) {
    410447            FocusCandidate currentFocusCandidate(candidate);
     448
     449            // There are two ways to identify we are in a recursive call from deepFindFocusableNodeInDirection
     450            // (i.e. processing an element in an iframe, frame or a scrollable block element):
     451
     452            // 1) If candidateParent is not null, and it holds the distance and alignment data of the
     453            // parent container element itself;
     454            // 2) Parent of outer is <frame> or <iframe>;
     455            // 3) Parent is any other scrollable block element.
     456            if (!candidateParent.isNull()) {
     457                currentFocusCandidate.parentAlignment = candidateParent.alignment;
     458                currentFocusCandidate.parentDistance = candidateParent.distance;
     459                currentFocusCandidate.enclosingScrollableBox = candidateParent.node;
     460
     461            } else if (!isInRootDocument(outer)) {
     462                if (Document* document = static_cast<Document*>(outer->parent()))
     463                    currentFocusCandidate.enclosingScrollableBox = static_cast<Node*>(document->ownerElement());
     464
     465            } else if (isScrollableContainerNode(outer->parent()))
     466                currentFocusCandidate.enclosingScrollableBox = outer->parent();
    411467
    412468            // Get distance and alignment from current candidate.
     
    419475            }
    420476
    421             // If candidateParent is not null, it means that we are in a recursive call
    422             // from deepFineFocusableNodeInDirection (i.e. processing an element in an iframe),
    423             // and holds the distance and alignment data of the iframe element itself.
    424             if (!candidateParent.isNull()) {
    425                 currentFocusCandidate.parentAlignment = candidateParent.alignment;
    426                 currentFocusCandidate.parentDistance = candidateParent.distance;
    427             }
    428 
    429477            updateFocusCandidateIfCloser(focusedNode, currentFocusCandidate, closestFocusCandidate);
    430478        }
     
    438486                                                       FocusCandidate& closestFocusCandidate)
    439487{
    440     ASSERT(container->hasTagName(frameTag) || container->hasTagName(iframeTag));
     488    ASSERT(container->hasTagName(frameTag)
     489        || container->hasTagName(iframeTag)
     490        || isScrollableContainerNode(container));
    441491
    442492    // Track if focusedNode is a descendant of the current container node being processed.
     
    458508        firstChild = innerDocument->firstChild();
    459509
     510    // Scrollable block elements (e.g. <div>, etc)
     511    } else if (isScrollableContainerNode(container)) {
     512
     513        firstChild = container->firstChild();
     514        descendantOfContainer = focusedNode->isDescendantOf(container);
    460515    }
    461516
  • trunk/WebCore/page/SpatialNavigation.cpp

    r60943 r61134  
    528528}
    529529
     530bool isScrollableContainerNode(Node* node)
     531{
     532    if (!node)
     533        return false;
     534
     535    if (RenderObject* renderer = node->renderer()) {
     536        return (renderer->isBox() && toRenderBox(renderer)->canBeScrolledAndHasScrollableArea()
     537             && node->hasChildNodes() && !node->isDocumentNode());
     538    }
     539
     540    return false;
     541}
     542
    530543} // namespace WebCore
  • trunk/WebCore/page/SpatialNavigation.h

    r59046 r61134  
    9898    FocusCandidate()
    9999        : node(0)
     100        , enclosingScrollableBox(0)
    100101        , distance(maxDistance())
    101102        , parentDistance(maxDistance())
     
    107108    FocusCandidate(Node* n)
    108109        : node(n)
     110        , enclosingScrollableBox(0)
    109111        , distance(maxDistance())
    110112        , parentDistance(maxDistance())
     
    115117
    116118    bool isNull() const { return !node; }
     119    bool inScrollableContainer() const { return node && enclosingScrollableBox; }
    117120    Document* document() const { return node ? node->document() : 0; }
    118121
    119122    Node* node;
     123    Node* enclosingScrollableBox;
    120124    long long distance;
    121125    long long parentDistance;
     
    129133bool hasOffscreenRect(Node*);
    130134bool isInRootDocument(Node*);
     135bool isScrollableContainerNode(Node*);
    131136
    132137} // namspace WebCore
Note: See TracChangeset for help on using the changeset viewer.