ShadowContent query should be able to have fallback elements.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 19 Jan 2012 07:05:58 +0000 (07:05 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 19 Jan 2012 07:05:58 +0000 (07:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=75306

Patch by Shinya Kawanaka <shinyak@google.com> on 2012-01-18
Reviewed by Hajime Morita.

Source/WebCore:

When no elements are selected by a shadow content element selector query,
light children are selected as a fallback elements.

Test: fast/dom/shadow/shadow-contents-fallback.html

* dom/NodeRenderingContext.cpp:
(WebCore::NodeRenderingContext::NodeRenderingContext):
  Considers fallback phase. When no elements are chosen, the phase is set to 'fallback'.
(WebCore::NodeRenderingContext::nextRenderer):
  Takes fallback phase into account.
(WebCore::NodeRenderingContext::previousRenderer): ditto.
* dom/NodeRenderingContext.h:
* html/shadow/HTMLContentElement.cpp:
(WebCore::HTMLContentElement::attach):
  Calculates inclusions before attaching light children.
* html/shadow/HTMLContentElement.h:
(WebCore::HTMLContentElement::hasInclusion):

LayoutTests:

* fast/dom/shadow/shadow-contents-fallback-expected.txt: Added.
* fast/dom/shadow/shadow-contents-fallback.html: Added.

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

LayoutTests/ChangeLog
LayoutTests/fast/dom/shadow/shadow-contents-fallback-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/shadow/shadow-contents-fallback.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/dom/NodeRenderingContext.cpp
Source/WebCore/dom/NodeRenderingContext.h
Source/WebCore/html/shadow/HTMLContentElement.cpp
Source/WebCore/html/shadow/HTMLContentElement.h

index 47d9de5..63828e6 100644 (file)
@@ -1,3 +1,13 @@
+2012-01-18  Shinya Kawanaka  <shinyak@google.com>
+
+        ShadowContent query should be able to have fallback elements.
+        https://bugs.webkit.org/show_bug.cgi?id=75306
+
+        Reviewed by Hajime Morita.
+
+        * fast/dom/shadow/shadow-contents-fallback-expected.txt: Added.
+        * fast/dom/shadow/shadow-contents-fallback.html: Added.
+
 2012-01-18  Kent Tamura  <tkent@chromium.org>
 
         REGRESSION(r100111): A 'change' event does not fire when a mouse drag
diff --git a/LayoutTests/fast/dom/shadow/shadow-contents-fallback-expected.txt b/LayoutTests/fast/dom/shadow/shadow-contents-fallback-expected.txt
new file mode 100644 (file)
index 0000000..39522b4
--- /dev/null
@@ -0,0 +1,9 @@
+PASS
+PASS
+PASS
+PASS
+PASS
+PASS
+PASS
+PASS
+
diff --git a/LayoutTests/fast/dom/shadow/shadow-contents-fallback.html b/LayoutTests/fast/dom/shadow/shadow-contents-fallback.html
new file mode 100644 (file)
index 0000000..fabe64c
--- /dev/null
@@ -0,0 +1,247 @@
+ <!DOCTYPE html>
+<html>
+<head>
+<style>
+/* relative positioning ensures underlying RenderLayer */
+.container {
+    position: relative;
+}
+
+.span {
+    display: boxed-inline;
+    margin: 2px;
+    border: solid;
+}
+</style>
+<script>
+function log(message) {
+    document.getElementById('console').innerHTML += (message + "\n");
+}
+
+function removeAllChildren(elem) {
+    while (elem.firstChild)
+        elem.removeChild(elem.firstChild);
+}
+
+function cleanUp() {
+    removeAllChildren(document.getElementById('actual-container'));
+    removeAllChildren(document.getElementById('expect-container'));
+}
+
+function removeContainerLines(text) {
+    var lines = text.split('\n');
+    lines.splice(0, 2);
+    return lines.join('\n');
+}
+
+function check() {
+    var refContainerRenderTree = internals.elementRenderTreeAsText(document.getElementById('expect-container'));
+    var refRenderTree = removeContainerLines(refContainerRenderTree);
+
+    var targetContainerRenderTree = internals.elementRenderTreeAsText(document.getElementById('actual-container'));
+    var targetRenderTree = removeContainerLines(targetContainerRenderTree);
+
+    if (targetRenderTree == refRenderTree)
+        log("PASS");
+    else {
+        log("FAIL");
+        log("Expected: ");
+        log(refRenderTree);
+        log("Actual: ");
+        log(targetRenderTree);
+    }
+
+}
+
+function createSpanWithText(text) {
+    var span = document.createElement('span');
+    span.appendChild(document.createTextNode(text));
+    return span;
+}
+
+function appendShadow(target, select) {
+    var root = internals.ensureShadowRoot(target);
+
+    var content = internals.createContentElement(document);
+    content.setAttribute('select', select);
+    content.appendChild(createSpanWithText("FALLBACK"));
+
+    root.appendChild(document.createTextNode("{SHADOW: "));
+    root.appendChild(content);
+    root.appendChild(document.createTextNode("}"));
+}
+
+function appendShadowDeep(target, select) {
+    var root = internals.ensureShadowRoot(target);
+
+    var child = document.createElement("span");
+    {
+        var content = internals.createContentElement(document);
+        content.setAttribute('select', select);
+        content.appendChild(createSpanWithText("FALLBACK"));
+
+        child.appendChild(document.createTextNode("{INNER: "));
+        child.appendChild(content);
+        child.appendChild(document.createTextNode("}"));
+    }
+
+    root.appendChild(document.createTextNode("{SHADOW: "));
+    root.appendChild(child);
+    root.appendChild(document.createTextNode("}"));
+}
+
+function testFallback() {
+    var target = document.createElement('div');
+    target.innerHTML = "<span>SELECTED</span>";
+
+    appendShadow(target, "#non-element");
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>FALLBACK</span>}</div>";
+}
+
+function testFallbackDeep() {
+    var target = document.createElement('div');
+    target.innerHTML = "<span>SELECTED</span>";
+
+    appendShadowDeep(target, "#non-element");
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>{INNER: <span>FALLBACK</span>}</span>}</div>";
+}
+
+function testNonFallbackWithLightChildren() {
+    var target = document.createElement('div');
+    target.innerHTML = "<span id='selected-1'>SELECTED</span>";
+
+    appendShadow(target, "#selected-1");
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>SELECTED</span>}</div>";
+}
+
+function testNonFallbackDeepWithLightChildren() {
+    var target = document.createElement('div');
+    target.innerHTML = "<span id='selected-2'>SELECTED</span>";
+
+    appendShadowDeep(target, "#selected-2");
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>{INNER: <span>SELECTED</span>}</span>}</div>";
+}
+
+function testComplexFallback() {
+    var target = document.createElement('div');
+    appendShadow(target, '#complex-1');
+
+    var selectContent = document.createElement('span');
+    selectContent.setAttribute('id', 'complex-1');
+    {
+        selectContent.appendChild(createSpanWithText('SELECTED'));
+    }
+    appendShadow(selectContent, 'span');
+
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+    target.appendChild(selectContent);
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>{SHADOW: <span>SELECTED</span>}</span>}</div>";
+}
+
+function testComplexFallbackDeep() {
+    var target = document.createElement('div');
+    appendShadowDeep(target, '#complex-2');
+
+    var selectContent = document.createElement('span');
+    selectContent.setAttribute('id', 'complex-2');
+    {
+        selectContent.appendChild(createSpanWithText('SELECTED'));
+    }
+    appendShadowDeep(selectContent, 'span');
+
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+    target.appendChild(selectContent);
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>{INNER: <span>{SHADOW: <span>{INNER: <span>SELECTED</span>}</span>}</span>}</span>}</span></div>";
+}
+
+function testComplexFallbackAgain() {
+    var target = document.createElement('div');
+    appendShadow(target, '#complex-3');
+
+    var selectContent = document.createElement('span');
+    selectContent.setAttribute('id', 'complex-3');
+    {
+        selectContent.appendChild(createSpanWithText('SELECTED'));
+    }
+    appendShadow(selectContent, '#non-element');
+
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+    target.appendChild(selectContent);
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>{SHADOW: <span>FALLBACK</span>}</span>}</span></div>";
+}
+
+function testComplexFallbackDeepAgain() {
+    var target = document.createElement('div');
+    appendShadowDeep(target, '#complex-4');
+
+    var selectContent = document.createElement('span');
+    selectContent.setAttribute('id', 'complex-4');
+    {
+        selectContent.appendChild(createSpanWithText('SELECTED'));
+    }
+    appendShadowDeep(selectContent, '#non-element');
+
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+    target.appendChild(selectContent);
+    target.appendChild(document.createTextNode('[WONT SELECTED]'));
+
+    document.getElementById('actual-container').appendChild(target);
+    document.getElementById('expect-container').innerHTML =
+        "<div>{SHADOW: <span>{INNER: <span>{SHADOW: <span>{INNER: <span>FALLBACK</span>}</span>}</span>}</span>}</span></div>";
+}
+
+var testFuncs = [
+    testFallback,
+    testFallbackDeep,
+    testNonFallbackWithLightChildren,
+    testNonFallbackDeepWithLightChildren,
+    testComplexFallback,
+    testComplexFallbackDeep,
+    testComplexFallbackAgain,
+    testComplexFallbackDeepAgain
+]
+
+function doTest() {
+    layoutTestController.dumpAsText();
+    cleanUp();
+
+    for (var i = 0; i < testFuncs.length; ++i) {
+        testFuncs[i]();
+        check();
+        cleanUp();
+    }
+}
+</script>
+</head>
+<body onload="doTest()">
+
+<div id="actual-container" class="container"></div>
+<div id="expect-container" class="container"></div>
+<pre id="console"></pre>
+
+</body>
+</html>
index 8cdb756..588630f 100644 (file)
@@ -1,3 +1,28 @@
+2012-01-18  Shinya Kawanaka  <shinyak@google.com>
+
+        ShadowContent query should be able to have fallback elements.
+        https://bugs.webkit.org/show_bug.cgi?id=75306
+
+        Reviewed by Hajime Morita.
+
+        When no elements are selected by a shadow content element selector query,
+        light children are selected as a fallback elements.
+
+        Test: fast/dom/shadow/shadow-contents-fallback.html
+
+        * dom/NodeRenderingContext.cpp:
+        (WebCore::NodeRenderingContext::NodeRenderingContext):
+          Considers fallback phase. When no elements are chosen, the phase is set to 'fallback'.
+        (WebCore::NodeRenderingContext::nextRenderer):
+          Takes fallback phase into account.
+        (WebCore::NodeRenderingContext::previousRenderer): ditto.
+        * dom/NodeRenderingContext.h:
+        * html/shadow/HTMLContentElement.cpp:
+        (WebCore::HTMLContentElement::attach):
+          Calculates inclusions before attaching light children.
+        * html/shadow/HTMLContentElement.h:
+        (WebCore::HTMLContentElement::hasInclusion):
+
 2012-01-18  Kent Tamura  <tkent@chromium.org>
 
         REGRESSION(r100111): A 'change' event does not fire when a mouse drag
