Add :defined support
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 21:11:33 +0000 (21:11 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 10 Mar 2016 21:11:33 +0000 (21:11 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155108

Reviewed by Antti Koivisto.

Source/WebCore:

Added :defined pseudo class which applies to a successfully instantiated custom element or a builtin element.
A new node flag, isUnresolvedCustomElement, which was added in r197917 tracks un-upgraded / unresolved custom
elements for which :defined should not apply.

Tests: fast/custom-elements/defined-pseudo-class.html
       fast/custom-elements/defined-rule.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::constructElement): Unset isUnresolvedCustomElement now that HTMLElement's
constructor sets isUnresolvedCustomElement.
* bindings/js/JSHTMLElementCustom.cpp:
(WebCore::constructJSHTMLElement): Set isUnresolvedCustomElement to true since :defined should never apply to
a custom element inside its constructor as HTMLElement constructor does not set the defined flag:
https://w3c.github.io/webcomponents/spec/custom/#htmlelement-constructor
* css/CSSSelector.cpp:
(WebCore::CSSSelector::selectorText): Added the support for serializing :defined.
* css/CSSSelector.h:
(PseudoClassType): Added PseudoClassDefined for :defined.
* css/SelectorChecker.cpp:
(WebCore::SelectorChecker::checkOne): Added the support for :defined.
* css/SelectorCheckerTestFunctions.h:
(WebCore::isDefinedElement): Added. Returns true for any builtin element and a custom element after a successful
construction / upgrades.
* css/SelectorPseudoClassAndCompatibilityElementMap.in: Added :defined.
* cssjit/SelectorCompiler.cpp:
(WebCore::SelectorCompiler::addPseudoClassType): Added the support for :defined.

LayoutTests:

Added W3C style testharness.js tests and ref tests for :defined pseudo class.

* fast/custom-elements/defined-pseudo-class-expected.txt: Added.
* fast/custom-elements/defined-pseudo-class.html: Added.
* fast/custom-elements/defined-rule-expected.html: Added.
* fast/custom-elements/defined-rule.html: Added.

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

14 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/defined-pseudo-class.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/defined-rule-expected.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/defined-rule.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSCustomElementInterface.cpp
Source/WebCore/bindings/js/JSHTMLElementCustom.cpp
Source/WebCore/css/CSSSelector.cpp
Source/WebCore/css/CSSSelector.h
Source/WebCore/css/SelectorChecker.cpp
Source/WebCore/css/SelectorCheckerTestFunctions.h
Source/WebCore/css/SelectorPseudoClassAndCompatibilityElementMap.in
Source/WebCore/cssjit/SelectorCompiler.cpp

index 96dfa31..9f53695 100644 (file)
@@ -1,3 +1,17 @@
+2016-03-10  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add :defined support
+        https://bugs.webkit.org/show_bug.cgi?id=155108
+
+        Reviewed by Antti Koivisto.
+
+        Added W3C style testharness.js tests and ref tests for :defined pseudo class.
+
+        * fast/custom-elements/defined-pseudo-class-expected.txt: Added.
+        * fast/custom-elements/defined-pseudo-class.html: Added.
+        * fast/custom-elements/defined-rule-expected.html: Added.
+        * fast/custom-elements/defined-rule.html: Added.
+
 2016-03-10  Daniel Bates  <dabates@apple.com>
 
         CSP: Implement support for script and style nonces
diff --git a/LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt b/LayoutTests/fast/custom-elements/defined-pseudo-class-expected.txt
new file mode 100644 (file)
index 0000000..a85869c
--- /dev/null
@@ -0,0 +1,9 @@
+
+PASS The defined flag of a custom element must be set if a custom element has not been upgraded yet 
+PASS The defined flag of a custom element must be set when a custom element is successfully upgraded 
+PASS The defined flag of a custom element must be set if there is a matching definition 
+PASS The defined flag of a custom element created by HTML parser must be unset if there is no matching definition 
+PASS The defined flag of a custom element created by HTML parser must be set if there is a matching definition 
+PASS The defined flag of a custom element created by HTML parser must be set after checking the returned result is an instance of HTMLElement 
+PASS The defined flag of a custom element must be set after checking the returned result is an instance of HTMLElement when upgrading a custom element 
+
diff --git a/LayoutTests/fast/custom-elements/defined-pseudo-class.html b/LayoutTests/fast/custom-elements/defined-pseudo-class.html
new file mode 100644 (file)
index 0000000..28a4a39
--- /dev/null
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: The ':defined' pseudo-class applies to elements that are defined.</title>
+<link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"/>
+<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#the-defined-element-pseudo-class-defined">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<link rel='stylesheet' href='../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+var upgradeCandidate = document.createElement('my-element');
+
+test(function () {
+    assert_false(upgradeCandidate.matches(':defined'));
+}, 'The defined flag of a custom element must be set if a custom element has not been upgraded yet');
+
+var matchInsideConstructor;
+class MyElement extends HTMLElement {
+    constructor() {
+        super();
+        matchInsideConstructor = this.matches(':defined');
+    }
+}
+document.defineElement('my-element', MyElement);
+
+test(function () {
+    assert_true(upgradeCandidate.matches(':defined'));
+    assert_false(matchInsideConstructor, 'Upgrading a custom element must set defined flag after invoking the constructor');
+}, 'The defined flag of a custom element must be set when a custom element is successfully upgraded');
+
+test(function () {
+    var definedElement = document.createElement('my-element');
+    assert_true(definedElement.matches(':defined'));
+    assert_false(matchInsideConstructor, 'Creating a custom element must set defined flag after invoking the constructor');
+}, 'The defined flag of a custom element must be set if there is a matching definition');
+
+document.write('<my-other-element></my-other-element>');
+
+test(function () {
+    var parserCreatedUnfefinedElement = document.querySelector('my-other-element');
+    assert_false(parserCreatedUnfefinedElement.matches(':defined'));
+}, 'The defined flag of a custom element created by HTML parser must be unset if there is no matching definition');
+
+document.write('<my-element></my-element>');
+
+test(function () {
+    var parserCreatedDefinedElement = document.querySelector('my-element');
+    assert_true(parserCreatedDefinedElement.matches(':defined'));
+}, 'The defined flag of a custom element created by HTML parser must be set if there is a matching definition');
+
+class ReturnsAnotherNode extends HTMLElement {
+    constructor() {
+        super();
+        matchInsideConstructor = this.matches(':defined');
+        return document.createTextNode('');
+    }
+}
+document.defineElement('returns-another-node', ReturnsAnotherNode);
+
+document.write('<returns-another-node></returns-another-node>');
+
+test(function () {
+    assert_true(document.querySelector('returns-another-node').matches(':defined'));
+    assert_false(matchInsideConstructor,
+        'HTML parser must create a custom element with the defined flag initially unset');
+}, 'The defined flag of a custom element created by HTML parser must be set after checking the returned result is an instance of HTMLElement');
+
+test(function () {
+    var instance = document.createElement('returns-another-node-2');
+    try {
+        document.defineElement('returns-another-node-2', class extends ReturnsAnotherNode {});
+    } catch (e) { }
+    assert_false(instance.matches(':defined'));
+    assert_false(matchInsideConstructor,
+        'Creating a custom element must leave the defined flag unset when synchronous custom elements flag is not set');
+}, 'The defined flag of a custom element must be set after checking the returned result is an instance of HTMLElement when upgrading a custom element');
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/fast/custom-elements/defined-rule-expected.html b/LayoutTests/fast/custom-elements/defined-rule-expected.html
new file mode 100644 (file)
index 0000000..e704d24
--- /dev/null
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body>
+    <p>Test passes if you see a single 100px by 100px green box below.</p>
+    <div style="width: 100px; height: 100px; background: green;"></div>
+</body>
+</html>
diff --git a/LayoutTests/fast/custom-elements/defined-rule.html b/LayoutTests/fast/custom-elements/defined-rule.html
new file mode 100644 (file)
index 0000000..6335061
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Custom Elements: The ':defined' pseudo-class applies to elements that are defined.</title>
+    <link rel="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org"/>
+    <link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#the-defined-element-pseudo-class-defined">
+    <link rel="match" href="reference/green-box.html"/>
+</head>
+<body>
+    <style>
+        .box {
+            display: block;
+            width: 100px;
+            height: 25px;
+        }
+        my-defined-element { background: red; color: green; }
+        my-defined-element:defined { background: green; }
+        my-defined-element:not(:defined) { color: red; }
+
+        my-undefined-element { background: green; color: red; }
+        my-undefined-element:defined { background: red; }
+        my-undefined-element:not(:defined) { color: green; }
+
+        div.box { background: green; color: red; height: 50px; }
+        div:defined { color: green; }
+        div:not(:defined) { background: red; }
+    </style>
+    <p>Test passes if you see a single 100px by 100px green box below.</p>
+    <my-defined-element class="box">FAIL</my-defined-element>
+    <my-undefined-element class="box">FAIL</my-undefined-element>
+    <div class="box"></div>
+    <script>
+
+        document.defineElement('my-defined-element', class extends HTMLElement {});
+
+    </script>
+</body>
+</html>
index 76fc0f2..4f72c4c 100644 (file)
@@ -1,3 +1,37 @@
+2016-03-10  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add :defined support
+        https://bugs.webkit.org/show_bug.cgi?id=155108
+
+        Reviewed by Antti Koivisto.
+
+        Added :defined pseudo class which applies to a successfully instantiated custom element or a builtin element.
+        A new node flag, isUnresolvedCustomElement, which was added in r197917 tracks un-upgraded / unresolved custom
+        elements for which :defined should not apply.
+
+        Tests: fast/custom-elements/defined-pseudo-class.html
+               fast/custom-elements/defined-rule.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::constructElement): Unset isUnresolvedCustomElement now that HTMLElement's
+        constructor sets isUnresolvedCustomElement.
+        * bindings/js/JSHTMLElementCustom.cpp:
+        (WebCore::constructJSHTMLElement): Set isUnresolvedCustomElement to true since :defined should never apply to
+        a custom element inside its constructor as HTMLElement constructor does not set the defined flag:
+        https://w3c.github.io/webcomponents/spec/custom/#htmlelement-constructor
+        * css/CSSSelector.cpp:
+        (WebCore::CSSSelector::selectorText): Added the support for serializing :defined.
+        * css/CSSSelector.h:
+        (PseudoClassType): Added PseudoClassDefined for :defined.
+        * css/SelectorChecker.cpp:
+        (WebCore::SelectorChecker::checkOne): Added the support for :defined.
+        * css/SelectorCheckerTestFunctions.h:
+        (WebCore::isDefinedElement): Added. Returns true for any builtin element and a custom element after a successful
+        construction / upgrades.
+        * css/SelectorPseudoClassAndCompatibilityElementMap.in: Added :defined.
+        * cssjit/SelectorCompiler.cpp:
+        (WebCore::SelectorCompiler::addPseudoClassType): Added the support for :defined.
+
 2016-03-10  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r197943.
