Diff
Modified: trunk/LayoutTests/ChangeLog (88420 => 88421)
--- trunk/LayoutTests/ChangeLog 2011-06-09 04:56:37 UTC (rev 88420)
+++ trunk/LayoutTests/ChangeLog 2011-06-09 05:33:12 UTC (rev 88421)
@@ -1,5 +1,26 @@
2011-06-08 Hayato Ito <hay...@chromium.org>
+ Reviewed by Dimitri Glazkov.
+
+ A forward/backward tab traversal now visits focusable elements in a shadow root.
+ https://bugs.webkit.org/show_bug.cgi?id=61410
+
+ Like a iframe element, a shadow host becomes a scope of
+ tabindex. That means all descendant elements in a shadow root are
+ skipped if the host node of the shadow root is not focusable.
+
+ The patch doesn't affect HTMLInputElement and HTMLTextAreaElement,
+ which uses a shadow root and do extra works in their focus()
+ method.
+
+ A shadow root's <content> is not considered in this patch.
+ That will be addressed in a following patch.
+
+ * fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt: Added.
+ * fast/dom/shadow/tab-order-iframe-and-shadow.html: Added.
+
+2011-06-08 Hayato Ito <hay...@chromium.org>
+
Reviewed by Hajime Morita.
Makes sure that document.activeElement won't be an element in shadow root.
Added: trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt (0 => 88421)
--- trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt 2011-06-09 05:33:12 UTC (rev 88421)
@@ -0,0 +1,34 @@
+This tests that pressing Tab key should traverse into iframe and shadow tree, and pressing Shift-Tab should reverse the order. Makes sure that a shadow host element should act like a iframe element.
+
+Focus input-01.
+id:input-01(tabIndex=1) is focused.
+
+Press Tab 11 times.
+id:input-13(tabIndex=1) is focused.
+id:input-15(tabIndex=2) is focused.
+id:input-02(tabIndex=0) is focused.
+id:input-04(tabIndex=0) is focused.
+id:iframe-input-06(tabIndex=1) is focused.
+id:iframe-shadow-input-09(tabIndex=1) is focused.
+id:iframe-shadow-input-08(tabIndex=0) is focused.
+id:iframe-input-12(tabIndex=1) is focused.
+id:iframe-input-11(tabIndex=2) is focused.
+id:iframe-input-05(tabIndex=0) is focused.
+id:input-14(tabIndex=0) is focused.
+
+Press Shift-Tab 11 times.
+id:iframe-input-05(tabIndex=0) is focused.
+id:iframe-input-11(tabIndex=2) is focused.
+id:iframe-input-12(tabIndex=1) is focused.
+id:iframe-shadow-input-08(tabIndex=0) is focused.
+id:iframe-shadow-input-09(tabIndex=1) is focused.
+id:iframe-input-06(tabIndex=1) is focused.
+id:input-04(tabIndex=0) is focused.
+id:input-02(tabIndex=0) is focused.
+id:input-15(tabIndex=2) is focused.
+id:input-13(tabIndex=1) is focused.
+id:input-01(tabIndex=1) is focused.
+
+Test finished.
+
+
Property changes on: trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt
___________________________________________________________________
Added: svn:eol-style
Added: trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow.html (0 => 88421)
--- trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow.html (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow.html 2011-06-09 05:33:12 UTC (rev 88421)
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+<body>
+<p>This tests that pressing Tab key should traverse into iframe and shadow tree, and pressing Shift-Tab should reverse the order.
+ Makes sure that a shadow host element should act like a iframe element.</p>
+<pre id="console"></pre>
+<script>
+function log(msg) {
+ document.querySelector('#console').textContent += (msg + '\n');
+}
+
+function description(element) {
+ var msg = '';
+ if (element.id) {
+ msg += 'id:' + element.id;
+ }
+ msg += '(tabIndex=' + element.tabIndex + ')';
+ return msg;
+}
+
+function onFocus(event) {
+ log(description(event.target) + ' is focused.');
+}
+
+function addFocusEventListener(element) {
+ element.addEventListener('focus', onFocus, false);
+}
+
+function createTextInputElement(doc, id, tabIndex) {
+ var input = doc.createElement('input');
+ input.type = 'text';
+ input.id = id;
+ input.tabIndex = tabIndex;
+ addFocusEventListener(input);
+ return input;
+}
+
+if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ var doc = document;
+
+ doc.body.appendChild(createTextInputElement(doc, 'input-01', 1));
+ doc.body.appendChild(createTextInputElement(doc, 'input-02', 0));
+
+ function addShadowHost(doc) {
+ var shadowHost = doc.createElement('p');
+ shadowHost.tabIndex = -1; // This shadow host (and a shadow tree in that) should be skipped.
+ var shadow = layoutTestController.ensureShadowRoot(shadowHost);
+ doc.body.appendChild(shadowHost);
+ shadow.appendChild(createTextInputElement(doc, 'shadow-input-03', 0));
+ }
+ addShadowHost(doc);
+
+ doc.body.appendChild(createTextInputElement(doc, 'input-04', 0));
+
+ function addIframe(doc) {
+ var iframe = doc.createElement('iframe');
+ doc.body.appendChild(iframe);
+ doc = iframe.contentDocument;
+
+ doc.body.appendChild(createTextInputElement(doc, 'iframe-input-05', 0));
+ doc.body.appendChild(createTextInputElement(doc, 'iframe-input-06', 1));
+ doc.body.appendChild(createTextInputElement(doc, 'iframe-input-07', -1));
+
+ function addShadowHost(doc) {
+ var shadowHost = doc.createElement('p');
+ shadowHost.tabIndex = 1;
+ var shadow = layoutTestController.ensureShadowRoot(shadowHost);
+ doc.body.appendChild(shadowHost);
+
+ shadow.appendChild(createTextInputElement(doc, 'iframe-shadow-input-08', 0));
+ shadow.appendChild(createTextInputElement(doc, 'iframe-shadow-input-09', 1));
+ shadow.appendChild(createTextInputElement(doc, 'iframe-shadow-input-10', -1));
+ }
+ addShadowHost(doc);
+
+ doc.body.appendChild(createTextInputElement(doc, 'iframe-input-11', 2));
+ doc.body.appendChild(createTextInputElement(doc, 'iframe-input-12', 1));
+ }
+ addIframe(doc);
+
+ doc.body.appendChild(createTextInputElement(doc, 'input-13', 1));
+ doc.body.appendChild(createTextInputElement(doc, 'input-14', 0));
+ doc.body.appendChild(createTextInputElement(doc, 'input-15', 2));
+
+ log('Focus input-01.');
+ doc.getElementById('input-01').focus();
+
+ if (window.eventSender) {
+ var pressed = 11;
+ log('\nPress Tab ' + pressed + ' times.');
+ for (var i = 0; i < pressed; ++i) {
+ eventSender.keyDown('\t');
+ }
+ log('\nPress Shift-Tab ' + pressed + ' times.');
+ for (var i = 0; i < pressed; ++i) {
+ eventSender.keyDown('\t', ['shiftKey']);
+ }
+ }
+ log('\nTest finished.');
+}
+</script>
+</body>
+</html>
Property changes on: trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow.html
___________________________________________________________________
Added: svn:eol-style
Modified: trunk/Source/WebCore/ChangeLog (88420 => 88421)
--- trunk/Source/WebCore/ChangeLog 2011-06-09 04:56:37 UTC (rev 88420)
+++ trunk/Source/WebCore/ChangeLog 2011-06-09 05:33:12 UTC (rev 88421)
@@ -1,5 +1,37 @@
2011-06-08 Hayato Ito <hay...@chromium.org>
+ Reviewed by Dimitri Glazkov.
+
+ A forward/backward tab traversal now visits focusable elements in a shadow root.
+ https://bugs.webkit.org/show_bug.cgi?id=61410
+
+ Test: fast/dom/shadow/tab-order-iframe-and-shadow.html
+
+ Like a iframe element, a shadow host becomes a scope of
+ tabindex. That means all descendant elements in a shadow root are
+ skipped if the host node of the shadow root is not focusable.
+
+ The patch doesn't affect HTMLInputElement and HTMLTextAreaElement,
+ which uses a shadow root and do extra works in their focus()
+ method.
+
+ A shadow root's <content> is not considered in this patch.
+ That will be addressed in a following patch.
+
+ * page/FocusController.cpp:
+ (WebCore::shadowRoot):
+ (WebCore::isTreeScopeOwner):
+ (WebCore::FocusController::deepFocusableNode):
+ (WebCore::FocusController::advanceFocusInDocumentOrder):
+ (WebCore::FocusController::findFocusableNodeAcrossTreeScope):
+ (WebCore::FocusController::findFocusableNode):
+ (WebCore::FocusController::nextFocusableNode):
+ (WebCore::FocusController::previousFocusableNode):
+ (WebCore::FocusController::ownerOfTreeScope):
+ * page/FocusController.h:
+
+2011-06-08 Hayato Ito <hay...@chromium.org>
+
Reviewed by Hajime Morita.
Makes sure that document.activeElement won't be an element in shadow root.
Modified: trunk/Source/WebCore/page/FocusController.cpp (88420 => 88421)
--- trunk/Source/WebCore/page/FocusController.cpp 2011-06-09 04:56:37 UTC (rev 88420)
+++ trunk/Source/WebCore/page/FocusController.cpp 2011-06-09 05:33:12 UTC (rev 88421)
@@ -53,6 +53,7 @@
#include "RenderWidget.h"
#include "ScrollAnimator.h"
#include "Settings.h"
+#include "ShadowRoot.h"
#include "SpatialNavigation.h"
#include "Widget.h"
#include "htmlediting.h" // For firstPositionInOrBeforeNode
@@ -145,27 +146,42 @@
}
}
+inline static ShadowRoot* shadowRoot(Node* node)
+{
+ return node->isElementNode() ? toElement(node)->shadowRoot() : 0;
+}
+
+inline static bool isTreeScopeOwner(Node* node)
+{
+ return node && (node->isFrameOwnerElement() || shadowRoot(node));
+}
+
Node* FocusController::deepFocusableNode(FocusDirection direction, Node* node, KeyboardEvent* event)
{
- // The node we found might be a HTMLFrameOwnerElement, so descend down the frame tree until we find either:
+ // The node we found might be a HTMLFrameOwnerElement or a shadow host, so descend down the tree until we find either:
// 1) a focusable node, or
- // 2) the deepest-nested HTMLFrameOwnerElement
- while (node && node->isFrameOwnerElement()) {
- HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(node);
- if (!owner->contentFrame())
+ // 2) the deepest-nested HTMLFrameOwnerElement or shadow host.
+ while (isTreeScopeOwner(node)) {
+ Node* foundNode;
+ if (node->isFrameOwnerElement()) {
+ HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(node);
+ if (!owner->contentFrame())
+ break;
+ Document* document = owner->contentFrame()->document();
+ foundNode = findFocusableNode(direction, document, 0, event);
+ } else {
+ ASSERT(shadowRoot(node));
+ // FIXME: Some elements (e.g. HTMLInputElement and HTMLTextAreaElement) do extra work in their focus() methods.
+ // Skipping these elements is the safest fix until we find a better way.
+ if (node->hasTagName(inputTag) || node->hasTagName(textareaTag))
+ break;
+ foundNode = findFocusableNode(direction, shadowRoot(node), 0, event);
+ }
+ if (!foundNode)
break;
-
- Document* document = owner->contentFrame()->document();
-
- node = (direction == FocusDirectionForward)
- ? nextFocusableNode(document, 0, event)
- : previousFocusableNode(document, 0, event);
- if (!node) {
- node = owner;
- break;
- }
+ ASSERT(node != foundNode);
+ node = foundNode;
}
-
return node;
}
@@ -215,31 +231,8 @@
document->updateLayoutIgnorePendingStylesheets();
- Node* node = (direction == FocusDirectionForward)
- ? nextFocusableNode(document, currentNode, event)
- : previousFocusableNode(document, currentNode, event);
-
- // If there's no focusable node to advance to, move up the frame tree until we find one.
- while (!node && frame) {
- Frame* parentFrame = frame->tree()->parent();
- if (!parentFrame)
- break;
+ Node* node = findFocusableNodeAcrossTreeScope(direction, currentNode ? currentNode->treeScope() : document, currentNode, event);
- Document* parentDocument = parentFrame->document();
-
- HTMLFrameOwnerElement* owner = frame->ownerElement();
- if (!owner)
- break;
-
- node = (direction == FocusDirectionForward)
- ? nextFocusableNode(parentDocument, owner, event)
- : previousFocusableNode(parentDocument, owner, event);
-
- frame = parentFrame;
- }
-
- node = deepFocusableNode(direction, node, event);
-
if (!node) {
// We didn't find a node to focus, so we should try to pass focus to Chrome.
if (!initialFocus && m_page->chrome()->canTakeFocus(direction)) {
@@ -250,11 +243,7 @@
}
// Chrome doesn't want focus, so we should wrap focus.
- Document* d = m_page->mainFrame()->document();
- node = (direction == FocusDirectionForward)
- ? nextFocusableNode(d, 0, event)
- : previousFocusableNode(d, 0, event);
-
+ node = findFocusableNode(direction, m_page->mainFrame()->document(), 0, event);
node = deepFocusableNode(direction, node, event);
if (!node)
@@ -307,6 +296,29 @@
return true;
}
+Node* FocusController::findFocusableNodeAcrossTreeScope(FocusDirection direction, TreeScope* scope, Node* currentNode, KeyboardEvent* event)
+{
+ Node* node = findFocusableNode(direction, scope, currentNode, event);
+ // If there's no focusable node to advance to, move up the tree scopes until we find one.
+ while (!node && scope) {
+ Node* owner = ownerOfTreeScope(scope);
+ if (!owner)
+ break;
+ node = findFocusableNode(direction, owner->treeScope(), owner, event);
+ scope = owner->treeScope();
+ }
+ node = deepFocusableNode(direction, node, event);
+ return node;
+}
+
+
+Node* FocusController::findFocusableNode(FocusDirection direction, TreeScope* scope, Node* node, KeyboardEvent* event)
+{
+ return (direction == FocusDirectionForward)
+ ? nextFocusableNode(scope, node, event)
+ : previousFocusableNode(scope, node, event);
+}
+
static Node* nextNodeWithExactTabIndex(Node* start, int tabIndex, KeyboardEvent* event)
{
// Search is inclusive of start
@@ -355,7 +367,7 @@
return winner;
}
-Node* FocusController::nextFocusableNode(TreeScope* within, Node* start, KeyboardEvent* event)
+Node* FocusController::nextFocusableNode(TreeScope* scope, Node* start, KeyboardEvent* event)
{
if (start) {
// If a node is excluded from the normal tabbing cycle, the next focusable node is determined by tree order
@@ -365,7 +377,7 @@
return n;
}
- // First try to find a node with the same tabindex as start that comes after start in the tree scope.
+ // First try to find a node with the same tabindex as start that comes after start in the scope.
if (Node* winner = nextNodeWithExactTabIndex(start->traverseNextNode(), start->tabIndex(), event))
return winner;
@@ -374,24 +386,24 @@
return 0;
}
- // Look for the first node in the tree scope that:
+ // Look for the first node in the scope that:
// 1) has the lowest tabindex that is higher than start's tabindex (or 0, if start is null), and
- // 2) comes first in the tree scope, if there's a tie.
- if (Node* winner = nextNodeWithGreaterTabIndex(within, start ? start->tabIndex() : 0, event))
+ // 2) comes first in the scope, if there's a tie.
+ if (Node* winner = nextNodeWithGreaterTabIndex(scope, start ? start->tabIndex() : 0, event))
return winner;
// There are no nodes with a tabindex greater than start's tabindex,
// so find the first node with a tabindex of 0.
- return nextNodeWithExactTabIndex(within, 0, event);
+ return nextNodeWithExactTabIndex(scope, 0, event);
}
-Node* FocusController::previousFocusableNode(TreeScope* within, Node* start, KeyboardEvent* event)
+Node* FocusController::previousFocusableNode(TreeScope* scope, Node* start, KeyboardEvent* event)
{
Node* last;
- for (last = within; last->lastChild(); last = last->lastChild()) { }
+ for (last = scope; last->lastChild(); last = last->lastChild()) { }
- // First try to find the last node in the tree scope that comes before start and has the same tabindex as start.
- // If start is null, find the last node in the tree scope with a tabindex of 0.
+ // First try to find the last node in the scope that comes before start and has the same tabindex as start.
+ // If start is null, find the last node in the scope with a tabindex of 0.
Node* startingNode;
int startingTabIndex;
if (start) {
@@ -414,11 +426,21 @@
// There are no nodes before start with the same tabindex as start, so look for a node that:
// 1) has the highest non-zero tabindex (that is less than start's tabindex), and
- // 2) comes last in the tree scope, if there's a tie.
+ // 2) comes last in the scope, if there's a tie.
startingTabIndex = (start && start->tabIndex()) ? start->tabIndex() : std::numeric_limits<short>::max();
return previousNodeWithLowerTabIndex(last, startingTabIndex, event);
}
+Node* FocusController::ownerOfTreeScope(TreeScope* scope)
+{
+ ASSERT(scope);
+ if (scope->isShadowRoot())
+ return scope->shadowHost();
+ if (scope->document()->frame())
+ return scope->document()->frame()->ownerElement();
+ return 0;
+}
+
static bool relinquishesEditingFocus(Node *node)
{
ASSERT(node);
Modified: trunk/Source/WebCore/page/FocusController.h (88420 => 88421)
--- trunk/Source/WebCore/page/FocusController.h 2011-06-09 04:56:37 UTC (rev 88420)
+++ trunk/Source/WebCore/page/FocusController.h 2011-06-09 05:33:12 UTC (rev 88421)
@@ -65,8 +65,24 @@
bool advanceFocusDirectionally(FocusDirection, KeyboardEvent*);
bool advanceFocusInDocumentOrder(FocusDirection, KeyboardEvent*, bool initialFocus);
+ Node* findFocusableNodeAcrossTreeScope(FocusDirection, TreeScope* startScope, Node* start, KeyboardEvent*);
Node* deepFocusableNode(FocusDirection, Node*, KeyboardEvent*);
+ Node* ownerOfTreeScope(TreeScope*);
+ // Searches through the given tree scope, starting from start node, for the next/previous selectable element that comes after/before start node.
+ // The order followed is as specified in section 17.11.1 of the HTML4 spec, which is elements with tab indexes
+ // first (from lowest to highest), and then elements without tab indexes (in document order).
+ //
+ // @param start The node from which to start searching. The node after this will be focused. May be null.
+ //
+ // @return The focus node that comes after/before start node.
+ //
+ // See http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1
+ inline Node* findFocusableNode(FocusDirection, TreeScope*, Node* start, KeyboardEvent*);
+
+ Node* nextFocusableNode(TreeScope*, Node* start, KeyboardEvent*);
+ Node* previousFocusableNode(TreeScope*, Node* start, KeyboardEvent*);
+
bool advanceFocusDirectionallyInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*);
void findFocusCandidateInContainer(Node* container, const IntRect& startingRect, FocusDirection, KeyboardEvent*, FocusCandidate& closest);
@@ -76,29 +92,6 @@
bool m_isFocused;
bool m_isChangingFocusedFrame;
- // Searches through the document, starting from start node, for the next selectable element that comes after start node.
- // The order followed is as specified in section 17.11.1 of the HTML4 spec, which is elements with tab indexes
- // first (from lowest to highest), and then elements without tab indexes (in document order).
- //
- // @param within The tree scope where a search is executed.
- // @param start The node from which to start searching. The node before this will be focused. May be null.
- //
- // @return The focus node that comes after start node.
- //
- // See http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1
- Node* nextFocusableNode(TreeScope* within, Node* start, KeyboardEvent*);
-
- // Searches through the document, starting from start node, for the previous selectable element that comes before start node.
- // The order followed is as specified in section 17.11.1 of the HTML4 spec, which is elements with tab indexes
- // first (from lowest to highest), and then elements without tab indexes (in document order).
- //
- // @param within The tree scope where a search is executed.
- // @param start The node from which to start searching. The node before this will be focused. May be null.
- //
- // @return The focus node that comes before start node.
- //
- // See http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1
- Node* previousFocusableNode(TreeScope* within, Node* start, KeyboardEvent*);
};
} // namespace WebCore