index 226c0f4..468b8bb 100644 (file)
@@ -73,12 +73,21 @@ NodeRenderingContext::NodeRenderingContext(Node* node)
                 m_phase = AttachContentForwarded;
                 m_parentNodeForRenderingAndStyle = NodeRenderingContext(m_includer).parentNodeForRenderingAndStyle();
                 return;
-            } 
-                
+            }
+
             m_phase = AttachContentLight;
             m_parentNodeForRenderingAndStyle = parent;
             return;
         }
+
+        if (parent->isContentElement()) {
+            HTMLContentElement* shadowContentElement = toHTMLContentElement(parent);
+            if (!shadowContentElement->hasInclusion()) {
+                m_phase = AttachContentFallback;
+                m_parentNodeForRenderingAndStyle = NodeRenderingContext(parent).parentNodeForRenderingAndStyle();
+                return;
+            }
+        }
     }
 
     m_parentNodeForRenderingAndStyle = parent;
@@ -176,7 +185,7 @@ RenderObject* NodeRenderingContext::nextRenderer() const
 
     // Avoid an O(n^2) problem with this function by not checking for
     // nextRenderer() when the parent element hasn't attached yet.
-    if (m_node->parentOrHostNode() && !m_node->parentOrHostNode()->attached())
+    if (m_node->parentOrHostNode() && !m_node->parentOrHostNode()->attached() && m_phase != AttachContentFallback)
         return 0;
 
     for (Node* node = m_node->nextSibling(); node; node = node->nextSibling()) {
@@ -192,6 +201,9 @@ RenderObject* NodeRenderingContext::nextRenderer() const
         }
     }
 