index 18f3941..5d4c268 100644 (file)
@@ -98,7 +98,7 @@ RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& t
     Element* wrappedElement = JSElement::toWrapped(newElement);
     if (!wrappedElement)
         return nullptr;
-    wrappedElement->setIsCustomElement();
+    wrappedElement->setCustomElementIsResolved();
     return wrappedElement;
 }
 
index bb790b4..a2b3e1d 100644 (file)
@@ -66,6 +66,7 @@ EncodedJSValue JSC_HOST_CALL constructJSHTMLElement(ExecState* state)
             return JSValue::encode(jsUndefined());
 
         Ref<HTMLElement> element = HTMLElement::create(interface->name(), document);
+        element->setIsUnresolvedCustomElement();
         auto* jsElement = JSHTMLElement::create(newElementStructure, globalObject, element.get());
         cacheWrapper(globalObject->world(), element.ptr(), jsElement);
         return JSValue::encode(jsElement);
index 6d8128a..a5b5f21 100644 (file)
@@ -643,6 +643,11 @@ String CSSSelector::selectorText(const String& rightSide) const
                 str.appendLiteral(":host");
                 break;
 #endif
+#if ENABLE(CUSTOM_ELEMENTS)
+            case CSSSelector::PseudoClassDefined:
+                str.appendLiteral(":defined");
+                break;
+#endif
             case CSSSelector::PseudoClassUnknown:
                 ASSERT_NOT_REACHED();
             }
