From 7df594e9f4c31de45c07ab71e47494639468061d Mon Sep 17 00:00:00 2001 From: "rniwa@webkit.org" Date: Thu, 10 Mar 2016 02:33:12 +0000 Subject: [PATCH] defineElement should upgrade existing unresolved custom elements https://bugs.webkit.org/show_bug.cgi?id=155107 Reviewed by Darin Adler. Source/WebCore: Added the support for upgrading existing unresolved custom elements when defineElement is called. The current implementation upgrades elements in the order they were created and has the issue that it keeps accumulating all elements with a hyphen in its name until defineElement is called as documented in https://github.com/w3c/webcomponents/issues/419 This patch re-purposes IsEditingTextFlag to indicate that the node is an unresolved custom element. Since isEditingText() is only called in textRendererIsNeeded only on Text nodes, it's mutually exclusive with isUnresolvedCustomElement(). The list of unresolved custom elements is kept in m_upgradeCandidatesMap, a hash map of element names to the list of unresolved elements with that name. In addition, added the logic to use HTMLElement as the interface for unresolved custom element instead of HTMLUnknownElement. Test: fast/custom-elements/upgrading/upgrading-parser-created-element.html * bindings/js/JSCustomElementInterface.cpp: (WebCore::JSCustomElementInterface::upgradeElement): Clear the flag. * bindings/js/JSDocumentCustom.cpp: (WebCore::JSDocument::defineElement): Set the unique private name to keep the interface alive before calling addElementDefinition as the call can now invoke author scripts. * dom/CustomElementDefinitions.cpp: (WebCore::CustomElementDefinitions::addElementDefinition): Upgrade existing unresolved elements kept in m_upgradeCandidatesMap. (WebCore::CustomElementDefinitions::addUpgradeCandidate): Added. * dom/CustomElementDefinitions.h: * dom/Document.cpp: (WebCore::createHTMLElementWithNameValidation): Added the code to add the unresolved custom elements to the upgrade candidates map. Also instantiate it as HTMLElement instead of HTMLUnknownElement. (WebCore::createFallbackHTMLElement): Ditto. * dom/Node.h: (WebCore::Node::setIsCustomElement): (WebCore::Node::isUnresolvedCustomElement): Added. (WebCore::Node::setIsUnresolvedCustomElement): Added. (WebCore::Node::setCustomElementIsResolved): Added. Clears IsEditingTextOrUnresolvedCustomElementFlag and sets IsCustomElement. (WebCore::Node::isEditingText): Check both IsEditingTextOrUnresolvedCustomElementFlag and IsTextFlag for safety even though it's currently only used in textRendererIsNeeded which takes Text&. * dom/make_names.pl: (defaultParametersHash): Added customElementInterfaceName as a parameter. (printWrapperFactoryCppFile): Generate the code to use customElementInterfaceName when the element for which the wrapper is created has isUnresolvedCustomElement flag set. * html/HTMLTagNames.in: Use HTMLElement for unresolved custom elements. * html/parser/HTMLConstructionSite.cpp: (WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface): Added the code to add the unresolved custom elements to the upgrade candidates map. Also instantiate it as HTMLElement instead of HTMLUnknownElement. LayoutTests: Added W3C style testharness.js tests for asynchronously defining custom elements. * fast/custom-elements/upgrading/Node-cloneNode.html: * fast/custom-elements/upgrading/upgrading-parser-created-element-expected.txt: Added. * fast/custom-elements/upgrading/upgrading-parser-created-element.html: Added. git-svn-id: http://svn.webkit.org/repository/webkit/trunk@197917 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- LayoutTests/ChangeLog | 13 +++ .../upgrading/Node-cloneNode.html | 2 +- ...rading-parser-created-element-expected.txt | 6 ++ .../upgrading-parser-created-element.html | 93 +++++++++++++++++++ Source/WebCore/ChangeLog | 57 ++++++++++++ .../bindings/js/JSCustomElementInterface.cpp | 3 +- .../WebCore/bindings/js/JSDocumentCustom.cpp | 5 +- .../WebCore/dom/CustomElementDefinitions.cpp | 28 +++++- Source/WebCore/dom/CustomElementDefinitions.h | 2 + Source/WebCore/dom/Document.cpp | 22 ++++- Source/WebCore/dom/Node.h | 22 ++++- Source/WebCore/dom/make_names.pl | 15 +++ Source/WebCore/html/HTMLTagNames.in | 1 + .../html/parser/HTMLConstructionSite.cpp | 23 +++-- 14 files changed, 274 insertions(+), 18 deletions(-) create mode 100644 LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element-expected.txt create mode 100644 LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element.html diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index 5718153368c1c..23cfe5377833b 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -1,3 +1,16 @@ +2016-03-09 Ryosuke Niwa + + defineElement should upgrade existing unresolved custom elements + https://bugs.webkit.org/show_bug.cgi?id=155107 + + Reviewed by Darin Adler. + + Added W3C style testharness.js tests for asynchronously defining custom elements. + + * fast/custom-elements/upgrading/Node-cloneNode.html: + * fast/custom-elements/upgrading/upgrading-parser-created-element-expected.txt: Added. + * fast/custom-elements/upgrading/upgrading-parser-created-element.html: Added. + 2016-03-09 Saam Barati ES6: Implement lexical scoping for function definitions in strict mode diff --git a/LayoutTests/fast/custom-elements/upgrading/Node-cloneNode.html b/LayoutTests/fast/custom-elements/upgrading/Node-cloneNode.html index 9f4fcc35798b5..95aadadf2bc03 100644 --- a/LayoutTests/fast/custom-elements/upgrading/Node-cloneNode.html +++ b/LayoutTests/fast/custom-elements/upgrading/Node-cloneNode.html @@ -1,7 +1,7 @@ -Custom Elements: Extensions to Document interface +Custom Elements: Upgrading diff --git a/LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element-expected.txt b/LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element-expected.txt new file mode 100644 index 0000000000000..680cbf610b9a3 --- /dev/null +++ b/LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element-expected.txt @@ -0,0 +1,6 @@ + +PASS Element.prototype.createElement must add an unresolved custom element to the upgrade candidates map +PASS HTMLElement constructor must throw an InvalidStateError when the top of the construction stack is marked AlreadyConstructed due to a custom element constructor constructing itself after super() call +PASS HTMLElement constructor must throw an InvalidStateError when the top of the construction stack is marked AlreadyConstructed due to a custom element constructor constructing itself before super() call +PASS Upgrading a custom element must throw an InvalidStateError when the returned element is not SameValue as the upgraded element + diff --git a/LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element.html b/LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element.html new file mode 100644 index 0000000000000..61e827d7b253e --- /dev/null +++ b/LayoutTests/fast/custom-elements/upgrading/upgrading-parser-created-element.html @@ -0,0 +1,93 @@ + + + +Custom Elements: Upgrading unresolved elements + + + + + + + + +
+ + + + + + + + diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog index f7d564f4df832..3d73db94fc855 100644 --- a/Source/WebCore/ChangeLog +++ b/Source/WebCore/ChangeLog @@ -1,3 +1,60 @@ +2016-03-09 Ryosuke Niwa + + defineElement should upgrade existing unresolved custom elements + https://bugs.webkit.org/show_bug.cgi?id=155107 + + Reviewed by Darin Adler. + + Added the support for upgrading existing unresolved custom elements when defineElement is called. + + The current implementation upgrades elements in the order they were created and has the issue that + it keeps accumulating all elements with a hyphen in its name until defineElement is called as + documented in https://github.com/w3c/webcomponents/issues/419 + + This patch re-purposes IsEditingTextFlag to indicate that the node is an unresolved custom element. + Since isEditingText() is only called in textRendererIsNeeded only on Text nodes, it's mutually + exclusive with isUnresolvedCustomElement(). + + The list of unresolved custom elements is kept in m_upgradeCandidatesMap, a hash map of element names + to the list of unresolved elements with that name. + + In addition, added the logic to use HTMLElement as the interface for unresolved custom element instead + of HTMLUnknownElement. + + Test: fast/custom-elements/upgrading/upgrading-parser-created-element.html + + * bindings/js/JSCustomElementInterface.cpp: + (WebCore::JSCustomElementInterface::upgradeElement): Clear the flag. + * bindings/js/JSDocumentCustom.cpp: + (WebCore::JSDocument::defineElement): Set the unique private name to keep the interface alive before + calling addElementDefinition as the call can now invoke author scripts. + * dom/CustomElementDefinitions.cpp: + (WebCore::CustomElementDefinitions::addElementDefinition): Upgrade existing unresolved elements kept + in m_upgradeCandidatesMap. + (WebCore::CustomElementDefinitions::addUpgradeCandidate): Added. + * dom/CustomElementDefinitions.h: + * dom/Document.cpp: + (WebCore::createHTMLElementWithNameValidation): Added the code to add the unresolved custom elements + to the upgrade candidates map. Also instantiate it as HTMLElement instead of HTMLUnknownElement. + (WebCore::createFallbackHTMLElement): Ditto. + * dom/Node.h: + (WebCore::Node::setIsCustomElement): + (WebCore::Node::isUnresolvedCustomElement): Added. + (WebCore::Node::setIsUnresolvedCustomElement): Added. + (WebCore::Node::setCustomElementIsResolved): Added. Clears IsEditingTextOrUnresolvedCustomElementFlag + and sets IsCustomElement. + (WebCore::Node::isEditingText): Check both IsEditingTextOrUnresolvedCustomElementFlag and IsTextFlag + for safety even though it's currently only used in textRendererIsNeeded which takes Text&. + * dom/make_names.pl: + (defaultParametersHash): Added customElementInterfaceName as a parameter. + (printWrapperFactoryCppFile): Generate the code to use customElementInterfaceName when the element + for which the wrapper is created has isUnresolvedCustomElement flag set. + * html/HTMLTagNames.in: Use HTMLElement for unresolved custom elements. + * html/parser/HTMLConstructionSite.cpp: + (WebCore::HTMLConstructionSite::createHTMLElementOrFindCustomElementInterface): Added the code to add + the unresolved custom elements to the upgrade candidates map. Also instantiate it as HTMLElement instead + of HTMLUnknownElement. + 2016-03-09 Enrica Casucci Retrieve additional context for some data detector link for preview and action menu. diff --git a/Source/WebCore/bindings/js/JSCustomElementInterface.cpp b/Source/WebCore/bindings/js/JSCustomElementInterface.cpp index 4823a812b7be5..18f39416312e8 100644 --- a/Source/WebCore/bindings/js/JSCustomElementInterface.cpp +++ b/Source/WebCore/bindings/js/JSCustomElementInterface.cpp @@ -104,7 +104,7 @@ RefPtr JSCustomElementInterface::constructElement(const AtomicString& t void JSCustomElementInterface::upgradeElement(Element& element) { - ASSERT(element.isCustomElement()); + ASSERT(element.isUnresolvedCustomElement()); if (!canInvokeCallback()) return; @@ -147,6 +147,7 @@ void JSCustomElementInterface::upgradeElement(Element& element) throwInvalidStateError(*state, "Custom element constructor failed to upgrade an element"); return; } + wrappedElement->setCustomElementIsResolved(); ASSERT(wrappedElement->isCustomElement()); } diff --git a/Source/WebCore/bindings/js/JSDocumentCustom.cpp b/Source/WebCore/bindings/js/JSDocumentCustom.cpp index 4b5f4a5cbf924..2ea8bb3527b34 100644 --- a/Source/WebCore/bindings/js/JSDocumentCustom.cpp +++ b/Source/WebCore/bindings/js/JSDocumentCustom.cpp @@ -180,11 +180,12 @@ JSValue JSDocument::defineElement(ExecState& state) // FIXME: 13. Let detachedCallback be Get(prototype, "detachedCallback"). Rethrow any exceptions. // FIXME: 14. Let attributeChangedCallback be Get(prototype, "attributeChangedCallback"). Rethrow any exceptions. - QualifiedName name(nullAtom, tagName, HTMLNames::xhtmlNamespaceURI); - definitions.addElementDefinition(JSCustomElementInterface::create(name, object, globalObject())); PrivateName uniquePrivateName; globalObject()->putDirect(globalObject()->vm(), uniquePrivateName, object); + QualifiedName name(nullAtom, tagName, HTMLNames::xhtmlNamespaceURI); + definitions.addElementDefinition(JSCustomElementInterface::create(name, object, globalObject())); + // FIXME: 17. Let map be registry's upgrade candidates map. // FIXME: 18. Upgrade a newly-defined element given map and definition. diff --git a/Source/WebCore/dom/CustomElementDefinitions.cpp b/Source/WebCore/dom/CustomElementDefinitions.cpp index 06d19205ebefa..2e44187f68112 100644 --- a/Source/WebCore/dom/CustomElementDefinitions.cpp +++ b/Source/WebCore/dom/CustomElementDefinitions.cpp @@ -74,7 +74,33 @@ void CustomElementDefinitions::addElementDefinition(Refname().localName(); ASSERT(!m_nameMap.contains(localName)); m_constructorMap.add(interface->constructor(), interface.ptr()); - m_nameMap.add(localName, WTFMove(interface)); + m_nameMap.add(localName, interface.copyRef()); + + auto candidateList = m_upgradeCandidatesMap.find(localName); + if (candidateList == m_upgradeCandidatesMap.end()) + return; + + Vector> list(WTFMove(candidateList->value)); + + m_upgradeCandidatesMap.remove(localName); + + for (auto& candidate : list) { + ASSERT(candidate); + interface->upgradeElement(*candidate); + } + + // We should not be adding more upgrade candidate for this local name. + ASSERT(!m_upgradeCandidatesMap.contains(localName)); +} + +void CustomElementDefinitions::addUpgradeCandidate(Element& candidate) +{ + auto result = m_upgradeCandidatesMap.ensure(candidate.localName(), [] { + return Vector>(); + }); + auto& nodeVector = result.iterator->value; + ASSERT(!nodeVector.contains(&candidate)); + nodeVector.append(&candidate); } JSCustomElementInterface* CustomElementDefinitions::findInterface(const QualifiedName& name) const diff --git a/Source/WebCore/dom/CustomElementDefinitions.h b/Source/WebCore/dom/CustomElementDefinitions.h index d2498f4913324..6de0fdcb536c4 100644 --- a/Source/WebCore/dom/CustomElementDefinitions.h +++ b/Source/WebCore/dom/CustomElementDefinitions.h @@ -49,6 +49,7 @@ class CustomElementDefinitions { WTF_MAKE_FAST_ALLOCATED; public: void addElementDefinition(Ref&&); + void addUpgradeCandidate(Element&); JSCustomElementInterface* findInterface(const QualifiedName&) const; JSCustomElementInterface* findInterface(const AtomicString&) const; @@ -59,6 +60,7 @@ class CustomElementDefinitions { static NameStatus checkName(const AtomicString& tagName); private: + HashMap>> m_upgradeCandidatesMap; HashMap> m_nameMap; HashMap m_constructorMap; }; diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index c1d43fc487989..4ec2f41498a08 100644 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -899,7 +899,18 @@ static RefPtr createHTMLElementWithNameValidation(Document& document, c return nullptr; } - return HTMLUnknownElement::create(QualifiedName(nullAtom, localName, xhtmlNamespaceURI), document); + QualifiedName qualifiedName(nullAtom, localName, xhtmlNamespaceURI); + +#if ENABLE(CUSTOM_ELEMENTS) + if (CustomElementDefinitions::checkName(localName) == CustomElementDefinitions::NameStatus::Valid) { + Ref element = HTMLElement::create(qualifiedName, document); + element->setIsUnresolvedCustomElement(); + document.ensureCustomElementDefinitions().addUpgradeCandidate(element.get()); + return WTFMove(element); + } +#endif + + return HTMLUnknownElement::create(qualifiedName, document); } RefPtr Document::createElementForBindings(const AtomicString& name, ExceptionCode& ec) @@ -1080,11 +1091,18 @@ static Ref createFallbackHTMLElement(Document& document, const Qual if (UNLIKELY(definitions)) { if (auto* interface = definitions->findInterface(name)) { Ref element = HTMLElement::create(name, document); - element->setIsCustomElement(); // Pre-upgrade element is still considered a custom element. + element->setIsUnresolvedCustomElement(); LifecycleCallbackQueue::enqueueElementUpgrade(element.get(), *interface); return element; } } + // FIXME: Should we also check the equality of prefix between the custom element and name? + if (CustomElementDefinitions::checkName(name.localName()) == CustomElementDefinitions::NameStatus::Valid) { + Ref element = HTMLElement::create(name, document); + element->setIsUnresolvedCustomElement(); + document.ensureCustomElementDefinitions().addUpgradeCandidate(element.get()); + return element; + } #endif return HTMLUnknownElement::create(name, document); } diff --git a/Source/WebCore/dom/Node.h b/Source/WebCore/dom/Node.h index e43516003f763..114ab8c25ff18 100644 --- a/Source/WebCore/dom/Node.h +++ b/Source/WebCore/dom/Node.h @@ -266,7 +266,11 @@ class Node : public EventTarget { #if ENABLE(CUSTOM_ELEMENTS) bool isCustomElement() const { return getFlag(IsCustomElement); } - void setIsCustomElement() { return setFlag(IsCustomElement); } + void setIsCustomElement() { setFlag(IsCustomElement); } + + bool isUnresolvedCustomElement() const { return isElementNode() && getFlag(IsEditingTextOrUnresolvedCustomElementFlag); } + void setIsUnresolvedCustomElement() { setFlag(IsEditingTextOrUnresolvedCustomElementFlag); } + void setCustomElementIsResolved(); #endif // Returns null, a child of ShadowRoot, or a legacy shadow root. @@ -320,7 +324,7 @@ class Node : public EventTarget { StyleChangeType styleChangeType() const { return static_cast(m_nodeFlags & StyleChangeMask); } bool childNeedsStyleRecalc() const { return getFlag(ChildNeedsStyleRecalcFlag); } bool styleIsAffectedByPreviousSibling() const { return getFlag(StyleIsAffectedByPreviousSibling); } - bool isEditingText() const { return getFlag(IsEditingTextFlag); } + bool isEditingText() const { return getFlag(IsTextFlag) && getFlag(IsEditingTextOrUnresolvedCustomElementFlag); } void setChildNeedsStyleRecalc() { setFlag(ChildNeedsStyleRecalcFlag); } void clearChildNeedsStyleRecalc() { m_nodeFlags &= ~(ChildNeedsStyleRecalcFlag | DirectChildNeedsStyleRecalcFlag); } @@ -595,7 +599,7 @@ class Node : public EventTarget { IsParsingChildrenFinishedFlag = 1 << 13, // Element StyleChangeMask = 1 << nodeStyleChangeShift | 1 << (nodeStyleChangeShift + 1) | 1 << (nodeStyleChangeShift + 2), - IsEditingTextFlag = 1 << 17, + IsEditingTextOrUnresolvedCustomElementFlag = 1 << 17, IsNamedFlowContentNodeFlag = 1 << 18, HasSyntheticAttrChildNodesFlag = 1 << 19, HasCustomStyleResolveCallbacksFlag = 1 << 20, @@ -634,7 +638,7 @@ class Node : public EventTarget { CreateHTMLElement = CreateStyledElement | IsHTMLFlag, CreateSVGElement = CreateStyledElement | IsSVGFlag | HasCustomStyleResolveCallbacksFlag, CreateDocument = CreateContainer | InDocumentFlag, - CreateEditingText = CreateText | IsEditingTextFlag, + CreateEditingText = CreateText | IsEditingTextOrUnresolvedCustomElementFlag, CreateMathMLElement = CreateStyledElement | IsMathMLFlag }; Node(Document&, ConstructionType); @@ -769,6 +773,16 @@ inline ContainerNode* Node::parentNodeGuaranteedHostFree() const return parentNode(); } +#if ENABLE(CUSTOM_ELEMENTS) + +inline void Node::setCustomElementIsResolved() +{ + clearFlag(IsEditingTextOrUnresolvedCustomElementFlag); + setFlag(IsCustomElement); +} + +#endif + } // namespace WebCore #if ENABLE(TREE_DEBUGGING) diff --git a/Source/WebCore/dom/make_names.pl b/Source/WebCore/dom/make_names.pl index 8de96d4021610..14e8329563074 100755 --- a/Source/WebCore/dom/make_names.pl +++ b/Source/WebCore/dom/make_names.pl @@ -216,6 +216,7 @@ sub defaultParametersHash 'attrsNullNamespace' => 0, 'fallbackInterfaceName' => '', 'fallbackJSInterfaceName' => '', + 'customElementInterfaceName' => '', ); } @@ -1318,6 +1319,20 @@ END populate$parameters{namespace}WrapperMap(functions); if (auto function = functions.get().get(element->localName().impl())) return function(globalObject, element); +END +; + + if ($parameters{customElementInterfaceName}) { + print F <isUnresolvedCustomElement()) + return CREATE_DOM_WRAPPER(globalObject, $parameters{customElementInterfaceName}, element.get()); +#endif +END +; + } + + print F < HTMLConstructionSite::createHTMLElementOrFindCustomElementInterf bool insideTemplateElement = !ownerDocument.frame(); RefPtr element = HTMLElementFactory::createKnownElement(localName, ownerDocument, insideTemplateElement ? nullptr : form(), true); if (UNLIKELY(!element)) { - #if ENABLE(CUSTOM_ELEMENTS) - auto* definitions = ownerDocumentForCurrentNode().customElementDefinitions(); - if (customElementInterface && UNLIKELY(definitions)) { - if (auto* interface = definitions->findInterface(localName)) { - *customElementInterface = interface; - return nullptr; + if (customElementInterface) { + auto* definitions = ownerDocument.customElementDefinitions(); + if (UNLIKELY(definitions)) { + if (auto* interface = definitions->findInterface(localName)) { + *customElementInterface = interface; + return nullptr; + } } } #else UNUSED_PARAM(customElementInterface); #endif - element = HTMLUnknownElement::create(QualifiedName(nullAtom, localName, xhtmlNamespaceURI), ownerDocumentForCurrentNode()); + QualifiedName qualifiedName(nullAtom, localName, xhtmlNamespaceURI); +#if ENABLE(CUSTOM_ELEMENTS) + if (CustomElementDefinitions::checkName(localName) == CustomElementDefinitions::NameStatus::Valid) { + element = HTMLElement::create(qualifiedName, ownerDocument); + element->setIsUnresolvedCustomElement(); + ownerDocument.ensureCustomElementDefinitions().addUpgradeCandidate(*element); + } else +#endif + element = HTMLUnknownElement::create(qualifiedName, ownerDocument); } ASSERT(element);