[SCM] WebKit Debian packaging branch, webkit-1.2, updated. upstream/1.1.90-6072-g9a69373

tonikitoo at webkit.org tonikitoo at webkit.org
Thu Apr 8 02:10:04 UTC 2010


The following commit has been merged in the webkit-1.2 branch:
commit 660f8ffe8dfdcc7310e1b75158ce919be4a1c4ef
Author: tonikitoo at webkit.org <tonikitoo at webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date:   Thu Mar 4 20:19:44 2010 +0000

    Extend keyboard navigation to allow directional navigation
    https://bugs.webkit.org/show_bug.cgi?id=18662
    
    Reviewed by Simon Fraser, Eric Seidel and Darin Adler.
    Patch by Antonio Gomes <tonikitoo at webkit.org>
    Based on the initial work of Marco Barisione <marco.barisione at collabora.co.uk>
    
    This patch implements the core logic of the 'Spatial Navigation' feature [1].
    It improves the accessibility support of WebCore by extending the basic keyboard
    navigation currently available (based on Tab forward and backward) with the
    addition of a two-dimensional directional navigation by using Left, Right, Up and
    Down arrow keys to move to the "nearest" element in the corresponding direction.
    
    Highlights:
    * Feature is turned off by default in Settings. Port specific APIs need to be added
      for toggling it on/off.
    * Only elements viewed in the current viewport can have focus move to it. If the
      "nearest" is not in viewport dimensions, then a scroll-in-direction action is
      performed.
    * The layout tests added run on Qt's DRT only for now (skipped for Mac, Win and Gtk).
    
    Known issues (to be covered in follow-up bugs):
    * Add port specific hooks to each DRT to enable/disable Spatial Navigation.
    * Support for spatial navigation through form elements (<input>, <select>, etc)
      is be added.
    * Make navigation keys customizable. It currently works with arrows keys only
      (up, down, right and left).
    * Make it support modifiers (Alt, Ctrl and Shift).
    
    [1] http://en.wikipedia.org/wiki/Spatial_navigation
    
    * Android.mk:
    * GNUmakefile.am:
    * WebCore.gypi:
    * WebCore.pro:
    * WebCore.vcproj/WebCore.vcproj:
    * page/EventHandler.cpp:
    (WebCore::EventHandler::defaultKeyboardEventHandler):
    (WebCore::EventHandler::focusDirectionForKey):
    (WebCore::EventHandler::defaultArrowEventHandler):
    * page/EventHandler.h:
    * page/FocusController.cpp:
    (WebCore::FocusController::advanceFocus):
    (WebCore::FocusController::advanceFocusInDocumentOrder):
    (WebCore::FocusController::advanceFocusDirectionally):
    (WebCore::updateFocusCandidateIfCloser):
    (WebCore::FocusController::findFocusableNodeInDirection):
    (WebCore::FocusController::deepFindFocusableNodeInDirection):
    * page/FocusController.h:
    * page/FocusDirection.h:
    (WebCore::):
    * page/Settings.cpp:
    (WebCore::Settings::Settings):
    (WebCore::Settings::setSpatialNavigationEnabled):
    * page/Settings.h:
    (WebCore::Settings::isSpatialNavigationEnabled):
    * page/SpatialNavigation.cpp: Added.
    (WebCore::distanceInDirection):
    (WebCore::renderRectRelativeToRootDocument):
    (WebCore::alignmentForRects):
    (WebCore::isHorizontalMove):
    (WebCore::areRectsFullyAligned):
    (WebCore::areRectsPartiallyAligned):
    (WebCore::spatialDistance):
    (WebCore::isRectInDirection):
    (WebCore::hasOffscreenRect):
    (WebCore::scrollInDirection):
    (WebCore::isInRootDocument):
    (WebCore::deflateIfOverlapped):
    * page/SpatialNavigation.h: Added.
    (WebCore::):
    (WebCore::FocusCandidate::FocusCandidate):
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@55543 268f45cc-cd09-0410-ab3c-d52691b4dbfc

diff --git a/WebCore/Android.mk b/WebCore/Android.mk
index f2675e0..917350f 100644
--- a/WebCore/Android.mk
+++ b/WebCore/Android.mk
@@ -351,6 +351,7 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
 	page/Screen.cpp \
 	page/SecurityOrigin.cpp \
 	page/Settings.cpp \
+	page/SpatialNavigation.cpp \
 	page/UserContentURLPattern.cpp \
 	page/WindowFeatures.cpp \
 	page/WorkerNavigator.cpp \
diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog
index 7dea1d0..3a68d55 100644
--- a/WebCore/ChangeLog
+++ b/WebCore/ChangeLog
@@ -1,3 +1,78 @@
+2010-03-02  Antonio Gomes  <tonikitoo at webkit.org>
+
+        Reviewed by Simon Fraser, Eric Seidel and Darin Adler.
+        Patch by Antonio Gomes <tonikitoo at webkit.org>
+        Based on the initial work of Marco Barisione <marco.barisione at collabora.co.uk>
+
+        Extend keyboard navigation to allow directional navigation
+        https://bugs.webkit.org/show_bug.cgi?id=18662
+
+        This patch implements the core logic of the 'Spatial Navigation' feature [1].
+        It improves the accessibility support of WebCore by extending the basic keyboard
+        navigation currently available (based on Tab forward and backward) with the
+        addition of a two-dimensional directional navigation by using Left, Right, Up and
+        Down arrow keys to move to the "nearest" element in the corresponding direction.
+
+        Highlights:
+        * Feature is turned off by default in Settings. Port specific APIs need to be added
+          for toggling it on/off.
+        * Only elements viewed in the current viewport can have focus move to it. If the
+          "nearest" is not in viewport dimensions, then a scroll-in-direction action is
+          performed.
+
+        Known issues (to be covered in follow-up bugs):
+        * Add port specific hooks to each DRT to enable/disable Spatial Navigation.
+        * Support for spatial navigation through form elements (<input>, <select>, etc)
+          is be added.
+        * Make navigation keys customizable. It currently works with arrows keys only
+          (up, down, right and left).
+        * Make it support modifiers (Alt, Ctrl and Shift).
+        * Improve support on scrollable content.
+
+        [1] http://en.wikipedia.org/wiki/Spatial_navigation
+
+        * Android.mk:
+        * GNUmakefile.am:
+        * WebCore.gypi:
+        * WebCore.pro:
+        * WebCore.vcproj/WebCore.vcproj:
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::defaultKeyboardEventHandler):
+        (WebCore::EventHandler::focusDirectionForKey):
+        (WebCore::EventHandler::defaultArrowEventHandler):
+        * page/EventHandler.h:
+        * page/FocusController.cpp:
+        (WebCore::FocusController::advanceFocus):
+        (WebCore::FocusController::advanceFocusInDocumentOrder):
+        (WebCore::FocusController::advanceFocusDirectionally):
+        (WebCore::updateFocusCandidateIfCloser):
+        (WebCore::FocusController::findFocusableNodeInDirection):
+        (WebCore::FocusController::deepFindFocusableNodeInDirection):
+        * page/FocusController.h:
+        * page/FocusDirection.h:
+        (WebCore::):
+        * page/Settings.cpp:
+        (WebCore::Settings::Settings):
+        (WebCore::Settings::setSpatialNavigationEnabled):
+        * page/Settings.h:
+        (WebCore::Settings::isSpatialNavigationEnabled):
+        * page/SpatialNavigation.cpp: Added.
+        (WebCore::distanceInDirection):
+        (WebCore::renderRectRelativeToRootDocument):
+        (WebCore::alignmentForRects):
+        (WebCore::isHorizontalMove):
+        (WebCore::areRectsFullyAligned):
+        (WebCore::areRectsPartiallyAligned):
+        (WebCore::spatialDistance):
+        (WebCore::isRectInDirection):
+        (WebCore::hasOffscreenRect):
+        (WebCore::scrollInDirection):
+        (WebCore::isInRootDocument):
+        (WebCore::deflateIfOverlapped):
+        * page/SpatialNavigation.h: Added.
+        (WebCore::):
+        (WebCore::FocusCandidate::FocusCandidate):
+
 2010-03-04  Beth Dakin  <bdakin at apple.com>
 
         Reviewed by Anders Carlsson.
diff --git a/WebCore/GNUmakefile.am b/WebCore/GNUmakefile.am
index f7bb217..8fd36bc 100644
--- a/WebCore/GNUmakefile.am
+++ b/WebCore/GNUmakefile.am
@@ -1420,6 +1420,8 @@ webcore_sources += \
 	WebCore/page/SecurityOriginHash.h \
 	WebCore/page/Settings.cpp \
 	WebCore/page/Settings.h \
+	WebCore/page/SpatialNavigation.cpp \
+	WebCore/page/SpatialNavigation.h \
 	WebCore/page/UserContentURLPattern.cpp \
 	WebCore/page/UserContentURLPattern.h \
 	WebCore/page/UserScript.h \
diff --git a/WebCore/WebCore.gypi b/WebCore/WebCore.gypi
index da10582..13131a8 100644
--- a/WebCore/WebCore.gypi
+++ b/WebCore/WebCore.gypi
@@ -1883,6 +1883,8 @@
             'page/SecurityOriginHash.h',
             'page/Settings.cpp',
             'page/Settings.h',
+            'page/SpatialNavigation.h',
+            'page/SpatialNavigation.cpp',
             'page/UserContentURLPattern.cpp',
             'page/UserContentURLPattern.h',
             'page/UserScript.h',
diff --git a/WebCore/WebCore.pro b/WebCore/WebCore.pro
index 1d42e55..876d3eb 100644
--- a/WebCore/WebCore.pro
+++ b/WebCore/WebCore.pro
@@ -773,6 +773,7 @@ SOURCES += \
     page/SecurityOrigin.cpp \
     page/Screen.cpp \
     page/Settings.cpp \
+    page/SpatialNavigation.cpp \
     page/UserContentURLPattern.cpp \
     page/WindowFeatures.cpp \
     page/XSSAuditor.cpp \
@@ -1479,6 +1480,7 @@ HEADERS += \
     page/Screen.h \
     page/SecurityOrigin.h \
     page/Settings.h \
+    page/SpatialNavigation.h \
     page/WindowFeatures.h \
     page/WorkerNavigator.h \
     page/XSSAuditor.h \
diff --git a/WebCore/WebCore.vcproj/WebCore.vcproj b/WebCore/WebCore.vcproj/WebCore.vcproj
index 636cf99..bc16663 100644
--- a/WebCore/WebCore.vcproj/WebCore.vcproj
+++ b/WebCore/WebCore.vcproj/WebCore.vcproj
@@ -20973,6 +20973,14 @@
 				>
 			</File>
 			<File
+				RelativePath="..\page\SpatialNavigation.cpp"
+				>
+			</File>
+			<File
+				RelativePath="..\page\SpatialNavigation.h"
+				>
+			</File>
+			<File
 				RelativePath="..\page\UserContentURLPattern.cpp"
 				>
 			</File>
