Changeset 112511 in webkit


Ignore:
Timestamp:
Mar 29, 2012 2:45:50 AM (12 years ago)
Author:
hayato@chromium.org
Message:

Let focus navigation be compliant with Shadow DOM spec.
https://bugs.webkit.org/show_bug.cgi?id=78588

Reviewed by Dimitri Glazkov.

Source/WebCore:

Re-landing r112500. Fixed an assertion failure on ReifiedTreeTraversal.

Sequential focus navigation now behaves exactly as specified in the Shadow DOM spec.

According to the Shadow DOM spec:
The shadow DOM navigation order sequence is inserted into the document navigation order:

  1. immediately after the shadow host, if the shadow host is focusable; or
  2. in place of the shadow host as if the shadow host were assigned the value of auto for determining its position.

Prior to this patch, sequential focus navigation goes into Shadow DOM, but it is incomplete
since insertion points, such as <content> elements or <shadow> elements, are not resolved at all.
Now focus navigation can traverse shadow DOM subtrees in 'reified tree order', resolving lower boundaries transparently.

Implementation notes:
Prior to this patch, sequential focus navigation does not go into Shadow DOM if a shadow host is non-focusable.
Now focus navigation must go into Shadow DOM subtrees even if a show host is not focusable as described in 2).
To support this behavior, this patch introduced adjustedTabIndex() locally in FocusController so that
it does not skip a non-focusable shadow host in current focus scope.
After finding a *pseudo* focusable element in current focus scope, it tries to resolve a focused element recursively,
considering a nested focus scope inside of a shadow host or iframe.
To traverse Shadow DOM subtrees, a FocusController makes use of ReifiedTreeTraversal APIs, which was introduced in r112055.

This change does not affect an existing behavior if a shadow dom is not involved.

Test: fast/dom/shadow/focus-navigation.html

  • dom/Element.cpp:

(WebCore::Element::focus):

  • dom/ReifiedTreeTraversal.cpp:

(WebCore::ReifiedTreeTraversal::parentNodeWithoutCrossingUpperBoundary):

  • page/FocusController.cpp:

(WebCore::isShadowHost):
(WebCore):
(WebCore::FocusScope::FocusScope):
(WebCore::FocusScope::rootNode):
(WebCore::FocusScope::owner):
(WebCore::FocusScope::focusScopeOf):
(WebCore::FocusScope::focusScopeOwnedByShadowHost):
(WebCore::FocusScope::focusScopeOwnedByIFrame):
(WebCore::hasCustomFocusLogic):
(WebCore::isNonFocusableShadowHost):
(WebCore::isFocusableShadowHost):
(WebCore::adjustedTabIndex):
(WebCore::shouldVisit):
(WebCore::FocusController::findFocusableNodeDecendingDownIntoFrameDocument):
(WebCore::FocusController::advanceFocusInDocumentOrder):
(WebCore::FocusController::findFocusableNodeAcrossFocusScope):
(WebCore::FocusController::findFocusableNodeRecursively):
(WebCore::FocusController::findFocusableNode):
(WebCore::FocusController::findNodeWithExactTabIndex):
(WebCore::nextNodeWithGreaterTabIndex):
(WebCore::previousNodeWithLowerTabIndex):
(WebCore::FocusController::nextFocusableNode):
(WebCore::FocusController::previousFocusableNode):

  • page/FocusController.h:

(WebCore):
(FocusScope):
(FocusController):

LayoutTests:

  • fast/dom/shadow/focus-navigation-expected.txt: Added.
  • fast/dom/shadow/focus-navigation.html: Added.
  • fast/dom/shadow/resources/shadow-dom.js:

(isShadowHost):
(isIframeElement):
(getNodeInShadowTreeStack):
(dumpNode):
(innermostActiveElement):
(isInnermostActiveElement):
(shouldNavigateFocus):
(navigateFocusForward):
(navigateFocusBackward):
(testFocusNavigationFowrad):
(testFocusNavigationBackward):

  • fast/dom/shadow/shadow-host-transfer-focus-expected.txt: Removed.
  • fast/dom/shadow/shadow-host-transfer-focus.html: Removed.
  • fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt:
  • fast/dom/shadow/tab-order-iframe-and-shadow.html:
