Diff
Modified: trunk/LayoutTests/ChangeLog (90003 => 90004)
--- trunk/LayoutTests/ChangeLog 2011-06-29 09:32:21 UTC (rev 90003)
+++ trunk/LayoutTests/ChangeLog 2011-06-29 09:39:58 UTC (rev 90004)
@@ -1,3 +1,21 @@
+2011-06-29 Hayato Ito <[email protected]>
+
+ Reviewed by Hajime Morita.
+
+ Make a shadow host transfer a focus to the first focusable element in the shadow root when its focus() is called.
+ https://bugs.webkit.org/show_bug.cgi?id=62358.
+
+ This patch doesn't take an effect on the following elements to keep a compatibility.
+ - <input>, <textarea>, <video> and <audio> elements
+ We'll address these elements separately after re-targeting focus events
+ (bug 61421) is implemented.
+
+ A shadow root's <content> is not considered in this patch.
+ That should be addressed in another patch. See bug 63522.
+
+ * fast/dom/shadow/shadow-host-transfer-focus-expected.txt: Added.
+ * fast/dom/shadow/shadow-host-transfer-focus.html: Added.
+
2011-06-29 Roland Steiner <[email protected]>
Unreviewed: last last ruby expectations for WebKit2 Win.
Added: trunk/LayoutTests/fast/dom/shadow/shadow-host-transfer-focus-expected.txt (0 => 90004)
--- trunk/LayoutTests/fast/dom/shadow/shadow-host-transfer-focus-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/shadow-host-transfer-focus-expected.txt 2011-06-29 09:39:58 UTC (rev 90004)
@@ -0,0 +1,17 @@
+When a shadow host is focused, the shadow host should transfer focus to the first focusable element in the shadow root if there is an such an element.
+
+Focusing: shadow0
+PASS elementWasFocused(input0) is false
+PASS elementWasFocused(input1) is true
+PASS elementWasFocused(input2) is false
+PASS input0.value is ""
+PASS input1.value is ""
+PASS input2.value is ""
+Pressing: "a"
+PASS input0.value is ""
+PASS input1.value is "a"
+PASS input2.value is ""
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/dom/shadow/shadow-host-transfer-focus.html (0 => 90004)
--- trunk/LayoutTests/fast/dom/shadow/shadow-host-transfer-focus.html (rev 0)
+++ trunk/LayoutTests/fast/dom/shadow/shadow-host-transfer-focus.html 2011-06-29 09:39:58 UTC (rev 90004)
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+</head>
+<body>
+<p>
+When a shadow host is focused, the shadow host should transfer focus to the first focusable element in the shadow root if there is an such an element.
+</p>
+<div id="console"></div>
+<script>
+if (window.internals) {
+ layoutTestController.dumpAsText();
+
+ function shadowRoot(shadowHost) {
+ return internals.ensureShadowRoot(shadowHost);
+ }
+
+ function appendChildToShadow(shadowHost, child) {
+ return shadowRoot(shadowHost).appendChild(child)
+ }
+
+ function appendShadowHost(doc, parent) {
+ var shadowHost = doc.createElement('p');
+ shadowHost.tabIndex = 1; // Makes sure that the shadow host is focusable.
+ parent = parent || doc.body;
+ return parent.appendChild(shadowHost);
+ }
+
+ var focusedElemements = [];
+
+ function focused(elem) {
+ focusedElements.push(elem)
+ }
+
+ function doFocus(elem) {
+ focusedElements = []
+ elem.focus()
+ }
+
+ function elementWasFocused(elem) {
+ for (var i = 0; i < focusedElements.length; ++i) {
+ if (focusedElements[i] == elem) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // For readability, I noted the DOM tree under the test here.
+ //
+ // - document
+ // - shadow0 (tabindex=1)
+ // - input0 (tabindex=-1)
+ // - input1
+ // - input2
+ var shadow0 = appendShadowHost(document);
+ var input0 = appendChildToShadow(shadow0, document.createElement('input'));
+ input0.tabIndex = -1
+ var input1 = appendChildToShadow(shadow0, document.createElement('input'));
+ var input2 = appendChildToShadow(shadow0, document.createElement('input'));
+
+ var elementIds = ['shadow0', 'input0', 'input1', 'input2']
+ for (var i = 0; i < elementIds.length; i++) {
+ var id = elementIds[i];
+ var element = window[id];
+ element.id = id;
+ element.addEventListener('focus', function() {focused(this);}, false);
+ }
+
+ debug('Focusing: shadow0');
+ doFocus(shadow0);
+ // Commented out due to bug 61421.
+ // shouldBeTrue('elementWasFocused(shadow0)');
+ shouldBeFalse('elementWasFocused(input0)');
+ shouldBeTrue('elementWasFocused(input1)');
+ shouldBeFalse('elementWasFocused(input2)');
+ if (window.eventSender) {
+ // Makes sure that we can change 'input1' element's value by pressing a key.
+ shouldBe('input0.value', '""');
+ shouldBe('input1.value', '""');
+ shouldBe('input2.value', '""');
+ debug('Pressing: "a"')
+ eventSender.keyDown('a');
+ shouldBe('input0.value', '""');
+ shouldBe('input1.value', '"a"');
+ shouldBe('input2.value', '""');
+ }
+
+ var successfullyParsed = true;
+}
+</script>
+<script src=""
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (90003 => 90004)
--- trunk/Source/WebCore/ChangeLog 2011-06-29 09:32:21 UTC (rev 90003)
+++ trunk/Source/WebCore/ChangeLog 2011-06-29 09:39:58 UTC (rev 90004)
@@ -1,3 +1,33 @@
+2011-06-29 Hayato Ito <[email protected]>
+
+ Reviewed by Hajime Morita.
+
+ Make a shadow host transfer a focus to the first focusable element in the shadow root when its focus() is called.
+ https://bugs.webkit.org/show_bug.cgi?id=62358.
+
+ This patch doesn't take an effect on the following elements to keep a compatibility.
+ - <input>, <textarea>, <video> and <audio> elements
+ We'll address these elements separately after re-targeting focus events
+ (bug 61421) is implemented.
+
+ A shadow root's <content> is not considered in this patch.
+ That should be addressed in another patch. See bug 63522.
+
+ Test: fast/dom/shadow/shadow-host-transfer-focus.html
+
+ * dom/Element.cpp:
+ (WebCore::Element::focus):
+ * page/FocusController.cpp:
+ (WebCore::shadowRoot):
+ (WebCore::isTreeScopeOwner):
+ (WebCore::FocusController::transferFocusToElementInShadowRoot):
+ (WebCore::hasCustomFocusLogic):
+ (WebCore::FocusController::findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot):
+ (WebCore::FocusController::advanceFocusInDocumentOrder):
+ (WebCore::ownerOfTreeScope):
+ (WebCore::FocusController::findFocusableNodeAcrossTreeScope):
+ * page/FocusController.h:
+
2011-06-29 Ryan Sleevi <[email protected]>
Reviewed by Dirk Schulze.
Modified: trunk/Source/WebCore/dom/Element.cpp (90003 => 90004)
--- trunk/Source/WebCore/dom/Element.cpp 2011-06-29 09:32:21 UTC (rev 90003)
+++ trunk/Source/WebCore/dom/Element.cpp 2011-06-29 09:39:58 UTC (rev 90004)
@@ -1610,6 +1610,8 @@
// If a focus event handler changes the focus to a different node it
// does not make sense to continue and update appearence.
protect = this;
+ if (shadowRoot() && page->focusController()->transferFocusToElementInShadowRoot(this, restorePreviousSelection))
+ return;
if (!page->focusController()->setFocusedNode(this, doc->frame()))
return;
}
Modified: trunk/Source/WebCore/page/FocusController.cpp (90003 => 90004)
--- trunk/Source/WebCore/page/FocusController.cpp 2011-06-29 09:32:21 UTC (rev 90003)
+++ trunk/Source/WebCore/page/FocusController.cpp 2011-06-29 09:39:58 UTC (rev 90004)
@@ -146,18 +146,33 @@
}
}
-inline static ShadowRoot* shadowRoot(Node* node)
+static inline ShadowRoot* shadowRoot(Node* node)
{
return node->isElementNode() ? toElement(node)->shadowRoot() : 0;
}
-inline static bool isTreeScopeOwner(Node* node)
+static inline bool isTreeScopeOwner(Node* node)
{
return node && (node->isFrameOwnerElement() || shadowRoot(node));
}
-Node* FocusController::deepFocusableNode(FocusDirection direction, Node* node, KeyboardEvent* event)
+bool FocusController::transferFocusToElementInShadowRoot(Element* shadowHost, bool restorePreviousSelection)
{
+ ASSERT(shadowRoot(shadowHost));
+ Node* node = findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(FocusDirectionForward, shadowHost, 0);
+ if (shadowHost == node)
+ return false;
+ toElement(node)->focus(restorePreviousSelection);
+ return true;
+}
+
+static inline bool hasCustomFocusLogic(Node* node)
+{
+ return node->hasTagName(inputTag) || node->hasTagName(textareaTag) || node->hasTagName(videoTag) || node->hasTagName(audioTag);
+}
+
+Node* FocusController::findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(FocusDirection direction, Node* node, KeyboardEvent* event)
+{
// 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 or shadow host.
@@ -170,11 +185,11 @@
Document* document = owner->contentFrame()->document();
foundNode = findFocusableNode(direction, document, 0, event);
} else {
+ // FIXME: Until a focus re-targeting (bug 61421) is implemented,
+ // skipping these elements is the safest way to keep a compatibility.
+ if (hasCustomFocusLogic(node))
+ break;
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)
@@ -244,7 +259,7 @@
// Chrome doesn't want focus, so we should wrap focus.
node = findFocusableNode(direction, m_page->mainFrame()->document(), 0, event);
- node = deepFocusableNode(direction, node, event);
+ node = findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(direction, node, event);
if (!node)
return false;
@@ -296,6 +311,16 @@
return true;
}
+static inline Node* ownerOfTreeScope(TreeScope* scope)
+{
+ ASSERT(scope);
+ if (scope->isShadowRoot())
+ return scope->shadowHost();
+ if (scope->document()->frame())
+ return scope->document()->frame()->ownerElement();
+ return 0;
+}
+
Node* FocusController::findFocusableNodeAcrossTreeScope(FocusDirection direction, TreeScope* scope, Node* currentNode, KeyboardEvent* event)
{
Node* node = findFocusableNode(direction, scope, currentNode, event);
@@ -307,7 +332,7 @@
node = findFocusableNode(direction, owner->treeScope(), owner, event);
scope = owner->treeScope();
}
- node = deepFocusableNode(direction, node, event);
+ node = findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(direction, node, event);
return node;
}
@@ -431,16 +456,6 @@
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 (90003 => 90004)
--- trunk/Source/WebCore/page/FocusController.h 2011-06-29 09:32:21 UTC (rev 90003)
+++ trunk/Source/WebCore/page/FocusController.h 2011-06-29 09:39:58 UTC (rev 90004)
@@ -34,6 +34,7 @@
namespace WebCore {
struct FocusCandidate;
+class Element;
class Frame;
class IntRect;
class KeyboardEvent;
@@ -61,13 +62,14 @@
void setFocused(bool);
bool isFocused() const { return m_isFocused; }
+ bool transferFocusToElementInShadowRoot(Element* shadowHost, bool restorePreviousSelection);
+
private:
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*);
+ Node* findFocusableNodeDecendingDownIntoFrameDocumentOrShadowRoot(FocusDirection, Node*, KeyboardEvent*);
// 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