diff --git a/WebCore/page/EventHandler.cpp b/WebCore/page/EventHandler.cpp
index d8208a5..a5c93a7 100644
--- a/WebCore/page/EventHandler.cpp
+++ b/WebCore/page/EventHandler.cpp
@@ -2174,10 +2174,15 @@ void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event)
             return;
         if (event->keyIdentifier() == "U+0009")
             defaultTabEventHandler(event);
+        else {
+            FocusDirection direction = focusDirectionForKey(event->keyIdentifier());
+            if (direction != FocusDirectionNone)
+                defaultArrowEventHandler(direction, event);
+        }
 
-       // provides KB navigation and selection for enhanced accessibility users
-       if (AXObjectCache::accessibilityEnhancedUserInterfaceEnabled())
-           handleKeyboardSelectionMovement(event);       
+        // provides KB navigation and selection for enhanced accessibility users
+        if (AXObjectCache::accessibilityEnhancedUserInterfaceEnabled())
+            handleKeyboardSelectionMovement(event);
     }
     if (event->type() == eventNames().keypressEvent) {
         m_frame->editor()->handleKeyboardEvent(event);
@@ -2188,6 +2193,27 @@ void EventHandler::defaultKeyboardEventHandler(KeyboardEvent* event)
     }
 }
 
