Changeset 72522 in webkit


Ignore:
Timestamp:
Nov 22, 2010 5:49:09 AM (13 years ago)
Author:
yael.aharon@nokia.com
Message:

Spatial Navigation: issues with the node selection algorithm.
https://bugs.webkit.org/show_bug.cgi?id=49382

Patch by Yael Aharon <yael.aharon@nokia.com>, Chang Shu <chang.shu@nokia.com> on 2010-11-22
Reviewed by Antonio Gomes.

WebCore:

Modify the Spatial Navigation algorithm, to better handle initial focus and
navigation between frames.
The new algorithm takes the rect of the focused node as the startingRect,
instead of the node itself. That allows us to construct a virtual rect if
there is no focused node, or if it is off the screen.
The virtual rect is the edge of the container in the direction of the navigation.

With this patch, scrollable containers and frames will scroll regardless of weather
they have focusable content. Users will be able to use arrow keys to view all the
content of such a container. The only exception is if the container has style overflow:hidden.
We will not scroll in that case.

With this patch, we handle z-index and positioning so that if there are 2 overlapping focusable nodes,
we do a hit test and only the node on top can get focus.

hasOffScreenRect() was modified so that it can check if a node will be off-screen even after we scrolled
its parent container. We do not add the scrolling conditions for containers that have overflow:hidden
and cannot scroll.

calculateScrollbarModesForLayout is used to decide if a frame can scroll or not. We cannot rely on
the exsistance of the scrollbar, because it could be removed via the API, while the frame is still
allowed to scroll.

  • page/FocusController.cpp:

(WebCore::updateFocusCandidateIfNeeded):
(WebCore::FocusController::findFocusCandidateInContainer):
(WebCore::FocusController::advanceFocusDirectionallyInContainer):
(WebCore::FocusController::advanceFocusDirectionally):

  • page/FocusController.h:
  • page/FrameView.h:
  • page/SpatialNavigation.cpp:

(WebCore::FocusCandidate::FocusCandidate):
(WebCore::distanceDataForNode):
(WebCore::alignmentForRects):
(WebCore::areRectsMoreThanFullScreenApart):
(WebCore::isRectInDirection):
(WebCore::hasOffscreenRect):
(WebCore::scrollInDirection):
(WebCore::isScrollableContainerNode):
(WebCore::scrollableEnclosingBoxOrParentFrameForNodeInDirection):
(WebCore::canScrollInDirection):
(WebCore::rectToAbsoluteCoordinates):
(WebCore::nodeRectInAbsoluteCoordinates):
(WebCore::frameRectInAbsoluteCoordinates):
(WebCore::entryAndExitPointsForDirection):
(WebCore::canBeScrolledIntoView):
(WebCore::virtualRectForDirection):

  • page/SpatialNavigation.h:

LayoutTests:

Replaced text in tests with images with fixed size to make them more cross platform and modified
test results to reflect that we can scroll container that do not include focusable content.

  • fast/events/spatial-navigation/snav-clipped-overflowed-content-expected.txt:
  • fast/events/spatial-navigation/snav-clipped-overflowed-content.html:
  • fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content-expected.txt:
  • fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html:
  • fast/events/spatial-navigation/snav-iframe-no-focusable-content-expected.txt:
  • fast/events/spatial-navigation/snav-iframe-no-focusable-content.html:
  • fast/events/spatial-navigation/snav-iframe-no-scrollable-content-expected.txt:
  • fast/events/spatial-navigation/snav-iframe-no-scrollable-content.html:
  • fast/events/spatial-navigation/snav-iframe-with-offscreen-focusable-element-expected.txt:
  • fast/events/spatial-navigation/snav-iframe-with-offscreen-focusable-element.html:
Location:
trunk
Files:
17 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r72521 r72522  
     12010-11-22  Yael Aharon  <yael.aharon@nokia.com>, Chang Shu  <chang.shu@nokia.com>
     2
     3        Reviewed by Antonio Gomes.
     4
     5        Spatial Navigation: issues with the node selection algorithm.
     6        https://bugs.webkit.org/show_bug.cgi?id=49382
     7
     8        Replaced text in tests with images with fixed size to make them more cross platform and modified
     9        test results to reflect that we can scroll container that do not include focusable content.
     10
     11        * fast/events/spatial-navigation/snav-clipped-overflowed-content-expected.txt:
     12        * fast/events/spatial-navigation/snav-clipped-overflowed-content.html:
     13        * fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content-expected.txt:
     14        * fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html:
     15        * fast/events/spatial-navigation/snav-iframe-no-focusable-content-expected.txt:
     16        * fast/events/spatial-navigation/snav-iframe-no-focusable-content.html:
     17        * fast/events/spatial-navigation/snav-iframe-no-scrollable-content-expected.txt:
     18        * fast/events/spatial-navigation/snav-iframe-no-scrollable-content.html:
     19        * fast/events/spatial-navigation/snav-iframe-with-offscreen-focusable-element-expected.txt:
     20        * fast/events/spatial-navigation/snav-iframe-with-offscreen-focusable-element.html:
     21
    1222010-11-22  Anton Muhin  <antonm@chromium.org>
    223
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-clipped-overflowed-content-expected.txt

    r71915 r72522  
    77PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
    88PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     9PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     10PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
    911PASS gFocusedDocument.activeElement.getAttribute("id") is "3"
    1012PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
    1113PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     14PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     15PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
    1216PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
    1317PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-clipped-overflowed-content.html

    r71915 r72522  
    3737      ["Down", "1"],
    3838      ["Down", "2"],
     39      ["Down", "2"],
     40      ["Down", "2"],
    3941      ["Down", "3"],
    4042      ["Up", "2"],
    4143      ["Up", "2"],
     44      ["Up", "2"],
     45      ["Up", "1"],
    4246      ["Up", "1"],
    4347      ["Up", "start"],
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content-expected.txt

    r61819 r72522  
    1 a
    2 This is a scrollable Div created with the CSS property overflow.
    3 
    4 It has no keyboard focusable elements ....
    51
    62
    73
    84
    9 
    10 
    11 
    12 
    13 
    14 
    15 
    16 
    17 
    18 ... and scrollbars!
    19 
    20 This is another scrollable div created with the CSS property overflow.
    21 
    22 It has also no keyboard focusable elements ....
    23 
    24 
    25 
    26 
    27 
    28 
    29 
    30 ... as well as scrollbars!
    31 
    32 e
     5PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     6PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     7PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     8PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     9PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     10PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     11PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     12PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     13PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     14PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     15PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     16PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
    3317PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
    3418PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
    35 
     19This test is to test that a scrollable div can scroll and reveal its content even if it does not have any focusable content.
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-div-scrollable-but-without-focusable-content.html

    r61134 r72522  
    2929
    3030    var resultMap = [
     31      ["Down", "start"],
     32      ["Down", "start"],
     33      ["Down", "start"],
     34      ["Down", "start"],
     35      ["Down", "start"],
     36      ["Down", "start"],
    3137      ["Down", "end"],
     38      ["Up"  , "end"],
     39      ["Up"  , "end"],
     40      ["Up"  , "end"],
     41      ["Up"  , "end"],
     42      ["Up"  , "end"],
     43      ["Up"  , "end"],
    3244      ["Up"  , "start"],
    3345      ["DONE", "DONE"]
     
    6072  </head>
    6173  <body id="some-content" xmlns="http://www.w3.org/1999/xhtml">
    62     <div><a id="start" href="a">a</a></div>
     74    <div><a id="start" href="a"><img src="resources/green.png" width=30 height=30></a></div>
    6375    <div class="scroll">
    64       <p>This is a scrollable Div created with the CSS property overflow.</p>
    65       <p>It has no keyboard focusable elements ....</p>
    66       <br><br><br><br><br><br>
    67       <br><br><br><br><br><br>
    68       <p>... and scrollbars!</p>
     76      <img src="resources/green.png" width=240 height=300>
    6977    </div>
    7078
    7179    <div class="scroll">
    72       <p>This is another scrollable div created with the CSS property overflow.</p>
    73       <p>It has also no keyboard focusable elements ....</p>
    74       <br><br><br><br><br><br>
    75       <p>... as well as scrollbars!</p>
     80      <img src="resources/green.png" width=240 height=300>
    7681    </div>
    77     <div><a id="end" href="a">e</a></div>
     82    <div><a id="end" href="a"><img src="resources/green.png" width=30 height=30></a></div>
    7883    <div id="console"></div>
     84    This test is to test that a scrollable div can scroll and reveal its content even if it does not have any focusable content.
    7985  </body>
    8086</html>
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-iframe-no-focusable-content-expected.txt

    r61819 r72522  
    33
    44e
     5PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     6PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     7PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     8PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     9PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     10PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
     11PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     12PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     13PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     14PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     15PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
     16PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
    517PASS gFocusedDocument.activeElement.getAttribute("id") is "end"
    618PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
    7 
     19This is to test that an iframe with no focusable content still scrolls
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-iframe-no-focusable-content.html

    r58888 r72522  
    1818
    1919    var resultMap = [
     20      ["Down", "start"],
     21      ["Down", "start"],
     22      ["Down", "start"],
     23      ["Down", "start"],
     24      ["Down", "start"],
     25      ["Down", "start"],
    2026      ["Down", "end"],
     27      ["Up"  , "end"],
     28      ["Up"  , "end"],
     29      ["Up"  , "end"],
     30      ["Up"  , "end"],
     31      ["Up"  , "end"],
     32      ["Up"  , "end"],
    2133      ["Up"  , "start"],
    2234      ["DONE", "DONE"]
     
    5365    <iframe width="80" height="80" scrolling="auto" src="data:text/html,
    5466      <body>
    55         <div style='margin-top:120px'>
    56           <p>This frame has some text and no focusable element ...</a>
    57         </div>
    58         <p>... and some more text ...</a>
     67        <img width=120 height=200 src='resources/green.png'>
    5968      </body>
    6069    "></iframe><br>
     
    6271    <iframe scrolling="auto" src="data:text/html,
    6372      <body>
    64         <div style='margin-top:120px'>
    65           <p>This frame has some text and no focusable element ...</a>
    66         </div>
    67         <p>... and some more text ...</a>
     73        <img width=120 height=200 src='resources/green.png'>
    6874      </body>
    6975    "></iframe><br>
     
    7177    <a id="end" href="a">e</a>
    7278    <div id="console"></div>
     79    This is to test that an iframe with no focusable content still scrolls
    7380  </body>
    7481</html>
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-iframe-no-scrollable-content-expected.txt

    r61819 r72522  
    1212PASS gFocusedDocument.activeElement.getAttribute("id") is "5"
    1313PASS gFocusedDocument.activeElement.getAttribute("id") is "6"
     14PASS gFocusedDocument.activeElement.getAttribute("id") is "3"
    1415PASS gFocusedDocument.activeElement.getAttribute("id") is "7"
    1516PASS gFocusedDocument.activeElement.getAttribute("id") is "3"
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-iframe-no-scrollable-content.html

    r58888 r72522  
    2828      ["Right", "5"],
    2929      ["Right", "6"],
     30      ["Up", "3"],
    3031      ["Right", "7"],
    3132      ["Up", "3"],
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-iframe-with-offscreen-focusable-element-expected.txt

    r61819 r72522  
    1 a
    21
    3 e
     2
     3PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
     4PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
     5PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
     6PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
    47PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
    58PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
     
    1013PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
    1114PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     15PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     16PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     17PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
     18PASS gFocusedDocument.activeElement.getAttribute("id") is "2"
    1219PASS gFocusedDocument.activeElement.getAttribute("id") is "1"
    1320PASS gFocusedDocument.activeElement.getAttribute("id") is "start"
  • trunk/LayoutTests/fast/events/spatial-navigation/snav-iframe-with-offscreen-focusable-element.html

    r58888 r72522  
    2323      ["Down", "1"],
    2424      ["Down", "1"],
     25      ["Down", "1"],
     26      ["Down", "1"],
     27      ["Down", "1"],
     28      ["Down", "1"],
    2529      ["Down", "2"],
    2630      ["Down", "end"],
     31      ["Up", "2"],
     32      ["Up", "2"],
     33      ["Up", "2"],
     34      ["Up", "2"],
    2735      ["Up", "2"],
    2836      ["Up", "2"],
     
    5967  </head>
    6068  <body id="some-content" xmlns="http://www.w3.org/1999/xhtml">
    61     <div><a id="start" href="a">a</a></div>
     69    <div><a id="start" href="a"><img src="resources/green.png" width=30 height=30></a></div>
    6270
    6371    <iframe width="80" height="80" scrolling="auto" src="data:text/html,
    6472      <body>
    65         <a id='1' href='a'>b</a>
    66         <div style='margin-top:120px'>
    67           <a id='2' href='a'>d</a>
     73        <a id='1' href='a'><img src='no_image' width=30 height=30></a>
     74        <div>
     75          <img src='no_image' width=50 height=300>
     76          <a id='2' href='a'><img src='no_image' width=30 height=30></a>
    6877        </div>
    6978      </body>
    7079    "></iframe><br>
    7180
    72     <div><a id="end" href="a">e</a></div>
     81    <div><a id="end" href="a"><img src="resources/green.png" width=30 height=30></a></div>
    7382    <div id="console"></div>
    7483  </body>
  • trunk/WebCore/ChangeLog

    r72518 r72522  
     12010-11-22  Yael Aharon  <yael.aharon@nokia.com>, Chang Shu  <chang.shu@nokia.com>
     2
     3        Reviewed by Antonio Gomes.
     4
     5        Spatial Navigation: issues with the node selection algorithm.
     6        https://bugs.webkit.org/show_bug.cgi?id=49382
     7
     8        Modify the Spatial Navigation algorithm, to better handle initial focus and
     9        navigation between frames.
     10        The new algorithm takes the rect of the focused node as the startingRect,
     11        instead of the node itself. That allows us to construct a virtual rect if
     12        there is no focused node, or if it is off the screen.
     13        The virtual rect is the edge of the container in the direction of the navigation.
     14
     15        With this patch, scrollable containers and frames will scroll regardless of weather
     16        they have focusable content. Users will be able to use arrow keys to view all the
     17        content of such a container. The only exception is if the container has style overflow:hidden.
     18        We will not scroll in that case.
     19
     20        With this patch, we handle z-index and positioning so that if there are 2 overlapping focusable nodes,
     21        we do a hit test and only the node on top can get focus.
     22
     23        hasOffScreenRect() was modified so that it can check if a node will be off-screen even after we scrolled
     24        its parent container. We do not add the scrolling conditions for containers that have overflow:hidden
     25        and cannot scroll.
     26
     27        calculateScrollbarModesForLayout is used to decide if a frame can scroll or not. We cannot rely on
     28        the exsistance of the scrollbar, because it could be removed via the API, while the frame is still
     29        allowed to scroll.
     30
     31        * page/FocusController.cpp:
     32        (WebCore::updateFocusCandidateIfNeeded):
     33        (WebCore::FocusController::findFocusCandidateInContainer):
     34        (WebCore::FocusController::advanceFocusDirectionallyInContainer):
     35        (WebCore::FocusController::advanceFocusDirectionally):
     36        * page/FocusController.h:
     37        * page/FrameView.h:
     38        * page/SpatialNavigation.cpp:
     39        (WebCore::FocusCandidate::FocusCandidate):
     40        (WebCore::distanceDataForNode):
     41        (WebCore::alignmentForRects):
     42        (WebCore::areRectsMoreThanFullScreenApart):
     43        (WebCore::isRectInDirection):
     44        (WebCore::hasOffscreenRect):
     45        (WebCore::scrollInDirection):
     46        (WebCore::isScrollableContainerNode):
     47        (WebCore::scrollableEnclosingBoxOrParentFrameForNodeInDirection):
     48        (WebCore::canScrollInDirection):
     49        (WebCore::rectToAbsoluteCoordinates):
     50        (WebCore::nodeRectInAbsoluteCoordinates):
     51        (WebCore::frameRectInAbsoluteCoordinates):
     52        (WebCore::entryAndExitPointsForDirection):
     53        (WebCore::canBeScrolledIntoView):
     54        (WebCore::virtualRectForDirection):
     55        * page/SpatialNavigation.h:
     56
    1572010-11-22  Nikolas Zimmermann  <nzimmermann@rim.com>
    258
  • trunk/WebCore/page/FocusController.cpp

    r72259 r72522  
    4141#include "FrameTree.h"
    4242#include "FrameView.h"
     43#include "HitTestResult.h"
    4344#include "HTMLFrameOwnerElement.h"
    4445#include "HTMLNames.h"
     
    4647#include "Page.h"
    4748#include "Range.h"
     49#include "RenderLayer.h"
    4850#include "RenderObject.h"
    4951#include "RenderWidget.h"
     
    5860using namespace std;
    5961
     62static void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest);
    6063static inline void dispatchEventsOnWindowAndFocusedNode(Document* document, bool focused)
    6164{
     
    290293}
    291294
    292 bool FocusController::advanceFocusDirectionally(FocusDirection direction, KeyboardEvent* event)
    293 {
    294     Frame* frame = focusedOrMainFrame();
    295     ASSERT(frame);
    296     Document* focusedDocument = frame->document();
    297     if (!focusedDocument)
    298         return false;
    299 
    300     Node* focusedNode = focusedDocument->focusedNode();
    301     if (!focusedNode) {
    302         // Just move to the first focusable node.
    303         FocusDirection tabDirection = (direction == FocusDirectionUp || direction == FocusDirectionLeft) ?
    304                                        FocusDirectionBackward : FocusDirectionForward;
    305         // 'initialFocus' is set to true so the chrome is not focused.
    306         return advanceFocusInDocumentOrder(tabDirection, event, true);
    307     }
    308 
    309     // Move up in the chain of nested frames.
    310     frame = frame->tree()->top();
    311 
    312     FocusCandidate focusCandidate;
    313     findFocusableNodeInDirection(frame->document()->firstChild(), focusedNode, direction, event, focusCandidate);
    314 
    315     Node* node = focusCandidate.node;
    316     if (!node || !node->isElementNode()) {
    317         // FIXME: May need a way to focus a document here.
    318         Frame* frame = focusedOrMainFrame();
    319         scrollInDirection(frame, direction);
    320         return false;
    321     }
    322 
    323     // In order to avoid crazy jump between links that are either far away from each other,
    324     // or just not currently visible, lets do a scroll in the given direction and bail out
    325     // if |node| element is not in the viewport.
    326     if (hasOffscreenRect(node)) {
    327         Frame* frame = node->document()->view()->frame();
    328         scrollInDirection(frame, direction, focusCandidate);
    329         return true;
    330     }
    331 
    332     Document* newDocument = node->document();
    333 
    334     if (newDocument != focusedDocument) {
    335         // Focus is going away from the originally focused document, so clear the focused node.
    336         focusedDocument->setFocusedNode(0);
    337     }
    338 
    339     if (newDocument)
    340         setFocusedFrame(newDocument->frame());
    341 
    342     Element* element = static_cast<Element*>(node);
    343     ASSERT(element);
    344 
    345     scrollIntoView(element);
    346     element->focus(false);
    347     return true;
    348 }
    349 
    350295static void updateFocusCandidateInSameContainer(const FocusCandidate& candidate, FocusCandidate& closest)
    351296{
     
    659604}
    660605
     606void updateFocusCandidateIfNeeded(FocusDirection direction, const IntRect& startingRect, FocusCandidate& candidate, FocusCandidate& closest)
     607{
     608    if (!candidate.node->isElementNode() || !candidate.node->renderer())
     609        return;
     610
     611    // Ignore iframes that don't have a src attribute
     612    if (candidate.node->isFrameOwnerElement() && !static_cast<HTMLFrameOwnerElement*>(candidate.node)->contentFrame())
     613        return;
     614
     615    // Ignore off screen child nodes of containers that do not scroll (overflow:hidden)
     616    if (hasOffscreenRect(candidate.node) && !canBeScrolledIntoView(direction, candidate))
     617        return;
     618
     619    FocusCandidate current;
     620    current.rect = startingRect;
     621    distanceDataForNode(direction, current, candidate);
     622    if (candidate.distance == maxDistance())
     623        return;
     624
     625    if (hasOffscreenRect(candidate.node, direction) && candidate.alignment < Full)
     626        return;
     627
     628    if (closest.isNull()) {
     629        closest = candidate;
     630        return;
     631    }
     632
     633    IntRect intersectionRect = intersection(nodeRectInAbsoluteCoordinates(candidate.node, true), nodeRectInAbsoluteCoordinates(closest.node, true));
     634    if (!intersectionRect.isEmpty()) {
     635        // If 2 nodes are intersecting, do hit test to find which node in on top.
     636        int x = intersectionRect.x() + intersectionRect.width() / 2;
     637        int y = intersectionRect.y() + intersectionRect.height() / 2;
     638        HitTestResult result = candidate.node->document()->page()->mainFrame()->eventHandler()->hitTestResultAtPoint(IntPoint(x, y), false, true);
     639        if (candidate.node->contains(result.innerNode())) {
     640            closest = candidate;
     641            return;
     642        }
     643        if (closest.node->contains(result.innerNode()))
     644            return;
     645    }
     646
     647    if (candidate.alignment == closest.alignment) {
     648        if (candidate.distance < closest.distance)
     649            closest = candidate;
     650        return;
     651    }
     652
     653    if (candidate.alignment > closest.alignment)
     654        closest = candidate;
     655}
     656
     657void FocusController::findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closest)
     658{
     659    ASSERT(container);
     660    Node* focusedNode = (focusedFrame() && focusedFrame()->document()) ? focusedFrame()->document()->focusedNode() : 0;
     661
     662    Node* node = container->firstChild();
     663    for (; node; node = (node->isFrameOwnerElement() || canScrollInDirection(direction, node)) ? node->traverseNextSibling(container) : node->traverseNextNode(container)) {
     664        if (node == focusedNode)
     665            continue;
     666
     667        if (!node->renderer())
     668            continue;
     669
     670        if (!node->isKeyboardFocusable(event) && !node->isFrameOwnerElement() && !canScrollInDirection(direction, node))
     671            continue;
     672
     673        FocusCandidate candidate(node);
     674        candidate.enclosingScrollableBox = container;
     675        updateFocusCandidateIfNeeded(direction, startingRect, candidate, closest);
     676    }
     677}
     678
     679bool FocusController::advanceFocusDirectionallyInContainer(Node* container, const IntRect& startingRect, FocusDirection direction, KeyboardEvent* event)
     680{
     681    if (!container || !container->document())
     682        return false;
     683
     684    IntRect newStartingRect = startingRect;
     685
     686    if (startingRect.isEmpty())
     687        newStartingRect = virtualRectForDirection(direction, nodeRectInAbsoluteCoordinates(container));
     688
     689    // Find the closest node within current container in the direction of the navigation.
     690    FocusCandidate focusCandidate;
     691    findFocusCandidateInContainer(container, newStartingRect, direction, event, focusCandidate);
     692
     693    if (focusCandidate.isNull()) {
     694        if (canScrollInDirection(direction, container)) {
     695            // Nothing to focus, scroll if possible.
     696            scrollInDirection(container, direction);
     697            return true;
     698        }
     699        // Return false will cause a re-try, skipping this container.
     700        return false;
     701    }
     702    if (focusCandidate.node->isFrameOwnerElement()) {
     703        HTMLFrameOwnerElement* frameElement = static_cast<HTMLFrameOwnerElement*>(focusCandidate.node);
     704        // If we have an iframe without the src attribute, it will not have a contentFrame().
     705        // We ASSERT here to make sure that
     706        // updateFocusCandidateIfNeeded() will never consider such an iframe as a candidate.
     707        ASSERT(frameElement->contentFrame());
     708
     709        if (hasOffscreenRect(focusCandidate.node, direction)) {
     710            scrollInDirection(focusCandidate.node->document(), direction);
     711            return true;
     712        }
     713        // Navigate into a new frame.
     714        IntRect rect;
     715        Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
     716        if (focusedNode && !hasOffscreenRect(focusedNode))
     717            rect = nodeRectInAbsoluteCoordinates(focusedNode, true /* ignore border */);
     718        frameElement->contentFrame()->document()->updateLayoutIgnorePendingStylesheets();
     719        if (!advanceFocusDirectionallyInContainer(frameElement->contentFrame()->document(), rect, direction, event)) {
     720            // The new frame had nothing interesting, need to find another candidate.
     721            return advanceFocusDirectionallyInContainer(container, nodeRectInAbsoluteCoordinates(focusCandidate.node, true), direction, event);
     722        }
     723        return true;
     724    }
     725    if (canScrollInDirection(direction, focusCandidate.node)) {
     726        if (hasOffscreenRect(focusCandidate.node, direction)) {
     727            scrollInDirection(focusCandidate.node, direction);
     728            return true;
     729        }
     730        // Navigate into a new scrollable container.
     731        IntRect startingRect;
     732        Node* focusedNode = focusedOrMainFrame()->document()->focusedNode();
     733        if (focusedNode && !hasOffscreenRect(focusedNode))
     734            startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true);
     735        return advanceFocusDirectionallyInContainer(focusCandidate.node, startingRect, direction, event);
     736    }
     737    if (hasOffscreenRect(focusCandidate.node, direction)) {
     738        Node* container = focusCandidate.enclosingScrollableBox;
     739        scrollInDirection(container, direction);
     740        return true;
     741    }
     742
     743    // We found a new focus node, navigate to it.
     744    Element* element = toElement(focusCandidate.node);
     745    ASSERT(element);
     746
     747    element->focus(false);
     748    return true;
     749}
     750
     751bool FocusController::advanceFocusDirectionally(FocusDirection direction, KeyboardEvent* event)
     752{
     753    Frame* curFrame = focusedOrMainFrame();
     754    ASSERT(curFrame);
     755
     756    Document* focusedDocument = curFrame->document();
     757    if (!focusedDocument)
     758        return false;
     759
     760    Node* focusedNode = focusedDocument->focusedNode();
     761    Node* container = focusedDocument;
     762
     763    // Figure out the starting rect.
     764    IntRect startingRect;
     765    if (focusedNode && !hasOffscreenRect(focusedNode)) {
     766        container = scrollableEnclosingBoxOrParentFrameForNodeInDirection(direction, focusedNode);
     767        startingRect = nodeRectInAbsoluteCoordinates(focusedNode, true /* ignore border */);
     768    }
     769
     770    bool consumed = false;
     771    do {
     772        if (container->isDocumentNode())
     773            static_cast<Document*>(container)->updateLayoutIgnorePendingStylesheets();
     774        consumed = advanceFocusDirectionallyInContainer(container, startingRect, direction, event);
     775        startingRect = nodeRectInAbsoluteCoordinates(container, true /* ignore border */);
     776        container = scrollableEnclosingBoxOrParentFrameForNodeInDirection(direction, container);
     777    } while (!consumed && container);
     778
     779    return consumed;
     780}
     781
    661782} // namespace WebCore
  • trunk/WebCore/page/FocusController.h

    r67025 r72522  
    6969    void deepFindFocusableNodeInDirection(Node* container, Node* focused, FocusDirection, KeyboardEvent*, FocusCandidate&);
    7070
     71    bool advanceFocusDirectionallyInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*);
     72    void findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*, FocusCandidate& closest);
     73
    7174    Page* m_page;
    7275    RefPtr<Frame> m_focusedFrame;
  • trunk/WebCore/page/FrameView.h

    r72465 r72522  
    237237    void invalidateScrollCorner();
    238238
     239    void calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode);
     240
    239241    // Normal delay
    240242    static void setRepaintThrottlingDeferredRepaintDelay(double p);
     
    266268
    267269    void applyOverflowToViewport(RenderObject*, ScrollbarMode& hMode, ScrollbarMode& vMode);
    268     void calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode);
    269270
    270271    void updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow);
  • trunk/WebCore/page/SpatialNavigation.cpp

    r71479 r72522  
    4444static long long spatialDistance(FocusDirection, const IntRect&, const IntRect&);
    4545static IntRect renderRectRelativeToRootDocument(RenderObject*);
    46 static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&);
     46static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&, const IntSize& viewSize);
    4747static bool areRectsFullyAligned(FocusDirection, const IntRect&, const IntRect&);
    4848static bool areRectsPartiallyAligned(FocusDirection, const IntRect&, const IntRect&);
     49static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize);
    4950static bool isRectInDirection(FocusDirection, const IntRect&, const IntRect&);
    5051static void deflateIfOverlapped(IntRect&, IntRect&);
    5152static bool checkNegativeCoordsForNode(Node*, const IntRect&);
     53static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& rect);
     54static void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint);
     55
     56
     57FocusCandidate::FocusCandidate(Node* n)
     58    : node(n)
     59    , enclosingScrollableBox(0)
     60    , distance(maxDistance())
     61    , parentDistance(maxDistance())
     62    , alignment(None)
     63    , parentAlignment(None)
     64    , rect(nodeRectInAbsoluteCoordinates(n, true /* ignore border */))
     65{
     66}
    5267
    5368bool isSpatialNavigationEnabled(const Frame* frame)
     
    103118    // for the best focus candidate node. Alignment of rects can be also a good point to be
    104119    // considered in order to make the algorithm to behavior in a more intuitive way.
    105     candidate.alignment = alignmentForRects(direction, curRect, targetRect);
     120    IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
     121    candidate.alignment = alignmentForRects(direction, curRect, targetRect, viewSize);
    106122    candidate.distance = spatialDistance(direction, curRect, targetRect);
    107123}
     
    137153}
    138154
    139 static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
    140 {
     155static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
     156{
     157    // If we found a node in full alignment, but it is too far away, ignore it.
     158    if (areRectsMoreThanFullScreenApart(direction, curRect, targetRect, viewSize))
     159        return None;
     160
    141161    if (areRectsFullyAligned(direction, curRect, targetRect))
    142162        return Full;
     
    278298}
    279299
     300static bool areRectsMoreThanFullScreenApart(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect, const IntSize& viewSize)
     301{
     302    ASSERT(isRectInDirection(direction, curRect, targetRect));
     303
     304    switch (direction) {
     305    case FocusDirectionLeft:
     306        return curRect.x() - targetRect.right() > viewSize.width();
     307    case FocusDirectionRight:
     308        return targetRect.x() - curRect.right() > viewSize.width();
     309    case FocusDirectionUp:
     310        return curRect.y() - targetRect.bottom() > viewSize.height();
     311    case FocusDirectionDown:
     312        return targetRect.y() - curRect.bottom() > viewSize.height();
     313    default:
     314        ASSERT_NOT_REACHED();
     315        return true;
     316    }
     317}
     318
    280319// Return true if rect |a| is below |b|. False otherwise.
    281320static inline bool below(const IntRect& a, const IntRect& b)
     
    404443static bool isRectInDirection(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
    405444{
    406     IntPoint center(targetRect.center());
    407     int targetMiddle = isHorizontalMove(direction) ? center.x() : center.y();
    408 
    409445    switch (direction) {
    410446    case FocusDirectionLeft:
    411         return targetMiddle < curRect.x();
     447        return targetRect.right() <= curRect.x();
    412448    case FocusDirectionRight:
    413         return targetMiddle > curRect.right();
     449        return targetRect.x() >= curRect.right();
    414450    case FocusDirectionUp:
    415         return targetMiddle < curRect.y();
     451        return targetRect.bottom() <= curRect.y();
    416452    case FocusDirectionDown:
    417         return targetMiddle > curRect.bottom();
     453        return targetRect.y() >= curRect.bottom();
    418454    default:
    419455        ASSERT_NOT_REACHED();
    420     }
    421 
    422     return false;
     456        return false;
     457    }
    423458}
    424459
     
    426461// document. In case it is, one can scroll in direction or take any different
    427462// desired action later on.
    428 bool hasOffscreenRect(Node* node)
     463bool hasOffscreenRect(Node* node, FocusDirection direction)
    429464{
    430465    // Get the FrameView in which |node| is (which means the current viewport if |node|
     
    436471
    437472    IntRect containerViewportRect = frameView->visibleContentRect();
     473    // We want to select a node if it is currently off screen, but will be
     474    // exposed after we scroll. Adjust the viewport to post-scrolling position.
     475    // If the container has overflow:hidden, we cannot scroll, so we do not pass direction
     476    // and we do not adjust for scrolling.
     477    switch (direction) {
     478    case FocusDirectionLeft:
     479        containerViewportRect.setX(containerViewportRect.x() - Scrollbar::pixelsPerLineStep());
     480        containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
     481        break;
     482    case FocusDirectionRight:
     483        containerViewportRect.setWidth(containerViewportRect.width() + Scrollbar::pixelsPerLineStep());
     484        break;
     485    case FocusDirectionUp:
     486        containerViewportRect.setY(containerViewportRect.y() - Scrollbar::pixelsPerLineStep());
     487        containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
     488        break;
     489    case FocusDirectionDown:
     490        containerViewportRect.setHeight(containerViewportRect.height() + Scrollbar::pixelsPerLineStep());
     491        break;
     492    default:
     493        break;
     494    }
    438495
    439496    RenderObject* render = node->renderer();
     
    448505}
    449506
    450 // In a bottom-up way, this method tries to scroll |frame| in a given direction
    451 // |direction|, going up in the frame tree hierarchy in case it does not succeed.
    452 bool scrollInDirection(Frame* frame, FocusDirection direction, const FocusCandidate& candidate)
    453 {
    454     if (!frame)
    455         return false;
    456 
    457     ScrollDirection scrollDirection;
    458 
    459     switch (direction) {
    460     case FocusDirectionLeft:
    461         scrollDirection = ScrollLeft;
    462         break;
    463     case FocusDirectionRight:
    464         scrollDirection = ScrollRight;
    465         break;
    466     case FocusDirectionUp:
    467         scrollDirection = ScrollUp;
    468         break;
    469     case FocusDirectionDown:
    470         scrollDirection = ScrollDown;
    471         break;
    472     default:
    473         return false;
    474     }
    475 
    476     if (!candidate.isNull() && isScrollableContainerNode(candidate.enclosingScrollableBox))
    477         return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine, candidate.enclosingScrollableBox);
    478 
    479     return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine);
     507bool scrollInDirection(Frame* frame, FocusDirection direction)
     508{
     509    ASSERT(frame && canScrollInDirection(direction, frame->document()));
     510
     511    if (frame && canScrollInDirection(direction, frame->document())) {
     512        int dx = 0;
     513        int dy = 0;
     514        switch (direction) {
     515        case FocusDirectionLeft:
     516            dx = - Scrollbar::pixelsPerLineStep();
     517            break;
     518        case FocusDirectionRight:
     519            dx = Scrollbar::pixelsPerLineStep();
     520            break;
     521        case FocusDirectionUp:
     522            dy = - Scrollbar::pixelsPerLineStep();
     523            break;
     524        case FocusDirectionDown:
     525            dy = Scrollbar::pixelsPerLineStep();
     526            break;
     527        default:
     528            ASSERT_NOT_REACHED();
     529            return false;
     530        }
     531
     532        frame->view()->scrollBy(IntSize(dx, dy));
     533        return true;
     534    }
     535    return false;
     536}
     537
     538bool scrollInDirection(Node* container, FocusDirection direction)
     539{
     540    if (container->isDocumentNode())
     541        return scrollInDirection(static_cast<Document*>(container)->frame(), direction);
     542
     543    if (!container->renderBox())
     544        return false;
     545
     546    if (container && canScrollInDirection(direction, container)) {
     547        int dx = 0;
     548        int dy = 0;
     549        switch (direction) {
     550        case FocusDirectionLeft:
     551            dx = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollLeft());
     552            break;
     553        case FocusDirectionRight:
     554            ASSERT(container->renderBox()->scrollWidth() > (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
     555            dx = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollWidth() - (container->renderBox()->scrollLeft() + container->renderBox()->clientWidth()));
     556            break;
     557        case FocusDirectionUp:
     558            dy = - min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollTop());
     559            break;
     560        case FocusDirectionDown:
     561            ASSERT(container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
     562            dy = min(Scrollbar::pixelsPerLineStep(), container->renderBox()->scrollHeight() - (container->renderBox()->scrollTop() + container->renderBox()->clientHeight()));
     563            break;
     564        default:
     565            ASSERT_NOT_REACHED();
     566            return false;
     567        }
     568
     569        container->renderBox()->enclosingLayer()->scrollByRecursively(dx, dy);
     570        return true;
     571    }
     572    return false;
    480573}
    481574
     
    535628}
    536629
    537 bool isScrollableContainerNode(Node* node)
     630bool isScrollableContainerNode(const Node* node)
    538631{
    539632    if (!node)
     
    568661}
    569662
     663Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection direction, Node* node)
     664{
     665    ASSERT(node);
     666    Node* parent = node;
     667    do {
     668        if (parent->isDocumentNode())
     669            parent = static_cast<Document*>(parent)->document()->frame()->ownerElement();
     670        else
     671            parent = parent->parentNode();
     672    } while (parent && !canScrollInDirection(direction, parent) && !parent->isDocumentNode());
     673
     674    return parent;
     675}
     676
     677bool canScrollInDirection(FocusDirection direction, const Node* container)
     678{
     679    ASSERT(container);
     680    if (container->isDocumentNode())
     681        return canScrollInDirection(direction, static_cast<const Document*>(container)->frame());
     682
     683    if (!isScrollableContainerNode(container))
     684        return false;
     685
     686    switch (direction) {
     687    case FocusDirectionLeft:
     688        return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() > 0);
     689    case FocusDirectionUp:
     690        return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() > 0);
     691    case FocusDirectionRight:
     692        return (container->renderer()->style()->overflowX() != OHIDDEN && container->renderBox()->scrollLeft() + container->renderBox()->clientWidth() < container->renderBox()->scrollWidth());
     693    case FocusDirectionDown:
     694        return (container->renderer()->style()->overflowY() != OHIDDEN && container->renderBox()->scrollTop() + container->renderBox()->clientHeight() < container->renderBox()->scrollHeight());
     695    default:
     696        ASSERT_NOT_REACHED();
     697        return false;
     698    }
     699}
     700
     701bool canScrollInDirection(FocusDirection direction, const Frame* frame)
     702{
     703    if (!frame->view())
     704        return false;
     705    ScrollbarMode verticalMode;
     706    ScrollbarMode horizontalMode;
     707    frame->view()->calculateScrollbarModesForLayout(horizontalMode, verticalMode);
     708    if ((direction == FocusDirectionLeft || direction == FocusDirectionRight) && ScrollbarAlwaysOff == horizontalMode)
     709        return false;
     710    if ((direction == FocusDirectionUp || direction == FocusDirectionDown) &&  ScrollbarAlwaysOff == verticalMode)
     711        return false;
     712    IntSize size = frame->view()->contentsSize();
     713    IntSize offset = frame->view()->scrollOffset();
     714    IntRect rect = frame->view()->visibleContentRect(true);
     715
     716    switch (direction) {
     717    case FocusDirectionLeft:
     718        return offset.width() > 0;
     719    case FocusDirectionUp:
     720        return offset.height() > 0;
     721    case FocusDirectionRight:
     722        return rect.width() + offset.width() < size.width();
     723    case FocusDirectionDown:
     724        return rect.height() + offset.height() < size.height();
     725    default:
     726        ASSERT_NOT_REACHED();
     727        return false;
     728    }
     729}
     730
     731static IntRect rectToAbsoluteCoordinates(Frame* initialFrame, const IntRect& initialRect)
     732{
     733    IntRect rect = initialRect;
     734    for (Frame* frame = initialFrame; frame; frame = frame->tree()->parent()) {
     735        if (Element* element = static_cast<Element*>(frame->ownerElement())) {
     736            do {
     737                rect.move(element->offsetLeft(), element->offsetTop());
     738            } while ((element = element->offsetParent()));
     739            rect.move((-frame->view()->scrollOffset()));
     740        }
     741    }
     742    return rect;
     743}
     744
     745IntRect nodeRectInAbsoluteCoordinates(Node* node, bool ignoreBorder)
     746{
     747    ASSERT(node && node->renderer());
     748
     749    if (node->isDocumentNode())
     750        return frameRectInAbsoluteCoordinates(static_cast<Document*>(node)->frame());
     751    IntRect rect = rectToAbsoluteCoordinates(node->document()->frame(), node->getRect());
     752
     753    // For authors that use border instead of outline in their CSS, we compensate by ignoring the border when calculating
     754    // the rect of the focused element.
     755    if (ignoreBorder) {
     756        rect.move(node->renderer()->style()->borderLeftWidth(), node->renderer()->style()->borderTopWidth());
     757        rect.setWidth(rect.width() - node->renderer()->style()->borderLeftWidth() - node->renderer()->style()->borderRightWidth());
     758        rect.setHeight(rect.height() - node->renderer()->style()->borderTopWidth() - node->renderer()->style()->borderBottomWidth());
     759    }
     760    return rect;
     761}
     762
     763IntRect frameRectInAbsoluteCoordinates(Frame* frame)
     764{
     765    return rectToAbsoluteCoordinates(frame, frame->view()->visibleContentRect());
     766}
     767
     768// This method calculates the exitPoint from the startingRect and the entryPoint into the candidate rect.
     769// The line between those 2 points is the closest distance between the 2 rects.
     770void entryAndExitPointsForDirection(FocusDirection direction, const IntRect& startingRect, const IntRect& potentialRect, IntPoint& exitPoint, IntPoint& entryPoint)
     771{
     772    switch (direction) {
     773    case FocusDirectionLeft:
     774        exitPoint.setX(startingRect.x());
     775        entryPoint.setX(potentialRect.right());
     776        break;
     777    case FocusDirectionUp:
     778        exitPoint.setY(startingRect.y());
     779        entryPoint.setY(potentialRect.bottom());
     780        break;
     781    case FocusDirectionRight:
     782        exitPoint.setX(startingRect.right());
     783        entryPoint.setX(potentialRect.x());
     784        break;
     785    case FocusDirectionDown:
     786        exitPoint.setY(startingRect.bottom());
     787        entryPoint.setY(potentialRect.y());
     788        break;
     789    default:
     790        ASSERT_NOT_REACHED();
     791    }
     792
     793    switch (direction) {
     794    case FocusDirectionLeft:
     795    case FocusDirectionRight:
     796        if (below(startingRect, potentialRect)) {
     797            exitPoint.setY(startingRect.y());
     798            entryPoint.setY(potentialRect.bottom());
     799        } else if (below(potentialRect, startingRect)) {
     800            exitPoint.setY(startingRect.bottom());
     801            entryPoint.setY(potentialRect.y());
     802        } else {
     803            exitPoint.setY(max(startingRect.y(), potentialRect.y()));
     804            entryPoint.setY(exitPoint.y());
     805        }
     806        break;
     807    case FocusDirectionUp:
     808    case FocusDirectionDown:
     809        if (rightOf(startingRect, potentialRect)) {
     810            exitPoint.setX(startingRect.x());
     811            entryPoint.setX(potentialRect.right());
     812        } else if (rightOf(potentialRect, startingRect)) {
     813            exitPoint.setX(startingRect.right());
     814            entryPoint.setX(potentialRect.x());
     815        } else {
     816            exitPoint.setX(max(startingRect.x(), potentialRect.x()));
     817            entryPoint.setX(exitPoint.x());
     818        }
     819        break;
     820    default:
     821        ASSERT_NOT_REACHED();
     822    }
     823}
     824
     825void distanceDataForNode(FocusDirection direction, FocusCandidate& current, FocusCandidate& candidate)
     826{
     827    if (candidate.isNull())
     828        return;
     829    if (!candidate.node->renderer())
     830        return;
     831    IntRect nodeRect = candidate.rect;
     832    IntRect currentRect = current.rect;
     833    deflateIfOverlapped(currentRect, nodeRect);
     834
     835    if (!isRectInDirection(direction, currentRect, nodeRect))
     836        return;
     837
     838    IntPoint exitPoint;
     839    IntPoint entryPoint;
     840    int sameAxisDistance = 0;
     841    int otherAxisDistance = 0;
     842    entryAndExitPointsForDirection(direction, currentRect, nodeRect, exitPoint, entryPoint);
     843
     844    switch (direction) {
     845    case FocusDirectionLeft:
     846        sameAxisDistance = exitPoint.x() - entryPoint.x();
     847        otherAxisDistance = abs(exitPoint.y() - entryPoint.y());
     848        break;
     849    case FocusDirectionUp:
     850        sameAxisDistance = exitPoint.y() - entryPoint.y();
     851        otherAxisDistance = abs(exitPoint.x() - entryPoint.x());
     852        break;
     853    case FocusDirectionRight:
     854        sameAxisDistance = entryPoint.x() - exitPoint.x();
     855        otherAxisDistance = abs(entryPoint.y() - exitPoint.y());
     856        break;
     857    case FocusDirectionDown:
     858        sameAxisDistance = entryPoint.y() - exitPoint.y();
     859        otherAxisDistance = abs(entryPoint.x() - exitPoint.x());
     860        break;
     861    default:
     862        ASSERT_NOT_REACHED();
     863        return;
     864    }
     865
     866    int x = (entryPoint.x() - exitPoint.x()) * (entryPoint.x() - exitPoint.x());
     867    int y = (entryPoint.y() - exitPoint.y()) * (entryPoint.y() - exitPoint.y());
     868
     869    float euclidianDistance = sqrt((x + y) * 1.0f);
     870
     871    // Loosely based on http://www.w3.org/TR/WICD/#focus-handling
     872    // df = dotDist + dx + dy + 2 * (xdisplacement + ydisplacement) - sqrt(Overlap)
     873
     874    float distance = euclidianDistance + sameAxisDistance + 2 * otherAxisDistance;
     875    candidate.distance = roundf(distance);
     876    IntSize viewSize = candidate.node->document()->page()->mainFrame()->view()->visibleContentRect().size();
     877    candidate.alignment = alignmentForRects(direction, currentRect, nodeRect, viewSize);
     878}
     879
     880bool canBeScrolledIntoView(FocusDirection direction, FocusCandidate& candidate)
     881{
     882    ASSERT(candidate.node && hasOffscreenRect(candidate.node));
     883    IntRect candidateRect = candidate.rect;
     884    for (Node* parentNode = candidate.node->parent(); parentNode; parentNode = parentNode->parent()) {
     885        IntRect parentRect = nodeRectInAbsoluteCoordinates(parentNode);
     886        if (!candidateRect.intersects(parentRect)) {
     887            if (((direction == FocusDirectionLeft || direction == FocusDirectionRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN)
     888                || ((direction == FocusDirectionUp || direction == FocusDirectionDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN))
     889                return false;
     890        }
     891        if (parentNode == candidate.enclosingScrollableBox)
     892            return canScrollInDirection(direction, parentNode);
     893    }
     894    return true;
     895}
     896
     897// The starting rect is the rect of the focused node, in document coordinates.
     898// Compose a virtual starting rect if there is no focused node or if it is off screen.
     899// The virtual rect is the edge of the container or frame. We select which
     900// edge depending on the direction of the navigation.
     901IntRect virtualRectForDirection(FocusDirection direction, const IntRect& startingRect)
     902{
     903    IntRect virtualStartingRect = startingRect;
     904    switch (direction) {
     905    case FocusDirectionLeft:
     906        virtualStartingRect.setX(virtualStartingRect.right());
     907        virtualStartingRect.setWidth(0);
     908        break;
     909    case FocusDirectionUp:
     910        virtualStartingRect.setY(virtualStartingRect.bottom());
     911        virtualStartingRect.setHeight(0);
     912        break;
     913    case FocusDirectionRight:
     914        virtualStartingRect.setWidth(0);
     915        break;
     916    case FocusDirectionDown:
     917        virtualStartingRect.setHeight(0);
     918        break;
     919    default:
     920        ASSERT_NOT_REACHED();
     921    }
     922
     923    return virtualStartingRect;
     924}
     925
     926
    570927} // namespace WebCore
  • trunk/WebCore/page/SpatialNavigation.h

    r71479 r72522  
    2323
    2424#include "FocusDirection.h"
     25#include "IntRect.h"
    2526#include "Node.h"
    2627
     
    108109    }
    109110
    110     FocusCandidate(Node* n)
    111         : node(n)
    112         , enclosingScrollableBox(0)
    113         , distance(maxDistance())
    114         , parentDistance(maxDistance())
    115         , alignment(None)
    116         , parentAlignment(None)
    117     {
    118     }
    119 
     111    FocusCandidate(Node* n);
    120112    bool isNull() const { return !node; }
    121113    bool inScrollableContainer() const { return node && enclosingScrollableBox; }
     
    128120    RectsAlignment alignment;
    129121    RectsAlignment parentAlignment;
     122    IntRect rect;
    130123};
    131124
    132125void distanceDataForNode(FocusDirection direction, Node* start, FocusCandidate& candidate);
    133 bool scrollInDirection(Frame*, FocusDirection, const FocusCandidate& candidate = FocusCandidate());
     126bool scrollInDirection(Frame*, FocusDirection);
     127bool scrollInDirection(Node* container, FocusDirection);
    134128void scrollIntoView(Element*);
    135 bool hasOffscreenRect(Node*);
     129bool hasOffscreenRect(Node*, FocusDirection direction = FocusDirectionNone);
    136130bool isInRootDocument(Node*);
    137 bool isScrollableContainerNode(Node*);
     131bool isScrollableContainerNode(const Node*);
    138132bool isNodeDeepDescendantOfDocument(Node*, Document*);
    139 
     133Node* scrollableEnclosingBoxOrParentFrameForNodeInDirection(FocusDirection, Node* node);
     134bool canScrollInDirection(FocusDirection, const Node* container);
     135bool canScrollInDirection(FocusDirection, const Frame*);
     136IntRect nodeRectInAbsoluteCoordinates(Node*, bool ignoreBorder = false);
     137IntRect frameRectInAbsoluteCoordinates(Frame*);
     138void distanceDataForNode(FocusDirection, FocusCandidate& current, FocusCandidate& candidate);
     139bool canBeScrolledIntoView(FocusDirection, FocusCandidate&);
     140IntRect virtualRectForDirection(FocusDirection, const IntRect& startingRect);
    140141} // namspace WebCore
    141142
Note: See TracChangeset for help on using the changeset viewer.