[SCM] WebKit Debian packaging branch, webkit-1.1, updated. upstream/1.1.22-985-g3c00f00
tonikitoo at webkit.org
tonikitoo at webkit.org
Wed Mar 17 18:18:06 UTC 2010
The following commit has been merged in the webkit-1.1 branch:
commit f17dd9dba69590b48e9962f6805dc895fac6a0dc
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