Title: [88421] trunk
Revision
88421
Author
hay...@chromium.org
Date
2011-06-08 22:33:12 -0700 (Wed, 08 Jun 2011)

Log Message

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 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:

Modified Paths

Added Paths

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
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to