+FocusDirection EventHandler::focusDirectionForKey(const AtomicString& keyIdentifier) const
+{
+    DEFINE_STATIC_LOCAL(AtomicString, Down, ("Down"));
+    DEFINE_STATIC_LOCAL(AtomicString, Up, ("Up"));
+    DEFINE_STATIC_LOCAL(AtomicString, Left, ("Left"));
+    DEFINE_STATIC_LOCAL(AtomicString, Right, ("Right"));
+
+    FocusDirection retVal = FocusDirectionNone;
+
+    if (keyIdentifier == Down)
+        retVal = FocusDirectionDown;
+    else if (keyIdentifier == Up)
+        retVal = FocusDirectionUp;
+    else if (keyIdentifier == Left)
+        retVal = FocusDirectionLeft;
+    else if (keyIdentifier == Right)
+        retVal = FocusDirectionRight;
+
+    return retVal;
+}
+
 #if ENABLE(DRAG_SUPPORT)
 bool EventHandler::dragHysteresisExceeded(const FloatPoint& floatDragViewportLocation) const
 {
@@ -2482,6 +2508,27 @@ void EventHandler::defaultSpaceEventHandler(KeyboardEvent* event)
 
 #endif
 
+void EventHandler::defaultArrowEventHandler(FocusDirection focusDirection, KeyboardEvent* event)
+{
+    if (event->ctrlKey() || event->metaKey() || event->altGraphKey() || event->shiftKey())
+        return;
+
+    Page* page = m_frame->page();
+    if (!page)
+        return;
+
+    if (!page->settings() || !page->settings()->isSpatialNavigationEnabled())
+        return;
+
+    // Arrows and other possible directional navigation keys can be used in design
+    // mode editing.
+    if (m_frame->document()->inDesignMode())
+        return;
+
+    if (page->focusController()->advanceFocus(focusDirection, event))
+        event->setDefaultHandled();
+}
+
 void EventHandler::defaultTabEventHandler(KeyboardEvent* event)
 {
     // We should only advance focus on tabs if no special modifier keys are held down.
diff --git a/WebCore/page/EventHandler.h b/WebCore/page/EventHandler.h
index 2a29a17..b655db4 100644
--- a/WebCore/page/EventHandler.h
+++ b/WebCore/page/EventHandler.h
@@ -27,6 +27,7 @@
 #define EventHandler_h
 
 #include "DragActions.h"
+#include "FocusDirection.h"
 #include "PlatformMouseEvent.h"
 #include "ScrollTypes.h"
 #include "Timer.h"
@@ -308,6 +309,7 @@ private:
 
     void defaultSpaceEventHandler(KeyboardEvent*);
     void defaultTabEventHandler(KeyboardEvent*);
+    void defaultArrowEventHandler(FocusDirection, KeyboardEvent*);
 
 #if ENABLE(DRAG_SUPPORT)
     void allowDHTMLDrag(bool& flagDHTML, bool& flagUA) const;
@@ -330,6 +332,8 @@ private:
     
     void setFrameWasScrolledByUser();
 
+    FocusDirection focusDirectionForKey(const AtomicString&) const;
+
     bool capturesDragging() const { return m_capturesDragging; }
 
 #if PLATFORM(MAC) && defined(__OBJC__) && !ENABLE(EXPERIMENTAL_SINGLE_VIEW_MODE)
diff --git a/WebCore/page/FocusController.cpp b/WebCore/page/FocusController.cpp
index f94eaf9..5f4bb70 100644
--- a/WebCore/page/FocusController.cpp
+++ b/WebCore/page/FocusController.cpp
@@ -49,12 +49,14 @@
 #include "RenderWidget.h"
 #include "SelectionController.h"
 #include "Settings.h"
+#include "SpatialNavigation.h"
 #include "Widget.h"
 #include <wtf/Platform.h>
 
 namespace WebCore {
 
 using namespace HTMLNames;
+using namespace std;
 
 static inline void dispatchEventsOnWindowAndFocusedNode(Document* document, bool focused)
 {
@@ -160,6 +162,24 @@ bool FocusController::setInitialFocus(FocusDirection direction, KeyboardEvent* e
 
 bool FocusController::advanceFocus(FocusDirection direction, KeyboardEvent* event, bool initialFocus)
 {
+    switch (direction) {
+    case FocusDirectionForward:
+    case FocusDirectionBackward:
+        return advanceFocusInDocumentOrder(direction, event, initialFocus);
+    case FocusDirectionLeft:
+    case FocusDirectionRight:
+    case FocusDirectionUp:
+    case FocusDirectionDown:
+        return advanceFocusDirectionally(direction, event);
+    default:
+        ASSERT_NOT_REACHED();
+    }
+
+    return false;
+}
+
+bool FocusController::advanceFocusInDocumentOrder(FocusDirection direction, KeyboardEvent* event, bool initialFocus)
+{
     Frame* frame = focusedOrMainFrame();
     ASSERT(frame);
     Document* document = frame->document();
@@ -264,6 +284,137 @@ bool FocusController::advanceFocus(FocusDirection direction, KeyboardEvent* even
     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) ?
+                                       FocusDirectionForward : FocusDirectionBackward;
+        // '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(), 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);
+        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());
+
+    static_cast<Element*>(node)->focus(false);
+    return true;
+}
+
+void updateFocusCandidateIfCloser(Node* focusedNode, Node* candidate, long long distance, FocusCandidate& closestFocusCandidate)
+{
+    // Bail out if |distance| is bigger than the current closest candidate.
+    if (distance >= closestFocusCandidate.distance)
+        return;
+
+    // If |focusedNode| and |candidate| are in the same document AND
+    // current |closestFocusCandidadte| is not in an {i}frame that is
+    // preferable to get focused.
+    if (focusedNode->document() == candidate->document()
+        && distance < closestFocusCandidate.parentDistance) {
+        closestFocusCandidate.node = candidate;
+        closestFocusCandidate.distance = distance;
+        closestFocusCandidate.parentDistance = cMaxDistance;
+    } else if (focusedNode->document() != candidate->document()) {
+        // If the |focusedNode| is in an inner document and the |candidate| is
+        // in a different document, we only consider to change focus if there is
+        // not another already good focusable candidate in the same document as
+        // |focusedNode|.
+        if (!((isInRootDocument(candidate) && !isInRootDocument(focusedNode))
+            && closestFocusCandidate.node
+            && focusedNode->document() == closestFocusCandidate.node->document())) {
+            closestFocusCandidate.node = candidate;
+            closestFocusCandidate.distance = distance;
+        }
+    }
+}
+
+void FocusController::findFocusableNodeInDirection(Document* document, Node* focusedNode, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closestFocusCandidate)
+{
+    ASSERT(document);
+
+    // Walk all the child nodes and update focusCandidate if we find a nearer node.
+    for (Node* candidate = document->firstChild(); candidate; candidate = candidate->traverseNextNode()) {
+        // Inner documents case.
+        if (candidate->isFrameOwnerElement())
+            deepFindFocusableNodeInDirection(focusedNode, candidate, direction, event, closestFocusCandidate);
+        else if (candidate != focusedNode && candidate->isKeyboardFocusable(event)) {
+            long long distance = distanceInDirection(focusedNode, candidate,
+                                                     direction, closestFocusCandidate);
+            updateFocusCandidateIfCloser(focusedNode, candidate, distance, closestFocusCandidate);
+        }
+    }
+}
+
+void FocusController::deepFindFocusableNodeInDirection(Node* focusedNode, Node* candidate, FocusDirection direction, KeyboardEvent* event, FocusCandidate& closestFocusCandidate)
+{
+    HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(candidate);
+    if (!owner->contentFrame())
+        return;
+
+    Document* innerDocument = owner->contentFrame()->document();
+    if (!innerDocument)
+        return;
+
+    if (innerDocument == focusedNode->document())
+        findFocusableNodeInDirection(innerDocument, focusedNode, direction, event, closestFocusCandidate);
+    else {
+        // Check if the current {i}frame element itself is a good candidate
+        // to move focus to. If it is, then we traverse its inner nodes.
+        // Lets pass a copy of the best candidate, to not get fooled by a
+        // frame without focusable elements.
+        FocusCandidate focusCandidateCopy = closestFocusCandidate;
+        long long distance = distanceInDirection(focusedNode, candidate, direction, focusCandidateCopy);
+        if (distance < focusCandidateCopy.distance) {
+            focusCandidateCopy.parentAlignment = focusCandidateCopy.alignment;
+            focusCandidateCopy.parentDistance = distance;
+
+            findFocusableNodeInDirection(innerDocument, focusedNode, direction, event, focusCandidateCopy);
+
+            // If we really have an inner closer focus candidate node, take it.
+            if (closestFocusCandidate.node != focusCandidateCopy.node)
+                closestFocusCandidate = focusCandidateCopy;
+        }
+    }
+}
+
 static bool relinquishesEditingFocus(Node *node)
 {
     ASSERT(node);
diff --git a/WebCore/page/FocusController.h b/WebCore/page/FocusController.h
index 32d4060..75591c7 100644
--- a/WebCore/page/FocusController.h
+++ b/WebCore/page/FocusController.h
@@ -33,10 +33,12 @@
 
 namespace WebCore {
 
+class Document;
 class Frame;
 class KeyboardEvent;
 class Node;
 class Page;
+struct FocusCandidate;
 
 class FocusController : public Noncopyable {
 public:
@@ -58,6 +60,12 @@ public:
     bool isFocused() const { return m_isFocused; }
 
 private:
+    bool advanceFocusDirectionally(FocusDirection, KeyboardEvent*);
+    bool advanceFocusInDocumentOrder(FocusDirection, KeyboardEvent*, bool initialFocus);
+
+    void findFocusableNodeInDirection(Document*, Node*, FocusDirection, KeyboardEvent*, FocusCandidate&);
+    void deepFindFocusableNodeInDirection(Node*, Node*, FocusDirection, KeyboardEvent*, FocusCandidate&);
+
     Page* m_page;
     RefPtr<Frame> m_focusedFrame;
     bool m_isActive;
diff --git a/WebCore/page/FocusDirection.h b/WebCore/page/FocusDirection.h
index 261c745..8a6d51f 100644
--- a/WebCore/page/FocusDirection.h
+++ b/WebCore/page/FocusDirection.h
@@ -28,8 +28,13 @@
 
 namespace WebCore {
     enum FocusDirection {
-        FocusDirectionForward = 0,
-        FocusDirectionBackward
+        FocusDirectionNone = 0,
+        FocusDirectionForward,
+        FocusDirectionBackward,
+        FocusDirectionUp,
+        FocusDirectionDown,
+        FocusDirectionLeft,
+        FocusDirectionRight
     };
 }
 
diff --git a/WebCore/page/Settings.cpp b/WebCore/page/Settings.cpp
index 8f19403..f18696d 100644
--- a/WebCore/page/Settings.cpp
+++ b/WebCore/page/Settings.cpp
@@ -66,6 +66,7 @@ Settings::Settings(Page* page)
     , m_localStorageQuota(5 * 1024 * 1024)  // Suggested by the HTML5 spec.
     , m_pluginAllowedRunTime(numeric_limits<unsigned>::max())
     , m_zoomMode(ZoomPage)
+    , m_isSpatialNavigationEnabled(false)
     , m_isJavaEnabled(false)
     , m_loadsImagesAutomatically(false)
     , m_privateBrowsingEnabled(false)
@@ -246,6 +247,11 @@ void Settings::setAllowFileAccessFromFileURLs(bool allowFileAccessFromFileURLs)
     m_allowFileAccessFromFileURLs = allowFileAccessFromFileURLs;
 }
 
+void Settings::setSpatialNavigationEnabled(bool isSpatialNavigationEnabled)
+{
+    m_isSpatialNavigationEnabled = isSpatialNavigationEnabled;
+}
+
 void Settings::setJavaEnabled(bool isJavaEnabled)
 {
     m_isJavaEnabled = isJavaEnabled;
diff --git a/WebCore/page/Settings.h b/WebCore/page/Settings.h
index ab6c1d4..b99e089 100644
--- a/WebCore/page/Settings.h
+++ b/WebCore/page/Settings.h
@@ -119,6 +119,9 @@ namespace WebCore {
         void setJavaScriptCanOpenWindowsAutomatically(bool);
         bool javaScriptCanOpenWindowsAutomatically() const { return m_javaScriptCanOpenWindowsAutomatically; }
 
+        void setSpatialNavigationEnabled(bool);
+        bool isSpatialNavigationEnabled() const { return m_isSpatialNavigationEnabled; }
+
         void setJavaEnabled(bool);
         bool isJavaEnabled() const { return m_isJavaEnabled; }
 
@@ -311,6 +314,7 @@ namespace WebCore {
         unsigned m_localStorageQuota;
         unsigned m_pluginAllowedRunTime;
         ZoomMode m_zoomMode;
+        bool m_isSpatialNavigationEnabled : 1;
         bool m_isJavaEnabled : 1;
         bool m_loadsImagesAutomatically : 1;
         bool m_privateBrowsingEnabled : 1;
diff --git a/WebCore/page/SpatialNavigation.cpp b/WebCore/page/SpatialNavigation.cpp
new file mode 100644
index 0000000..26cd1d1
--- /dev/null
+++ b/WebCore/page/SpatialNavigation.cpp
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
+ * Copyright (C) 2009 Antonio Gomes <tonikitoo at webkit.org>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SpatialNavigation.h"
+
+#include "Frame.h"
+#include "FrameTree.h"
+#include "FrameView.h"
+#include "HTMLFrameOwnerElement.h"
+#include "IntRect.h"
+#include "Node.h"
+#include "Page.h"
+
+namespace WebCore {
+
+static long long spatialDistance(FocusDirection, const IntRect&, const IntRect&);
+static IntRect renderRectRelativeToRootDocument(RenderObject*);
+static RectsAlignment alignmentForRects(FocusDirection, const IntRect&, const IntRect&);
+static bool areRectsFullyAligned(FocusDirection, const IntRect&, const IntRect&);
+static bool areRectsPartiallyAligned(FocusDirection, const IntRect&, const IntRect&);
+static bool isRectInDirection(FocusDirection, const IntRect&, const IntRect&);
+static void deflateIfOverlapped(IntRect&, IntRect&);
+
+long long distanceInDirection(Node* start, Node* dest, FocusDirection direction, FocusCandidate& candidate)
+{
+    RenderObject* startRender = start->renderer();
+    if (!startRender)
+        return cMaxDistance;
+
+    RenderObject* destRender = dest->renderer();
+    if (!destRender)
+        return cMaxDistance;
+
+    IntRect curRect = renderRectRelativeToRootDocument(startRender);
+    IntRect targetRect  = renderRectRelativeToRootDocument(destRender);
+
+    // The bounding rectangle of two consecutive nodes can overlap. In such cases,
+    // deflate both.
+    deflateIfOverlapped(curRect, targetRect);
+
+    if ((curRect.isEmpty() || targetRect.isEmpty())
+        || (targetRect.x() < 0 || targetRect.y() < 0))
+        return cMaxDistance;
+
+    if (!isRectInDirection(direction, curRect, targetRect))
+        return cMaxDistance;
+
+    // The distance between two nodes is not to be considered alone when evaluating/looking
+    // for the best focus candidate node. Alignment of rects can be also a good point to be
+    // considered in order to make the algorithm to behavior in a more intuitive way.
+    RectsAlignment alignment = alignmentForRects(direction, curRect, targetRect);
+
+    bool sameDocument = candidate.node && dest->document() == candidate.node->document();
+    if (sameDocument) {
+        if (candidate.alignment > alignment || (candidate.parentAlignment && alignment > candidate.parentAlignment))
+            return cMaxDistance;
+    } else if (candidate.alignment > alignment && (candidate.parentAlignment && alignment > candidate.parentAlignment))
+        return cMaxDistance;
+
+    // FIXME_tonikitoo: simplify the logic here !
+    if (alignment != None
+        || (candidate.node && candidate.parentAlignment >= alignment
+        && (dest->document() == candidate.node->document())
+        && alignment > None)) {
+
+        // If we are now in an higher precedent case, lets reset the current |candidate|'s
+        // |distance| so we force it to be bigger than the result we will get from
+        // |spatialDistance| (see below).
+        if (candidate.alignment < alignment && candidate.parentAlignment < alignment)
+            candidate.distance = cMaxDistance;
+
+        candidate.alignment = alignment;
+    }
+
+    return spatialDistance(direction, curRect, targetRect);
+}
+
+// FIXME: This function does not behave correctly with transformed frames.
+static IntRect renderRectRelativeToRootDocument(RenderObject* render)
+{
+    ASSERT(render);
+
+    IntRect rect(render->absoluteClippedOverflowRect());
+
+    // In cases when the |render|'s associated node is in a scrollable inner
+    // document, we only consider its scrollOffset if it is not offscreen.
+    Node* node = render->node();
+    Document* mainDocument = node->document()->page()->mainFrame()->document();
+    bool considerScrollOffset = !(hasOffscreenRect(node) && node->document() != mainDocument);
+
+    if (considerScrollOffset) {
+        if (FrameView* frameView = render->node()->document()->view())
+            rect.move(-frameView->scrollOffset());
+    }
+
+    // Handle nested frames.
+    for (Frame* frame = render->document()->frame(); frame; frame = frame->tree()->parent()) {
+        if (HTMLFrameOwnerElement* ownerElement = frame->ownerElement())
+            rect.move(ownerElement->offsetLeft(), ownerElement->offsetTop());
+    }
+
+    return rect;
+}
+
+static RectsAlignment alignmentForRects(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
+{
+    if (areRectsFullyAligned(direction, curRect, targetRect))
+        return Full;
+
+    if (areRectsPartiallyAligned(direction, curRect, targetRect))
+        return Partial;
+
+    return None;
+}
+
+static inline bool isHorizontalMove(FocusDirection direction)
+{
+    return direction == FocusDirectionLeft || direction == FocusDirectionRight;
+}
+
+static inline int start(FocusDirection direction, const IntRect& rect)
+{
+    return isHorizontalMove(direction) ? rect.y() : rect.x();
+}
+
+static inline int middle(FocusDirection direction, const IntRect& rect)
+{
+    IntPoint center(rect.center());
+    return isHorizontalMove(direction) ? center.y(): center.x();
+}
+
+static inline int end(FocusDirection direction, const IntRect& rect)
+{
+    return isHorizontalMove(direction) ? rect.bottom() : rect.right();
+}
+
+// This method checks if rects |a| and |b| are fully aligned either vertically or
+// horizontally. In general, rects whose central point falls between the top or
+// bottom of each other are considered fully aligned.
+// Rects that match this criteria are preferable target nodes in move focus changing
+// operations.
+// * a = Current focused node's rect.
+// * b = Focus candidate node's rect.
+static bool areRectsFullyAligned(FocusDirection direction, const IntRect& a, const IntRect& b)
+{
+    int aStart, bStart, aEnd, bEnd;
+
+    switch (direction) {
+    case FocusDirectionLeft:
+        aStart = a.x();
+        bEnd = b.right();
+        break;
+    case FocusDirectionRight:
+        aStart = b.x();
+        bEnd = a.right();
+        break;
+    case FocusDirectionUp:
+        aStart = a.y();
+        bEnd = b.y();
+        break;
+    case FocusDirectionDown:
+        aStart = b.y();
+        bEnd = a.y();
+        break;
+    default:
+        ASSERT_NOT_REACHED();
+        return false;
+    }
+
+    if (aStart < bEnd)
+        return false;
+
+    aStart = start(direction, a);
+    bStart = start(direction, b);
+
+    int aMiddle = middle(direction, a);
+    int bMiddle = middle(direction, b);
+
+    aEnd = end(direction, a);
+    bEnd = end(direction, b);
+
+    // Picture of the totally aligned logic:
+    //
+    //     Horizontal    Vertical        Horizontal     Vertical
+    //  ****************************  *****************************
+    //  *  _          *   _ _ _ _  *  *         _   *      _ _    *
+    //  * |_|     _   *  |_|_|_|_| *  *  _     |_|  *     |_|_|   *
+    //  * |_|....|_|  *      .     *  * |_|....|_|  *       .     *
+    //  * |_|    |_| (1)     .     *  * |_|    |_| (2)      .     *
+    //  * |_|         *     _._    *  *        |_|  *    _ _._ _  *
+    //  *             *    |_|_|   *  *             *   |_|_|_|_| *
+    //  *             *            *  *             *             *
+    //  ****************************  *****************************
+
+    //     Horizontal    Vertical        Horizontal     Vertical
+    //  ****************************  *****************************
+    //  *  _......_   *   _ _ _ _  *  *  _          *    _ _ _ _  *
+    //  * |_|    |_|  *  |_|_|_|_| *  * |_|     _   *   |_|_|_|_| *
+    //  * |_|    |_|  *  .         *  * |_|    |_|  *           . *
+    //  * |_|        (3) .         *  * |_|....|_| (4)          . *
+    //  *             *  ._ _      *  *             *        _ _. *
+    //  *             *  |_|_|     *  *             *       |_|_| *
+    //  *             *            *  *             *             *
+    //  ****************************  *****************************
+
+    return ((bMiddle >= aStart && bMiddle <= aEnd) // (1)
+            || (aMiddle >= bStart && aMiddle <= bEnd) // (2)
+            || (bStart == aStart) // (3)
+            || (bEnd == aEnd)); // (4)
+}
+
+// This method checks if |start| and |dest| have a partial intersection, either
+// horizontally or vertically.
+// * a = Current focused node's rect.
+// * b = Focus candidate node's rect.
+static bool areRectsPartiallyAligned(FocusDirection direction, const IntRect& a, const IntRect& b)
+{
+    int aStart  = start(direction, a);
+    int bStart  = start(direction, b);
+    int bMiddle = middle(direction, b);
+    int aEnd = end(direction, a);
+    int bEnd = end(direction, b);
+
+    // Picture of the partially aligned logic:
+    //
+    //    Horizontal       Vertical
+    // ********************************
+    // *  _            *   _ _ _      *
+    // * |_|           *  |_|_|_|     *
+    // * |_|.... _     *      . .     *
+    // * |_|    |_|    *      . .     *
+    // * |_|....|_|    *      ._._ _  *
+    // *        |_|    *      |_|_|_| *
+    // *        |_|    *              *
+    // *               *              *
+    // ********************************
+    //
+    // ... and variants of the above cases.
+    return ((bStart >= aStart && bStart <= aEnd)
+            || (bStart >= aStart && bStart <= aEnd)
+            || (bEnd >= aStart && bEnd <= aEnd)
+            || (bMiddle >= aStart && bMiddle <= aEnd)
+            || (bEnd >= aStart && bEnd <= aEnd));
+}
+
+// Return true if rect |a| is below |b|. False otherwise.
+static inline bool below(const IntRect& a, const IntRect& b)
+{
+    return a.y() > b.bottom();
+}
+
+// Return true if rect |a| is on the right of |b|. False otherwise.
+static inline bool rightOf(const IntRect& a, const IntRect& b)
+{
+    return a.x() > b.right();
+}
+
+// * a = Current focused node's rect.
+// * b = Focus candidate node's rect.
+static long long spatialDistance(FocusDirection direction, const IntRect& a, const IntRect& b)
+{
+    int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+
+    if (direction == FocusDirectionLeft) {
+        // #1  |--|
+        //
+        // #2  |--|  |--|
+        //
+        // #3  |--|
+
+        x1 = a.x();
+        x2 = b.right();
+
+        if (below(a, b)) {
+            // #1 The a rect is below b.
+            y1 = a.y();
+            y2 = b.bottom();
+        } else if (below(b, a)) {
+            // #3 The b rect is below a.
+            y1 = a.bottom();
+            y2 = b.y();
+        } else {
+            // #2 Both b and a share some common y's.
+            y1 = 0;
+            y2 = 0;
+        }
+    } else if (direction == FocusDirectionRight) {
+        //        |--|  #1
+        //
+        //  |--|  |--|  #2
+        //
+        //        |--|  #3
+
+        x1 = a.right();
+        x2 = b.x();
+
+        if (below(a, b)) {
+            // #1 The b rect is above a.
+            y1 = a.y();
+            y2 = b.bottom();
+        } else if (below(b, a)) {
+            // #3 The b rect is below a.
+            y1 = a.bottom();
+            y2 = b.y();
+        } else {
+            // #2 Both b and a share some common y's.
+            y1 = 0;
+            y2 = 0;
+        }
+    } else if (direction == FocusDirectionUp) {
+        //
+        //   #1    #2    #3
+        //
+        //  |--|  |--|  |--|
+        //
+        //        |--|
+
+        y1 = a.y();
+        y2 = b.bottom();
+
+        if (rightOf(a, b)) {
+            // #1 The b rect is to the left of a.
+            x1 = a.x();
+            x2 = b.right();
+        } else if (rightOf(b, a)) {
+            // #3 The b rect is to the right of a.
+            x1 = a.right();
+            x2 = b.x();
+        } else {
+            // #2 Both b and a share some common x's.
+            x1 = 0;
+            x2 = 0;
+        }
+    } else if (direction == FocusDirectionDown) {
+        //        |--|
+        //
+        //  |--|  |--|  |--|
+        //
+        //   #1    #2    #3
+
+        y1 = a.bottom();
+        y2 = b.y();
+
+        if (rightOf(a, b)) {
+            // #1 The b rect is to the left of a.
+            x1 = a.x();
+            x2 = b.right();
+        } else if (rightOf(b, a)) {
+            // #3 The b rect is to the right of a
+            x1 = a.right();
+            x2 = b.x();
+        } else {
+            // #2 Both b and a share some common x's.
+            x1 = 0;
+            x2 = 0;
+        }
+    }
+
+    long long distance = pow((x1 - x2), 2) + pow((y1 - y2), 2);
+    return abs(distance);
+}
+
+static bool isRectInDirection(FocusDirection direction, const IntRect& curRect, const IntRect& targetRect)
+{
+    IntPoint center(targetRect.center());
+    int targetMiddle = isHorizontalMove(direction) ? center.x() : center.y();
+
+    switch (direction) {
+    case FocusDirectionLeft:
+        return targetMiddle < curRect.x();
+    case FocusDirectionRight:
+        return targetMiddle > curRect.right();
+    case FocusDirectionUp:
+        return targetMiddle < curRect.y();
+    case FocusDirectionDown:
+        return targetMiddle > curRect.bottom();
+    default:
+        ASSERT_NOT_REACHED();
+    }
+
+    return false;
+}
+
+// Checks if |node| is offscreen the visible area (viewport) of its container
+// document. In case it is, one can scroll in direction or take any different
+// desired action later on.
+bool hasOffscreenRect(Node* node)
+{
+    // Get the FrameView in which |node| is (which means the current viewport if |node|
+    // is not in an inner document), so we can check if its content rect is visible
+    // before we actually move the focus to it.
+    FrameView* frameView = node->document()->view();
+    if (!frameView)
+        return true;
+
+    IntRect containerViewportRect = frameView->visibleContentRect();
+
+    RenderObject* render = node->renderer();
+    if (!render)
+        return true;
+
+    IntRect rect(render->absoluteClippedOverflowRect());
+    return !containerViewportRect.intersects(rect);
+}
+
+// In a bottom-up way, this method tries to scroll |frame| in a given direction
+// |direction|, going up in the frame tree hierarchy in case it does not succeed.
+bool scrollInDirection(Frame* frame, FocusDirection direction)
+{
+    if (!frame)
+        return false;
+
+    ScrollDirection scrollDirection;
+
+    switch (direction) {
+    case FocusDirectionLeft:
+        scrollDirection = ScrollLeft;
+        break;
+    case FocusDirectionRight:
+        scrollDirection = ScrollRight;
+        break;
+    case FocusDirectionUp:
+        scrollDirection = ScrollUp;
+        break;
+    case FocusDirectionDown:
+        scrollDirection = ScrollDown;
+        break;
+    default:
+        return false;
+    }
+
+    return frame->eventHandler()->scrollRecursively(scrollDirection, ScrollByLine);
+}
+
+bool isInRootDocument(Node* node)
+{
+    if (!node)
+        return false;
+
+    Document* rootDocument = node->document()->page()->mainFrame()->document();
+    return node->document() == rootDocument;
+}
+
+static void deflateIfOverlapped(IntRect& a, IntRect& b)
+{
+    if (!a.intersects(b) || a.contains(b) || b.contains(a))
+        return;
+
+    static const int fudgeFactor = -2;
+
+    // Avoid negative width or height values.
+    if (fudgeFactor > 0 || ((a.width() + 2 * fudgeFactor > 0) && (a.height() + 2 * fudgeFactor > 0)))
+        a.inflate(fudgeFactor);
+
+    if (fudgeFactor > 0 || ((b.width() + 2 * fudgeFactor > 0) && (b.height() + 2 * fudgeFactor > 0)))
+        b.inflate(fudgeFactor);
+}
+
+} // namespace WebCore
diff --git a/WebCore/page/SpatialNavigation.h b/WebCore/page/SpatialNavigation.h
new file mode 100644
index 0000000..6b65de4
--- /dev/null
+++ b/WebCore/page/SpatialNavigation.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
+ * Copyright (C) 2009 Antonio Gomes <tonikitoo at webkit.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef SpatialNavigation_h
+#define SpatialNavigation_h
+
+#include "FocusDirection.h"
+
+#include <limits>
+
+namespace WebCore {
+
+class Frame;
+class IntRect;
+class Node;
+class RenderObject;
+
+using namespace std;
+
+const long long cMaxDistance = numeric_limits<long long>::max();
+
+// Spatially speaking, two given elements in a web page can be:
+// 1) Fully aligned: There is a full intersection between the rects, either
+//    vertically or horizontally.
+//
+// * Horizontally       * Vertically
+//    _
+//   |_|                   _ _ _ _ _ _
+//   |_|...... _          |_|_|_|_|_|_|
+//   |_|      |_|         .       .
+//   |_|......|_|   OR    .       .
+//   |_|      |_|         .       .
+//   |_|......|_|          _ _ _ _
+//   |_|                  |_|_|_|_|
+//
+//
+// 2) Partially aligned: There is a partial intersection between the rects, either
+//    vertically or horizontally.
+//
+// * Horizontally       * Vertically
+//    _                   _ _ _ _ _
+//   |_|                 |_|_|_|_|_|
+//   |_|.... _      OR           . .
+//   |_|    |_|                  . .
+//   |_|....|_|                  ._._ _
+//          |_|                  |_|_|_|
+//          |_|
+//
+// 3) Or, otherwise, not aligned at all.
+//
+// * Horizontally       * Vertically
+//         _              _ _ _ _
+//        |_|            |_|_|_|_|
+//        |_|                    .
+//        |_|                     .
+//       .          OR             .
+//    _ .                           ._ _ _ _ _
+//   |_|                            |_|_|_|_|_|
+//   |_|
+//   |_|
+//
+// "Totally Aligned" elements are preferable candidates to move
+// focus to over "Partially Aligned" ones, that on its turns are
+// more preferable than "Not Aligned".
+enum RectsAlignment {
+    None = 0,
+    Partial,
+    Full
+};
+
+struct FocusCandidate {
+    FocusCandidate()
+        : node(0)
+        , distance(cMaxDistance)
+        , parentDistance(cMaxDistance)
+        , alignment(None)
+        , parentAlignment(None)
+    {
+    }
+    Node* node;
+    long long distance;
+    long long parentDistance;
+    RectsAlignment alignment;
+    RectsAlignment parentAlignment;
+};
+
+long long distanceInDirection(Node*, Node*, FocusDirection, FocusCandidate&);
+bool scrollInDirection(Frame*, FocusDirection);
+bool hasOffscreenRect(Node*);
+bool isInRootDocument(Node*);
+
+} // namspace WebCore
+
+#endif // SpatialNavigation_h

-- 
WebKit Debian packaging



More information about the Pkg-webkit-commits mailing list