Input elements don't work inside shadow tree
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Sep 2016 23:45:21 +0000 (23:45 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 26 Sep 2016 23:45:21 +0000 (23:45 +0000)
https://bugs.webkit.org/show_bug.cgi?id=160427

Reviewed by Darin Adler.

Source/WebCore:

There is a bug in ComposedTreeIterator. If the iterator is initialized with an initial state where the root
is inside a shadow tree it won't iterate into slots.

If an input element is in a shadow tree it generates narrowly scoped style updates. When RenderTreeUpdater
applies such an update the update root will be inside the shadow tree and the bug will prevent the render tree
for slotted content from updating.

Added tests for both the iterator behavior and the specific symptom with input elements.

Tests: fast/shadow-dom/composed-tree-shadow-child-subtree.html
       fast/shadow-dom/input-element-in-shadow.html

* dom/ComposedTreeIterator.cpp:
(WebCore::ComposedTreeIterator::ComposedTreeIterator):

    Check and cache if the root is inside shadow tree.

(WebCore::ComposedTreeIterator::traverseNextInShadowTree):
* dom/ComposedTreeIterator.h:
(WebCore::ComposedTreeIterator::traverseNext):

    If it is, always use the shadow traversal code path.

LayoutTests:

* fast/shadow-dom/composed-tree-shadow-child-subtree-expected.txt: Added.
* fast/shadow-dom/composed-tree-shadow-child-subtree.html: Added.
* fast/shadow-dom/input-element-in-shadow-expected.html: Added.
* fast/shadow-dom/input-element-in-shadow.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@206403 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/shadow-dom/composed-tree-shadow-child-subtree-expected.txt [new file with mode: 0644]
LayoutTests/fast/shadow-dom/composed-tree-shadow-child-subtree.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/input-element-in-shadow-expected.html [new file with mode: 0644]
LayoutTests/fast/shadow-dom/input-element-in-shadow.html [new file with mode: 0644]
LayoutTests/platform/ios-simulator/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/dom/ComposedTreeIterator.cpp
Source/WebCore/dom/ComposedTreeIterator.h

index d80ac83..7e42d68 100644 (file)
@@ -1,3 +1,15 @@
+2016-09-26  Antti Koivisto  <antti@apple.com>
+
+        Input elements don't work inside shadow tree
+        https://bugs.webkit.org/show_bug.cgi?id=160427
+
+        Reviewed by Darin Adler.
+
+        * fast/shadow-dom/composed-tree-shadow-child-subtree-expected.txt: Added.
+        * fast/shadow-dom/composed-tree-shadow-child-subtree.html: Added.
+        * fast/shadow-dom/input-element-in-shadow-expected.html: Added.
+        * fast/shadow-dom/input-element-in-shadow.html: Added.
+
 2016-09-26  Ryan Haddad  <ryanhaddad@apple.com>
 
         Marking media/media-document-audio-repaint.html as flaky on Sierra.
diff --git a/LayoutTests/fast/shadow-dom/composed-tree-shadow-child-subtree-expected.txt b/LayoutTests/fast/shadow-dom/composed-tree-shadow-child-subtree-expected.txt
new file mode 100644 (file)
index 0000000..2a28487
--- /dev/null
@@ -0,0 +1,93 @@
+
+Test 1.1
+  div (shadow root)
+    div
+      slot
+        #text
+
+Shadow child 0 subtree
+  slot
+    #text
+
+Test 1.2
+  div (shadow root)
+    div
+      slot
+        div
+          #text
+
+Shadow child 0 subtree
+  slot
+    div
+      #text
+
+Test 2.1
+  div (shadow root)
+    div
+      #text
+    div
+      slot
+        #text
+
+Shadow child 0 subtree
+  #text
+
+Shadow child 1 subtree
+  slot
+    #text
+
+Test 2.2
+  div (shadow root)
+    div
+      #text
+    div
+      slot
+        div
+          #text
+
+Shadow child 0 subtree
+  #text
+
+Shadow child 1 subtree
+  slot
+    div
+      #text
+
+Test 3.1
+  div (shadow root)
+    div
+      #text
+      div (shadow root)
+        div
+          slot
+            slot
+              #text
+
+Shadow child 0 subtree
+  #text
+  div (shadow root)
+    div
+      slot
+        slot
+          #text
+
+Test 3.2
+  div (shadow root)
+    div
+      #text
+      div (shadow root)
+        div
+          slot
+            slot
+              div
+                #text
+
+Shadow child 0 subtree
+  #text
+  div (shadow root)
+    div
+      slot
+        slot
+          div
+            #text
+
diff --git a/LayoutTests/fast/shadow-dom/composed-tree-shadow-child-subtree.html b/LayoutTests/fast/shadow-dom/composed-tree-shadow-child-subtree.html
new file mode 100644 (file)
index 0000000..0323eb0
--- /dev/null
@@ -0,0 +1,57 @@
+<html>
+<script>
+if (window.testRunner)
+    testRunner.dumpAsText();
+</script>
+
+<template id=shadow1><div><slot></slot></div></template>
+<template id=shadow2><div>text</div><div><slot></slot></div></template>
+<template id=shadow3><div>text<div shadow=shadow1><slot></slot></div></div></template>
+
+
+<template test=1.1><div shadow=shadow1>text</div></template>
+<template test=1.2><div shadow=shadow1><div>text</div></div></template>
+
+<template test=2.1><div shadow=shadow2>text</div></template>
+<template test=2.2><div shadow=shadow2><div>text</div></div></template>
+
+<template test=3.1><div shadow=shadow3>text</div></template>
+<template test=3.2><div shadow=shadow3><div>text</div></div></template>
+
+<body>
+<pre id=console></pre>
+<script>
+function installShadows(tree)
+{
+    var shadowHosts = tree.querySelectorAll("[shadow]");
+    for (var i = 0; i < shadowHosts.length; ++i) {
+        var shadowId = shadowHosts[i].getAttribute("shadow");
+        var shadowContents = document.querySelector("#"+shadowId).content.cloneNode(true);
+
+        installShadows(shadowContents);
+
+        var shadowRoot = shadowHosts[i].attachShadow({ mode: "open" });
+        shadowRoot.appendChild(shadowContents);
+    }
+}
+
+var console = document.querySelector("#console");
+
+var tests = document.querySelectorAll("[test]");
+for (var i = 0; i < tests.length; ++i) {
+    var test = tests[i].content.cloneNode(true);
+    installShadows(test);
+    console.innerText += "\nTest " + tests[i].getAttribute("test") + "\n";
+    console.innerText += internals.composedTreeAsText(test);
+
+    var shadowSubtree = test.querySelector("[shadow]");
+
+    var children = shadowSubtree.shadowRoot.children;
+    for (var j = 0; j < children.length; ++j) {
+        console.innerText += "\nShadow child " + j + " subtree\n"
+        console.innerText += internals.composedTreeAsText(children[j]);
+    }
+}
+
+</script>
+</body>
diff --git a/LayoutTests/fast/shadow-dom/input-element-in-shadow-expected.html b/LayoutTests/fast/shadow-dom/input-element-in-shadow-expected.html
new file mode 100644 (file)
index 0000000..1d477e9
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+Test that input elements in shadow trees update when text is entered.
+<div>
+<input value="abc"><br>
+<textarea>def</textarea>
+</div>
+</body>
+</html>
diff --git a/LayoutTests/fast/shadow-dom/input-element-in-shadow.html b/LayoutTests/fast/shadow-dom/input-element-in-shadow.html
new file mode 100644 (file)
index 0000000..257a1d0
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body>
+Test that input elements in shadow trees update when text is entered.
+<div id="parentDiv">
+<input placeholder="distributed"><br>
+<textarea></textarea>
+</div>
+</body>
+<script>
+parentDiv.attachShadow({mode: 'open'});
+var innerDiv = document.createElement('div');
+innerDiv.appendChild(document.createElement('slot'))
+parentDiv.shadowRoot.appendChild(innerDiv);
+
+function enterText(selector, characters)
+{
+    if (window.eventSender) {
+        var input = document.querySelector(selector);
+        input.focus();
+        for (c of characters)
+            window.eventSender.keyDown(c);
+        input.blur();
+    }
+}
+
+enterText("input", "abc");
+enterText("textarea", "def");
+
+</script>
+</html>
index 06793d5..c4f1150 100644 (file)
@@ -332,6 +332,7 @@ fast/text/all-small-caps-whitespace.html [ Skip ]
 # This test relies on EventSender.keydown(), which is not supported on iOS
 webkit.org/b/155233 fast/events/max-tabindex-focus.html [ Skip ]
 fast/shadow-dom/shadow-host-removal-crash.html [ Skip ]
+fast/shadow-dom/input-element-in-shadow.html [ Skip ]
 
 # The file-wrapper part of <attachment> is not yet working on iOS
 fast/attachment/attachment-type-attribute.html [ Skip ]
index 4811df7..eee6d56 100644 (file)
@@ -1,3 +1,33 @@
+2016-09-26  Antti Koivisto  <antti@apple.com>
+
+        Input elements don't work inside shadow tree
+        https://bugs.webkit.org/show_bug.cgi?id=160427
+
+        Reviewed by Darin Adler.
+
+        There is a bug in ComposedTreeIterator. If the iterator is initialized with an initial state where the root
+        is inside a shadow tree it won't iterate into slots.
+
+        If an input element is in a shadow tree it generates narrowly scoped style updates. When RenderTreeUpdater
+        applies such an update the update root will be inside the shadow tree and the bug will prevent the render tree
+        for slotted content from updating.
+
+        Added tests for both the iterator behavior and the specific symptom with input elements.
+
+        Tests: fast/shadow-dom/composed-tree-shadow-child-subtree.html
+               fast/shadow-dom/input-element-in-shadow.html
+
+        * dom/ComposedTreeIterator.cpp:
+        (WebCore::ComposedTreeIterator::ComposedTreeIterator):
+
+            Check and cache if the root is inside shadow tree.
+
+        (WebCore::ComposedTreeIterator::traverseNextInShadowTree):
+        * dom/ComposedTreeIterator.h:
+        (WebCore::ComposedTreeIterator::traverseNext):
+
+            If it is, always use the shadow traversal code path.
+
 2016-09-26  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Seeking video doesn't update seek position
index d6ff498..7bb7612 100644 (file)
@@ -53,6 +53,7 @@ ComposedTreeIterator::Context::Context(ContainerNode& root, Node& node, SlottedT
 }
 
 ComposedTreeIterator::ComposedTreeIterator(ContainerNode& root, FirstChildTag)
+    : m_rootIsInShadowTree(root.isInShadowTree())
 {
     ASSERT(!is<ShadowRoot>(root));
 
@@ -73,6 +74,7 @@ ComposedTreeIterator::ComposedTreeIterator(ContainerNode& root, FirstChildTag)
 }
 
 ComposedTreeIterator::ComposedTreeIterator(ContainerNode& root, Node& current)
+    : m_rootIsInShadowTree(root.isInShadowTree())
 {
     ASSERT(!is<ShadowRoot>(root));
     ASSERT(!is<ShadowRoot>(current));
@@ -155,7 +157,7 @@ void ComposedTreeIterator::traverseShadowRoot(ShadowRoot& shadowRoot)
 
 void ComposedTreeIterator::traverseNextInShadowTree()
 {
-    ASSERT(m_contextStack.size() > 1);
+    ASSERT(m_contextStack.size() > 1 || m_rootIsInShadowTree);
 
     if (is<HTMLSlotElement>(current())) {
         auto& slot = downcast<HTMLSlotElement>(current());
@@ -175,8 +177,6 @@ void ComposedTreeIterator::traverseNextInShadowTree()
 
 void ComposedTreeIterator::traverseNextLeavingContext()
 {
-    ASSERT(m_contextStack.size() > 1);
-
     while (context().iterator == context().end && m_contextStack.size() > 1) {
         m_contextStack.removeLast();
         if (context().iterator == context().end)
index e717b4c..57fdc49 100644 (file)
@@ -80,6 +80,7 @@ private:
     const Context& context() const { return m_contextStack.last(); }
     Node& current() { return *context().iterator; }
 
+    bool m_rootIsInShadowTree { false };
     bool m_didDropAssertions { false };
     Vector<Context, 8> m_contextStack;
 };
@@ -96,7 +97,7 @@ inline ComposedTreeIterator& ComposedTreeIterator::traverseNext()
         return *this;
     }
 
-    if (m_contextStack.size() > 1) {
+    if (m_contextStack.size() > 1 || m_rootIsInShadowTree) {
         traverseNextInShadowTree();
         return *this;
     }
@@ -109,7 +110,7 @@ inline ComposedTreeIterator& ComposedTreeIterator::traverseNextSkippingChildren(
 {
     context().iterator.traverseNextSkippingChildren();
 
-    if (context().iterator == context().end && m_contextStack.size() > 1)
+    if (context().iterator == context().end)
         traverseNextLeavingContext();
     
     return *this;