index 3c920c6..2148a69 100644 (file)
@@ -162,6 +162,9 @@ namespace WebCore {
 #if ENABLE(SHADOW_DOM)
             PseudoClassHost,
 #endif
+#if ENABLE(CUSTOM_ELEMENTS)
+            PseudoClassDefined,
+#endif
         };
 
         enum PseudoElementType {
index 7a0665d..bb6131d 100644 (file)
@@ -991,6 +991,10 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, const LocalCont
             // :host matches based on context. Cases that reach selector checker don't match.
             return false;
 #endif
+#if ENABLE(CUSTOM_ELEMENTS)
+        case CSSSelector::PseudoClassDefined:
+            return isDefinedElement(element);
+#endif
         case CSSSelector::PseudoClassWindowInactive:
             return isWindowInactive(element);
 
index 49134a8..c42ca01 100644 (file)
@@ -63,6 +63,13 @@ ALWAYS_INLINE bool isEnabled(const Element& element)
         && !element.isDisabledFormControl();
 }
 
+#if ENABLE(CUSTOM_ELEMENTS)
+ALWAYS_INLINE bool isDefinedElement(const Element& element)
+{
+    return !element.isUnresolvedCustomElement();
+}
+#endif
+
 ALWAYS_INLINE bool isMediaDocument(const Element& element)
 {
     return element.document().isMediaDocument();
index 5f95e88..f670db6 100644 (file)
@@ -542,6 +542,11 @@ static inline FunctionType addPseudoClassType(const CSSSelector& selector, Selec
     case CSSSelector::PseudoClassEnabled:
         fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isEnabled));
         return FunctionType::SimpleSelectorChecker;
+#if ENABLE(CUSTOM_ELEMENTS)
+    case CSSSelector::PseudoClassDefined:
+        fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(isDefinedElement));
+        return FunctionType::SimpleSelectorChecker;
+#endif
     case CSSSelector::PseudoClassFocus:
         fragment.unoptimizedPseudoClasses.append(JSC::FunctionPtr(SelectorChecker::matchesFocusPseudoClass));
         return FunctionType::SimpleSelectorChecker;