Location:
trunk
Files:
2 added
2 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r112509 r112511  
     12012-03-29  hayato@chromium.org  <hayato@chromium.org>
     2
     3        Let focus navigation be compliant with Shadow DOM spec.
     4        https://bugs.webkit.org/show_bug.cgi?id=78588
     5
     6        Reviewed by Dimitri Glazkov.
     7
     8        * fast/dom/shadow/focus-navigation-expected.txt: Added.
     9        * fast/dom/shadow/focus-navigation.html: Added.
     10        * fast/dom/shadow/resources/shadow-dom.js:
     11        (isShadowHost):
     12        (isIframeElement):
     13        (getNodeInShadowTreeStack):
     14        (dumpNode):
     15        (innermostActiveElement):
     16        (isInnermostActiveElement):
     17        (shouldNavigateFocus):
     18        (navigateFocusForward):
     19        (navigateFocusBackward):
     20        (testFocusNavigationFowrad):
     21        (testFocusNavigationBackward):
     22        * fast/dom/shadow/shadow-host-transfer-focus-expected.txt: Removed.
     23        * fast/dom/shadow/shadow-host-transfer-focus.html: Removed.
     24        * fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt:
     25        * fast/dom/shadow/tab-order-iframe-and-shadow.html:
     26
    1272012-03-29  Ádám Kallai  <kadam@inf.u-szeged.hu>
    228
  • trunk/LayoutTests/fast/dom/shadow/resources/shadow-dom.js

    r112505 r112511  
    3535}
    3636
     37function isShadowHost(node)
     38{
     39    return window.internals.oldestShadowRoot(node);
     40}
     41
    3742function isShadowRoot(node)
    3843{
    3944    // FIXME: window.internals should have internals.isShadowRoot(node).
    4045    return node.nodeName == "#shadow-root" || node.host;
     46}
     47
     48function isIframeElement(element)
     49{
     50    return element && element.nodeName == 'IFRAME';
    4151}
    4252
     
    4858    var node = document.getElementById(ids[0]);
    4959    for (var i = 1; node != null && i < ids.length; ++i) {
     60        if (isIframeElement(node)) {
     61            node = node.contentDocument.getElementById(ids[i]);
     62            continue;
     63        }
    5064        if (isShadowRoot(node))
    5165            node = internals.youngerShadowRoot(node);
     66        else if (internals.oldestShadowRoot(node))
     67            node = internals.oldestShadowRoot(node);
    5268        else
    53             node = internals.oldestShadowRoot(node);
     69            return null;
    5470        if (ids[i] != '')
    5571            node = internals.getElementByIdInShadowRoot(node, ids[i]);
     
    5874}
    5975
     76function dumpNode(node)
     77{
     78    if (!node)
     79      return 'null';
     80    var output = '' + node;
     81    if (node.id)
     82        output += ' id=' + node.id;
     83    return output;
     84}
     85
     86function innermostActiveElement(element)
     87{
     88    element = element || document.activeElement;
     89    if (isIframeElement(element)) {
     90        if (element.contentDocument.activeElement)
     91            return innermostActiveElement(element.contentDocument.activeElement);
     92        return element;
     93    }
     94    if (isShadowHost(element)) {
     95        var shadowRoot = window.internals.oldestShadowRoot(element);
     96        while (shadowRoot) {
     97            if (shadowRoot.activeElement)
     98                return innermostActiveElement(shadowRoot.activeElement);
     99            shadowRoot = window.internals.youngerShadowRoot(shadowRoot);
     100        }
     101    }
     102    return element;
     103}
     104
     105function isInnermostActiveElement(id)
     106{
     107    var element = getNodeInShadowTreeStack(id);
     108    if (!element) {
     109        debug('FAIL: There is no such element with id: '+ from);
     110        return false;
     111    }
     112    if (element == innermostActiveElement())
     113        return true;
     114    debug('Expected innermost activeElement is ' + id + ', but actual innermost activeElement is ' + dumpNode(innermostActiveElement()));
     115    return false;
     116}
     117
     118function shouldNavigateFocus(from, to, direction)
     119{
     120    debug('Should move from ' + from + ' to ' + to + ' in ' + direction);
     121    var fromElement = getNodeInShadowTreeStack(from);
     122    if (!fromElement) {
     123      debug('FAIL: There is no such element with id: '+ from);
     124      return;
     125    }
     126    fromElement.focus();
     127    if (direction == 'forward')
     128        navigateFocusForward();
     129    else
     130        navigateFocusBackward();
     131    if (isInnermostActiveElement(to))
     132        debug('PASS');
     133    else
     134        debug('FAIL');
     135}
     136
     137function navigateFocusForward()
     138{
     139    eventSender.keyDown('\t');
     140}
     141
     142function navigateFocusBackward()
     143{
     144    eventSender.keyDown('\t', ['shiftKey']);
     145}
     146
     147function testFocusNavigationFowrad(elements)
     148{
     149    for (var i = 0; i + 1 < elements.length; ++i)
     150        shouldNavigateFocus(elements[i], elements[i + 1], 'forward');
     151}
     152
     153function testFocusNavigationBackward(elements)
     154{
     155    for (var i = 0; i + 1 < elements.length; ++i)
     156        shouldNavigateFocus(elements[i], elements[i + 1], 'backward');
     157}
  • trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow-expected.txt

    r112505 r112511  
    1 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.
     1CONSOLE MESSAGE: line 104: Uncaught ReferenceError: log is not defined
     2This tests that pressing Tab key should traverse into iframe and shadow tree, and pressing Shift-Tab should reverse the order.
    23
    3 Focus input-01.
    4 id:input-01(tabIndex=1) is focused.
    5 
    6 Press Tab 11 times.
    7 id:input-13(tabIndex=1) is focused.
    8 id:input-15(tabIndex=2) is focused.
    9 id:input-02(tabIndex=0) is focused.
    10 id:input-04(tabIndex=0) is focused.
    11 id:iframe-input-06(tabIndex=1) is focused.
    12 id:iframe-shadow-input-09(tabIndex=1) is focused.
    13 id:iframe-shadow-input-08(tabIndex=0) is focused.
    14 id:iframe-input-12(tabIndex=1) is focused.
    15 id:iframe-input-11(tabIndex=2) is focused.
    16 id:iframe-input-05(tabIndex=0) is focused.
    17 id:input-14(tabIndex=0) is focused.
    18 
    19 Press Shift-Tab 11 times.
    20 id:iframe-input-05(tabIndex=0) is focused.
    21 id:iframe-input-11(tabIndex=2) is focused.
    22 id:iframe-input-12(tabIndex=1) is focused.
    23 id:iframe-shadow-input-08(tabIndex=0) is focused.
    24 id:iframe-shadow-input-09(tabIndex=1) is focused.
    25 id:iframe-input-06(tabIndex=1) is focused.
    26 id:input-04(tabIndex=0) is focused.
    27 id:input-02(tabIndex=0) is focused.
    28 id:input-15(tabIndex=2) is focused.
    29 id:input-13(tabIndex=1) is focused.
    30 id:input-01(tabIndex=1) is focused.
    31 
    32 Test finished.
     4Should move from input-01 to input-13 in forward
     5PASS
     6Should move from input-13 to input-15 in forward
     7PASS
     8Should move from input-15 to input-02 in forward
     9PASS
     10Should move from input-02 to host-01/input-03 in forward
     11PASS
     12Should move from host-01/input-03 to input-04 in forward
     13PASS
     14Should move from input-04 to iframe/input-06 in forward
     15PASS
     16Should move from iframe/input-06 to iframe/host-02 in forward
     17PASS
     18Should move from iframe/host-02 to iframe/host-02/input-09 in forward
     19PASS
     20Should move from iframe/host-02/input-09 to iframe/host-02/input-08 in forward
     21PASS
     22Should move from iframe/host-02/input-08 to iframe/input-12 in forward
     23PASS
     24Should move from iframe/input-12 to iframe/input-11 in forward
     25PASS
     26Should move from iframe/input-11 to iframe/input-05 in forward
     27PASS
     28Should move from iframe/input-05 to input-14 in forward
     29PASS
     30Should move from input-14 to iframe/input-05 in backward
     31PASS
     32Should move from iframe/input-05 to iframe/input-11 in backward
     33PASS
     34Should move from iframe/input-11 to iframe/input-12 in backward
     35PASS
     36Should move from iframe/input-12 to iframe/host-02/input-08 in backward
     37PASS
     38Should move from iframe/host-02/input-08 to iframe/host-02/input-09 in backward
     39PASS
     40Should move from iframe/host-02/input-09 to iframe/host-02 in backward
     41PASS
     42Should move from iframe/host-02 to iframe/input-06 in backward
     43PASS
     44Should move from iframe/input-06 to input-04 in backward
     45PASS
     46Should move from input-04 to host-01/input-03 in backward
     47PASS
     48Should move from host-01/input-03 to input-02 in backward
     49PASS
     50Should move from input-02 to input-15 in backward
     51PASS
     52Should move from input-15 to input-13 in backward
     53PASS
     54Should move from input-13 to input-01 in backward
     55PASS
    3356
    3457
  • trunk/LayoutTests/fast/dom/shadow/tab-order-iframe-and-shadow.html

    r112505 r112511  
    11<!DOCTYPE html>
    22<html>
     3<head>
     4<script src="../../js/resources/js-test-pre.js"></script>
     5<script src="resources/shadow-dom.js"></script>
     6</head>
    37<body>
    4 <p>This tests that pressing Tab key should traverse into iframe and shadow tree, and pressing Shift-Tab should reverse the order.
    5  Makes sure that a shadow host element should act like a iframe element.</p>
     8<p>This tests that pressing Tab key should traverse into iframe and shadow tree, and pressing Shift-Tab should reverse the order.</p>
    69<pre id="console"></pre>
    710<script>
    8 function log(msg) {
    9     document.querySelector('#console').textContent += (msg + '\n');
    10 }
    11 
    12 function description(element) {
    13    var msg = '';
    14     if (element.id) {
    15       msg += 'id:' + element.id;
    16     }
    17     msg += '(tabIndex=' + element.tabIndex + ')';
    18     return msg;
    19 }
    20 
    21 function onFocus(event) {
    22     log(description(event.target) + ' is focused.');
    23 }
    24 
    25 function addFocusEventListener(element) {
    26     element.addEventListener('focus', onFocus, false);
    27 }
    28 
    2911function createTextInputElement(doc, id, tabIndex) {
    3012    var input = doc.createElement('input');
     
    3214    input.id = id;
    3315    input.tabIndex = tabIndex;
    34     addFocusEventListener(input);
    3516    return input;
    3617}
     
    4526    function addShadowHost(doc) {
    4627        var shadowHost = doc.createElement('p');
    47         shadowHost.tabIndex = -1;  // This shadow host (and a shadow tree in that) should be skipped.
     28        shadowHost.id = 'host-01';
     29        shadowHost.tabIndex = -1;
    4830        var shadow = internals.ensureShadowRoot(shadowHost);
    4931        doc.body.appendChild(shadowHost);
    50         shadow.appendChild(createTextInputElement(doc, 'shadow-input-03', 0));
     32        shadow.appendChild(createTextInputElement(doc, 'input-03', 0));
    5133    }
    5234    addShadowHost(doc);
     
    5638    function addIframe(doc) {
    5739        var iframe = doc.createElement('iframe');
     40        iframe.id = 'iframe';
    5841        doc.body.appendChild(iframe);
    5942        doc = iframe.contentDocument;
    6043
    61         doc.body.appendChild(createTextInputElement(doc, 'iframe-input-05', 0));
    62         doc.body.appendChild(createTextInputElement(doc, 'iframe-input-06', 1));
    63         doc.body.appendChild(createTextInputElement(doc, 'iframe-input-07', -1));
     44        doc.body.appendChild(createTextInputElement(doc, 'input-05', 0));
     45        doc.body.appendChild(createTextInputElement(doc, 'input-06', 1));
     46        doc.body.appendChild(createTextInputElement(doc, 'input-07', -1));
    6447
    6548        function addShadowHost(doc) {
    6649            var shadowHost = doc.createElement('p');
     50            shadowHost.id = 'host-02';
    6751            shadowHost.tabIndex = 1;
    6852            var shadow = internals.ensureShadowRoot(shadowHost);
    6953            doc.body.appendChild(shadowHost);
    7054
    71             shadow.appendChild(createTextInputElement(doc, 'iframe-shadow-input-08', 0));
    72             shadow.appendChild(createTextInputElement(doc, 'iframe-shadow-input-09', 1));
    73             shadow.appendChild(createTextInputElement(doc, 'iframe-shadow-input-10', -1));
     55            shadow.appendChild(createTextInputElement(doc, 'input-08', 0));
     56            shadow.appendChild(createTextInputElement(doc, 'input-09', 1));
     57            shadow.appendChild(createTextInputElement(doc, 'input-10', -1));
    7458        }
    7559        addShadowHost(doc);
    7660
    77         doc.body.appendChild(createTextInputElement(doc, 'iframe-input-11', 2));
    78         doc.body.appendChild(createTextInputElement(doc, 'iframe-input-12', 1));
     61        doc.body.appendChild(createTextInputElement(doc, 'input-11', 2));
     62        doc.body.appendChild(createTextInputElement(doc, 'input-12', 1));
    7963    }
    8064    addIframe(doc);
     
    8468    doc.body.appendChild(createTextInputElement(doc, 'input-15', 2));
    8569
    86     log('Focus input-01.');
    87     doc.getElementById('input-01').focus();
     70    testFocusNavigationFowrad([
     71        'input-01',
     72        'input-13',
     73        'input-15',
     74        'input-02',
     75        'host-01/input-03',
     76        'input-04',
     77        'iframe/input-06',
     78        'iframe/host-02',
     79        'iframe/host-02/input-09',
     80        'iframe/host-02/input-08',
     81        'iframe/input-12',
     82        'iframe/input-11',
     83        'iframe/input-05',
     84        'input-14',
     85    ]);
    8886
    89     if (window.eventSender) {
    90         var pressed = 11;
    91         log('\nPress Tab ' + pressed + ' times.');
    92         for (var i = 0; i < pressed; ++i) {
    93             eventSender.keyDown('\t');
    94         }
    95         log('\nPress Shift-Tab ' + pressed + ' times.');
    96         for (var i = 0; i < pressed; ++i) {
    97             eventSender.keyDown('\t', ['shiftKey']);
    98         }
    99    }
     87    testFocusNavigationBackward([
     88        'input-14',
     89        'iframe/input-05',
     90        'iframe/input-11',
     91        'iframe/input-12',
     92        'iframe/host-02/input-08',
     93        'iframe/host-02/input-09',
     94        'iframe/host-02',
     95        'iframe/input-06',
     96        'input-04',
     97        'host-01/input-03',
     98        'input-02',
     99        'input-15',
     100        'input-13',
     101        'input-01',
     102    ]);
     103
    100104   log('\nTest finished.');
    101105}
  • trunk/Source/WebCore/ChangeLog

    r112510 r112511  
     12012-03-29  hayato@chromium.org  <hayato@chromium.org>
     2
     3        Let focus navigation be compliant with Shadow DOM spec.
     4        https://bugs.webkit.org/show_bug.cgi?id=78588
     5
     6        Reviewed by Dimitri Glazkov.
     7
     8        Re-landing r112500. Fixed an assertion failure on ReifiedTreeTraversal.
     9
     10        Sequential focus navigation now behaves exactly as specified in the Shadow DOM spec.
     11
     12        According to the Shadow DOM spec:
     13        The shadow DOM navigation order sequence is inserted into the document navigation order:
     14        1. immediately after the shadow host, if the shadow host is focusable; or
     15        2. in place of the shadow host as if the shadow host were assigned the value of auto for determining its position.
     16
     17        Prior to this patch, sequential focus navigation goes into Shadow DOM, but it is incomplete
     18        since insertion points, such as <content> elements or <shadow> elements, are not resolved at all.
     19        Now focus navigation can traverse shadow DOM subtrees in 'reified tree order', resolving lower boundaries transparently.
     20
     21        Implementation notes:
     22        Prior to this patch, sequential focus navigation does not go into Shadow DOM if a shadow host is non-focusable.
     23        Now focus navigation must go into Shadow DOM subtrees even if a show host is not focusable as described in 2).
     24        To support this behavior, this patch introduced adjustedTabIndex() locally in FocusController so that
     25        it does not skip a non-focusable shadow host in current focus scope.
     26        After finding a *pseudo* focusable element in current focus scope, it tries to resolve a focused element recursively,
     27        considering a nested focus scope inside of a shadow host or iframe.
     28        To traverse Shadow DOM subtrees, a FocusController makes use of ReifiedTreeTraversal APIs, which was introduced in r112055.
     29
     30        This change does not affect an existing behavior if a shadow dom is not involved.
     31
     32        Test: fast/dom/shadow/focus-navigation.html
     33
     34        * dom/Element.cpp:
     35        (WebCore::Element::focus):
     36        * dom/ReifiedTreeTraversal.cpp:
     37        (WebCore::ReifiedTreeTraversal::parentNodeWithoutCrossingUpperBoundary):
     38        * page/FocusController.cpp:
     39        (WebCore::isShadowHost):
     40        (WebCore):
     41        (WebCore::FocusScope::FocusScope):
     42        (WebCore::FocusScope::rootNode):
     43        (WebCore::FocusScope::owner):
     44        (WebCore::FocusScope::focusScopeOf):
     45        (WebCore::FocusScope::focusScopeOwnedByShadowHost):
     46        (WebCore::FocusScope::focusScopeOwnedByIFrame):
     47        (WebCore::hasCustomFocusLogic):
     48        (WebCore::isNonFocusableShadowHost):
     49        (WebCore::isFocusableShadowHost):
     50        (WebCore::adjustedTabIndex):
     51        (WebCore::shouldVisit):
     52        (WebCore::FocusController::findFocusableNodeDecendingDownIntoFrameDocument):
     53        (WebCore::FocusController::advanceFocusInDocumentOrder):
     54        (WebCore::FocusController::findFocusableNodeAcrossFocusScope):
     55        (WebCore::FocusController::findFocusableNodeRecursively):
     56        (WebCore::FocusController::findFocusableNode):
     57        (WebCore::FocusController::findNodeWithExactTabIndex):
     58        (WebCore::nextNodeWithGreaterTabIndex):
     59        (WebCore::previousNodeWithLowerTabIndex):
     60        (WebCore::FocusController::nextFocusableNode):
     61        (WebCore::FocusController::previousFocusableNode):
     62        * page/FocusController.h:
     63        (WebCore):
     64        (FocusScope):
     65        (FocusController):
     66
    1672012-03-28  Kinuko Yasuda  <kinuko@chromium.org>
    268
  • trunk/Source/WebCore/dom/Element.cpp

    r112505 r112511  
    15491549        // does not make sense to continue and update appearence.
    15501550        protect = this;
    1551         if (hasShadowRoot() && page->focusController()->transferFocusToElementInShadowRoot(this, restorePreviousSelection))
    1552             return;
    15531551        if (!page->focusController()->setFocusedNode(this, doc->frame()))
    15541552            return;
  • trunk/Source/WebCore/dom/ReifiedTreeTraversal.cpp

    r112055 r112511  
    211211    // ASSERT(!isInsertionPoint(node) || !toInsertionPoint(node)->isActive());
    212212    CrossedUpperBoundary crossed;
    213     Node* parent = parentNodeOrBackToInsertionPoint(node, crossed);
     213    Node* parent = parentNode(node, crossed);
    214214    if (crossed == Crossed)
    215215        return 0;
  • trunk/Source/WebCore/page/FocusController.cpp

    r112505 r112511  
    4949#include "Page.h"
    5050#include "Range.h"
     51#include "ReifiedTreeTraversal.h"
    5152#include "RenderLayer.h"
    5253#include "RenderObject.h"
     
    6667using namespace std;
    6768
     69static inline bool isShadowHost(const Node* node)
     70{
     71    return node && node->isElementNode() && toElement(node)->hasShadowRoot();
     72}
     73
     74FocusScope::FocusScope(TreeScope* treeScope)
     75    : m_rootTreeScope(treeScope)
     76{
     77    ASSERT(treeScope);
     78    ASSERT(!treeScope->rootNode()->isShadowRoot() || toShadowRoot(treeScope->rootNode())->isYoungest());
     79}
     80
     81Node* FocusScope::rootNode() const
     82{
     83    return m_rootTreeScope->rootNode();
     84}
     85
     86Element* FocusScope::owner() const
     87{
     88    Node* root = rootNode();
     89    if (root->isShadowRoot())
     90        return root->shadowHost();
     91    if (Frame* frame = root->document()->frame())
     92        return frame->ownerElement();
     93    return 0;
     94}
     95
     96FocusScope FocusScope::focusScopeOf(Node* node)
     97{
     98    ASSERT(node);
     99    TreeScope* scope = node->treeScope();
     100    if (scope->rootNode()->isShadowRoot())
     101        return FocusScope(toShadowRoot(scope->rootNode())->tree()->youngestShadowRoot());
     102    return FocusScope(scope);
     103}
     104
     105FocusScope FocusScope::focusScopeOwnedByShadowHost(Node* node)
     106{
     107    ASSERT(isShadowHost(node));
     108    return FocusScope(toElement(node)->shadowTree()->youngestShadowRoot());
     109}
     110
     111FocusScope FocusScope::focusScopeOwnedByIFrame(HTMLFrameOwnerElement* frame)
     112{
     113    ASSERT(frame && frame->contentFrame());
     114    return FocusScope(frame->contentFrame()->document());
     115}
     116
    68117static inline void dispatchEventsOnWindowAndFocusedNode(Document* document, bool focused)
    69118{
     
    83132    if (focused && document->focusedNode())
    84133        document->focusedNode()->dispatchFocusEvent(0);
     134}
     135
     136static inline bool hasCustomFocusLogic(Node* node)
     137{
     138    return node->hasTagName(inputTag) || node->hasTagName(textareaTag) || node->hasTagName(videoTag) || node->hasTagName(audioTag);
     139}
     140
     141static inline bool isNonFocusableShadowHost(Node* node, KeyboardEvent* event)
     142{
     143    ASSERT(node);
     144    return !node->isKeyboardFocusable(event) && isShadowHost(node) && !hasCustomFocusLogic(node);
     145}
     146
     147static inline bool isFocusableShadowHost(Node* node, KeyboardEvent* event)
     148{
     149    ASSERT(node);
     150    return node->isKeyboardFocusable(event) && isShadowHost(node) && !hasCustomFocusLogic(node);
     151}
     152
     153static inline int adjustedTabIndex(Node* node, KeyboardEvent* event)
     154{
     155    ASSERT(node);
     156    return isNonFocusableShadowHost(node, event) ? 0 : node->tabIndex();
     157}
     158
     159static inline bool shouldVisit(Node* node, KeyboardEvent* event)
     160{
     161    ASSERT(node);
     162    return node->isKeyboardFocusable(event) || isNonFocusableShadowHost(node, event);
    85163}
    86164
     
    154232}
    155233
    156 static inline ShadowRoot* shadowRoot(Node* node)
    157 {
    158     return node->isElementNode() && toElement(node)->hasShadowRoot() ? toElement(node)->shadowTree()->youngestShadowRoot() : 0;
    159 }
    160 
    161 static inline bool isTreeScopeOwner(Node* node)
    162 {
    163     return node && (node->isFrameOwnerElement() || shadowRoot(node));
    164 }
    165 
    166 bool FocusController::transferFocusToElementInShadowRoot(Element* shadowHost, bool restorePreviousSelection)
    167 {
    168     ASSERT(shadowRoot(shadowHost));
    169     Node* node = findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(FocusDirectionForward, shadowHost, 0);
    170     if (shadowHost == node)
    171         return false;
    172     toElement(node)->focus(restorePreviousSelection);
    173     return true;
    174 }
    175 
    176 static inline bool hasCustomFocusLogic(Node* node)
    177 {
    178     return node->hasTagName(inputTag) || node->hasTagName(textareaTag) || node->hasTagName(videoTag) || node->hasTagName(audioTag);
    179 }
    180 
    181 Node* FocusController::findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(FocusDirection direction, Node* node, KeyboardEvent* event)
    182 {
    183     // The node we found might be a HTMLFrameOwnerElement or a shadow host, so descend down the tree until we find either:
     234Node* FocusController::findFocusableNodeDecendingDownIntoFrameDocument(FocusDirection direction, Node* node, KeyboardEvent* event)
     235{
     236    // The node we found might be a HTMLFrameOwnerElement, so descend down the tree until we find either:
    184237    // 1) a focusable node, or
    185     // 2) the deepest-nested HTMLFrameOwnerElement or shadow host.
    186     while (isTreeScopeOwner(node)) {
    187         Node* foundNode;
    188         if (node->isFrameOwnerElement()) {
    189             HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(node);
    190             if (!owner->contentFrame())
    191                 break;
    192             Document* document = owner->contentFrame()->document();
    193             foundNode = findFocusableNode(direction, document, 0, event);
    194         } else {
    195             // FIXME: Until a focus re-targeting (bug 61421) is implemented,
    196             // skipping these elements is the safest way to keep a compatibility.
    197             if (hasCustomFocusLogic(node))
    198                 break;
    199             ASSERT(shadowRoot(node));
    200             foundNode = findFocusableNode(direction, shadowRoot(node), 0, event);
    201         }
     238    // 2) the deepest-nested HTMLFrameOwnerElement.
     239    while (node && node->isFrameOwnerElement()) {
     240        HTMLFrameOwnerElement* owner = static_cast<HTMLFrameOwnerElement*>(node);
     241        if (!owner->contentFrame())
     242            break;
     243        Node* foundNode = findFocusableNode(direction, FocusScope::focusScopeOwnedByIFrame(owner), 0, event);
    202244        if (!foundNode)
    203245            break;
     
    254296    document->updateLayoutIgnorePendingStylesheets();
    255297
    256     RefPtr<Node> node = findFocusableNodeAcrossTreeScope(direction, currentNode ? currentNode->treeScope() : document, currentNode, event);
     298    RefPtr<Node> node = findFocusableNodeAcrossFocusScope(direction, FocusScope::focusScopeOf(currentNode ? currentNode : document), currentNode, event);
    257299
    258300    if (!node) {
     
    266308
    267309        // Chrome doesn't want focus, so we should wrap focus.
    268         node = findFocusableNode(direction, m_page->mainFrame()->document(), 0, event);
    269         node = findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(direction, node.get(), event);
     310        node = findFocusableNodeRecursively(direction, FocusScope::focusScopeOf(m_page->mainFrame()->document()), 0, event);
     311        node = findFocusableNodeDecendingDownIntoFrameDocument(direction, node.get(), event);
    270312
    271313        if (!node)
     
    319361}
    320362
    321 static inline Node* ownerOfTreeScope(TreeScope* scope)
    322 {
    323     ASSERT(scope);
    324     if (scope->rootNode()->isShadowRoot())
    325         return scope->rootNode()->shadowHost();
    326     if (scope->rootNode()->document()->frame())
    327         return scope->rootNode()->document()->frame()->ownerElement();
    328     return 0;
    329 }
    330 
    331 Node* FocusController::findFocusableNodeAcrossTreeScope(FocusDirection direction, TreeScope* scope, Node* currentNode, KeyboardEvent* event)
    332 {
    333     Node* node = findFocusableNode(direction, scope, currentNode, event);
    334     // If there's no focusable node to advance to, move up the tree scopes until we find one.
    335     while (!node && scope) {
    336         Node* owner = ownerOfTreeScope(scope);
     363Node* FocusController::findFocusableNodeAcrossFocusScope(FocusDirection direction, FocusScope scope, Node* currentNode, KeyboardEvent* event)
     364{
     365    ASSERT(!currentNode || !isNonFocusableShadowHost(currentNode, event));
     366    Node* found;
     367    if (currentNode && direction == FocusDirectionForward && isFocusableShadowHost(currentNode, event)) {
     368        Node* foundInInnerFocusScope = findFocusableNodeRecursively(direction, FocusScope::focusScopeOwnedByShadowHost(currentNode), 0, event);
     369        found = foundInInnerFocusScope ? foundInInnerFocusScope : findFocusableNodeRecursively(direction, scope, currentNode, event);
     370    } else
     371        found = findFocusableNodeRecursively(direction, scope, currentNode, event);
     372
     373    // If there's no focusable node to advance to, move up the focus scopes until we find one.
     374    while (!found) {
     375        Node* owner = scope.owner();
    337376        if (!owner)
    338377            break;
    339         node = findFocusableNode(direction, owner->treeScope(), owner, event);
    340         scope = owner->treeScope();
    341     }
    342     node = findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(direction, node, event);
    343     return node;
    344 }
    345 
    346 
    347 Node* FocusController::findFocusableNode(FocusDirection direction, TreeScope* scope, Node* node, KeyboardEvent* event)
     378        scope = FocusScope::focusScopeOf(owner);
     379        if (direction == FocusDirectionBackward && isFocusableShadowHost(owner, event)) {
     380            found = owner;
     381            break;
     382        }
     383        found = findFocusableNodeRecursively(direction, scope, owner, event);
     384    }
     385    found = findFocusableNodeDecendingDownIntoFrameDocument(direction, found, event);
     386    return found;
     387}
     388
     389Node* FocusController::findFocusableNodeRecursively(FocusDirection direction, FocusScope scope, Node* start, KeyboardEvent* event)
     390{
     391    // Starting node is exclusive.
     392    Node* found = findFocusableNode(direction, scope, start, event);
     393    if (!found)
     394        return 0;
     395    if (direction == FocusDirectionForward) {
     396        if (!isNonFocusableShadowHost(found, event))
     397            return found;
     398        Node* foundInInnerFocusScope = findFocusableNodeRecursively(direction, FocusScope::focusScopeOwnedByShadowHost(found), 0, event);
     399        return foundInInnerFocusScope ? foundInInnerFocusScope : findFocusableNodeRecursively(direction, scope, found, event);
     400    }
     401    ASSERT(direction == FocusDirectionBackward);
     402    if (isFocusableShadowHost(found, event)) {
     403        Node* foundInInnerFocusScope = findFocusableNodeRecursively(direction, FocusScope::focusScopeOwnedByShadowHost(found), 0, event);
     404        return foundInInnerFocusScope ? foundInInnerFocusScope : found;
     405    }
     406    if (isNonFocusableShadowHost(found, event)) {
     407        Node* foundInInnerFocusScope = findFocusableNodeRecursively(direction, FocusScope::focusScopeOwnedByShadowHost(found), 0, event);
     408        return foundInInnerFocusScope ? foundInInnerFocusScope :findFocusableNodeRecursively(direction, scope, found, event);
     409    }
     410    return found;
     411}
     412
     413Node* FocusController::findFocusableNode(FocusDirection direction, FocusScope scope, Node* node, KeyboardEvent* event)
    348414{
    349415    return (direction == FocusDirectionForward)
     
    352418}
    353419
    354 static Node* nextNodeWithExactTabIndex(Node* start, int tabIndex, KeyboardEvent* event)
     420Node* FocusController::findNodeWithExactTabIndex(Node* start, int tabIndex, KeyboardEvent* event, FocusDirection direction)
    355421{
    356422    // Search is inclusive of start
    357     for (Node* node = start; node; node = node->traverseNextNode())
    358         if (node->isKeyboardFocusable(event) && node->tabIndex() == tabIndex)
     423    for (Node* node = start; node; node = (direction == FocusDirectionForward ? ReifiedTreeTraversal::traverseNextNodeWithoutCrossingUpperBoundary(node): ReifiedTreeTraversal::traversePreviousNodeWithoutCrossingUpperBoundary(node))) {
     424        if (shouldVisit(node, event) && adjustedTabIndex(node, event) == tabIndex)
    359425            return node;
    360 
    361     return 0;
    362 }
    363 
    364 static Node* previousNodeWithExactTabIndex(Node* start, int tabIndex, KeyboardEvent* event)
    365 {
    366     // Search is inclusive of start
    367     for (Node* n = start; n; n = n->traversePreviousNode())
    368         if (n->isKeyboardFocusable(event) && n->tabIndex() == tabIndex)
    369             return n;
    370 
     426    }
    371427    return 0;
    372428}
     
    377433    int winningTabIndex = std::numeric_limits<short>::max() + 1;
    378434    Node* winner = 0;
    379     for (Node* n = start; n; n = n->traverseNextNode())
    380         if (n->isKeyboardFocusable(event) && n->tabIndex() > tabIndex && n->tabIndex() < winningTabIndex) {
     435    for (Node* n = start; n; n = ReifiedTreeTraversal::traverseNextNodeWithoutCrossingUpperBoundary(n))
     436        if (shouldVisit(n, event) && n->tabIndex() > tabIndex && n->tabIndex() < winningTabIndex) {
    381437            winner = n;
    382438            winningTabIndex = n->tabIndex();
     
    391447    int winningTabIndex = 0;
    392448    Node* winner = 0;
    393     for (Node* n = start; n; n = n->traversePreviousNode())
    394         if (n->isKeyboardFocusable(event) && n->tabIndex() < tabIndex && n->tabIndex() > winningTabIndex) {
     449    for (Node* n = start; n; n = ReifiedTreeTraversal::traversePreviousNodeWithoutCrossingUpperBoundary(n)) {
     450        int currentTabIndex = adjustedTabIndex(n, event);
     451        if ((shouldVisit(n, event) || isNonFocusableShadowHost(n, event)) && currentTabIndex < tabIndex && currentTabIndex > winningTabIndex) {
    395452            winner = n;
    396             winningTabIndex = n->tabIndex();
    397         }
    398 
     453            winningTabIndex = currentTabIndex;
     454        }
     455    }
    399456    return winner;
    400457}
    401458
    402 Node* FocusController::nextFocusableNode(TreeScope* scope, Node* start, KeyboardEvent* event)
     459Node* FocusController::nextFocusableNode(FocusScope scope, Node* start, KeyboardEvent* event)
    403460{
    404461    if (start) {
     462        int tabIndex = adjustedTabIndex(start, event);
    405463        // If a node is excluded from the normal tabbing cycle, the next focusable node is determined by tree order
    406         if (start->tabIndex() < 0) {
    407             for (Node* n = start->traverseNextNode(); n; n = n->traverseNextNode())
    408                 if (n->isKeyboardFocusable(event) && n->tabIndex() >= 0)
     464        if (tabIndex < 0) {
     465            for (Node* n = ReifiedTreeTraversal::traverseNextNodeWithoutCrossingUpperBoundary(start); n; n = ReifiedTreeTraversal::traverseNextNodeWithoutCrossingUpperBoundary(n)) {
     466                if (shouldVisit(n, event) && adjustedTabIndex(n, event) >= 0)
    409467                    return n;
     468            }
    410469        }
    411470
    412471        // First try to find a node with the same tabindex as start that comes after start in the scope.
    413         if (Node* winner = nextNodeWithExactTabIndex(start->traverseNextNode(), start->tabIndex(), event))
     472        if (Node* winner = findNodeWithExactTabIndex(ReifiedTreeTraversal::traverseNextNodeWithoutCrossingUpperBoundary(start), tabIndex, event, FocusDirectionForward))
    414473            return winner;
    415474
    416         if (!start->tabIndex())
     475        if (!tabIndex)
    417476            // We've reached the last node in the document with a tabindex of 0. This is the end of the tabbing order.
    418477            return 0;
     
    422481    // 1) has the lowest tabindex that is higher than start's tabindex (or 0, if start is null), and
    423482    // 2) comes first in the scope, if there's a tie.
    424     if (Node* winner = nextNodeWithGreaterTabIndex(scope->rootNode(), start ? start->tabIndex() : 0, event))
     483    if (Node* winner = nextNodeWithGreaterTabIndex(scope.rootNode(), start ? adjustedTabIndex(start, event) : 0, event))
    425484        return winner;
    426485
    427486    // There are no nodes with a tabindex greater than start's tabindex,
    428487    // so find the first node with a tabindex of 0.
    429     return nextNodeWithExactTabIndex(scope->rootNode(), 0, event);
    430 }
    431 
    432 Node* FocusController::previousFocusableNode(TreeScope* scope, Node* start, KeyboardEvent* event)
     488    return findNodeWithExactTabIndex(scope.rootNode(), 0, event, FocusDirectionForward);
     489}
     490
     491Node* FocusController::previousFocusableNode(FocusScope scope, Node* start, KeyboardEvent* event)
    433492{
    434493    Node* last;
    435     for (last = scope->rootNode(); last->lastChild(); last = last->lastChild()) { }
     494    for (last = scope.rootNode(); ReifiedTreeTraversal::lastChildWithoutCrossingUpperBoundary(last); last = ReifiedTreeTraversal::lastChildWithoutCrossingUpperBoundary(last)) { }
    436495
    437496    // First try to find the last node in the scope that comes before start and has the same tabindex as start.
     
    440499    int startingTabIndex;
    441500    if (start) {
    442         startingNode = start->traversePreviousNode();
    443         startingTabIndex = start->tabIndex();
     501        startingNode = ReifiedTreeTraversal::traversePreviousNodeWithoutCrossingUpperBoundary(start);
     502        startingTabIndex = adjustedTabIndex(start, event);
    444503    } else {
    445504        startingNode = last;
     
    449508    // However, if a node is excluded from the normal tabbing cycle, the previous focusable node is determined by tree order
    450509    if (startingTabIndex < 0) {
    451         for (Node* n = startingNode; n; n = n->traversePreviousNode())
    452             if (n->isKeyboardFocusable(event) && n->tabIndex() >= 0)
     510        for (Node* n = startingNode; n; n = ReifiedTreeTraversal::traversePreviousNodeWithoutCrossingUpperBoundary(n)) {
     511            if (shouldVisit(n, event) && adjustedTabIndex(n, event) >= 0)
    453512                return n;
    454     }
    455 
    456     if (Node* winner = previousNodeWithExactTabIndex(startingNode, startingTabIndex, event))
     513        }
     514    }
     515
     516    if (Node* winner = findNodeWithExactTabIndex(startingNode, startingTabIndex, event, FocusDirectionBackward))
    457517        return winner;
    458518
     
    460520    // 1) has the highest non-zero tabindex (that is less than start's tabindex), and
    461521    // 2) comes last in the scope, if there's a tie.
    462     startingTabIndex = (start && start->tabIndex()) ? start->tabIndex() : std::numeric_limits<short>::max();
     522    startingTabIndex = (start && startingTabIndex) ? startingTabIndex : std::numeric_limits<short>::max();
    463523    return previousNodeWithLowerTabIndex(last, startingTabIndex, event);
    464524}
  • trunk/Source/WebCore/page/FocusController.h

    r112505 r112511  
    3636
    3737struct FocusCandidate;
     38class Document;
    3839class Element;
    3940class Frame;
     41class HTMLFrameOwnerElement;
    4042class IntRect;
    4143class KeyboardEvent;
     
    4345class Page;
    4446class TreeScope;
     47
     48class FocusScope {
     49public:
     50    Node* rootNode() const;
     51    Element* owner() const;
     52    static FocusScope focusScopeOf(Node*);
     53    static FocusScope focusScopeOwnedByShadowHost(Node*);
     54    static FocusScope focusScopeOwnedByIFrame(HTMLFrameOwnerElement*);
     55
     56private:
     57    explicit FocusScope(TreeScope*);
     58    TreeScope* m_rootTreeScope;
     59};
    4560
    4661class FocusController {
     
    6782    bool containingWindowIsVisible() const { return m_containingWindowIsVisible; }
    6883
    69     bool transferFocusToElementInShadowRoot(Element* shadowHost, bool restorePreviousSelection);
    70 
    7184private:
    7285    FocusController(Page*);
     
    7588    bool advanceFocusInDocumentOrder(FocusDirection, KeyboardEvent*, bool initialFocus);
    7689
    77     Node* findFocusableNodeAcrossTreeScope(FocusDirection, TreeScope* startScope, Node* start, KeyboardEvent*);
    78     Node* findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(FocusDirection, Node*, KeyboardEvent*);
     90    Node* findFocusableNodeAcrossFocusScope(FocusDirection, FocusScope startScope, Node* start, KeyboardEvent*);
     91    Node* findFocusableNodeRecursively(FocusDirection, FocusScope, Node* start, KeyboardEvent*);
     92    Node* findFocusableNodeDecendingDownIntoFrameDocument(FocusDirection, Node*, KeyboardEvent*);
    7993
    8094    // Searches through the given tree scope, starting from start node, for the next/previous selectable element that comes after/before start node.
     
    87101    //
    88102    // See http://www.w3.org/TR/html4/interact/forms.html#h-17.11.1
    89     inline Node* findFocusableNode(FocusDirection, TreeScope*, Node* start, KeyboardEvent*);
     103    inline Node* findFocusableNode(FocusDirection, FocusScope, Node* start, KeyboardEvent*);
    90104
    91     Node* nextFocusableNode(TreeScope*, Node* start, KeyboardEvent*);
    92     Node* previousFocusableNode(TreeScope*, Node* start, KeyboardEvent*);
     105    Node* nextFocusableNode(FocusScope, Node* start, KeyboardEvent*);
     106    Node* previousFocusableNode(FocusScope, Node* start, KeyboardEvent*);
     107
     108    Node* findNodeWithExactTabIndex(Node* start, int tabIndex, KeyboardEvent*, FocusDirection);
    93109
    94110    bool advanceFocusDirectionallyInContainer(Node* container, const LayoutRect& startingRect, FocusDirection, KeyboardEvent*);
Note: See TracChangeset for help on using the changeset viewer.