+    if (m_phase == AttachContentFallback)
+        return NodeRenderingContext(m_node->parentNode()).nextRenderer();
+
     return 0;
 }
 
@@ -225,6 +237,9 @@ RenderObject* NodeRenderingContext::previousRenderer() const
         }
     }
 
+    if (m_phase == AttachContentFallback)
+        return NodeRenderingContext(m_node->parentNode()).previousRenderer();
+
     return 0;
 }
 
index d314117..610420d 100644 (file)
@@ -79,6 +79,7 @@ private:
         AttachStraight,
         AttachContentLight,
         AttachContentForwarded,
+        AttachContentFallback,
     };
 
     TreeLocation m_location;
index 1c14fec..d41652f 100644 (file)
@@ -56,13 +56,18 @@ HTMLContentElement::~HTMLContentElement()
 
 void HTMLContentElement::attach()
 {
-    ASSERT(!firstChild()); // Currently doesn't support any light child.
-    HTMLElement::attach();
+    ShadowRoot* root = toShadowRoot(shadowTreeRootNode());
 
-    if (ShadowRoot* root = toShadowRoot(shadowTreeRootNode())) {
+    // Before calling StyledElement::attach, selector must be calculated.
+    if (root) {
         ContentInclusionSelector* selector = root->ensureInclusions();
         selector->unselect(m_inclusions.get());
         selector->select(this, m_inclusions.get());
+    }
+
+    HTMLElement::attach();
+
+    if (root) {
         for (ShadowInclusion* inclusion = m_inclusions->first(); inclusion; inclusion = inclusion->next())
             inclusion->content()->detach();
         for (ShadowInclusion* inclusion = m_inclusions->first(); inclusion; inclusion = inclusion->next())
index 337772b..a117a48 100644 (file)
@@ -31,6 +31,7 @@
 #ifndef HTMLContentElement_h
 #define HTMLContentElement_h
 
+#include "ContentInclusionSelector.h"
 #include "HTMLElement.h"
 #include <wtf/Forward.h>
 
@@ -59,6 +60,7 @@ public:
     void setSelect(const AtomicString&);
 
     const ShadowInclusionList* inclusions() const { return m_inclusions.get(); }
+    bool hasInclusion() const { return inclusions()->first(); }
 
 protected:
     HTMLContentElement(const QualifiedName&, Document*);