Upgrade in-document custom elements when an element is defined.
BUG=594918
Review-Url: https://codereview.chromium.org/2023093003
Cr-Commit-Position: refs/heads/master@{#397660}
diff --git a/third_party/WebKit/Source/core/core.gypi b/third_party/WebKit/Source/core/core.gypi
index bfc2e96..9ce10cd 100644
--- a/third_party/WebKit/Source/core/core.gypi
+++ b/third_party/WebKit/Source/core/core.gypi
@@ -2630,6 +2630,8 @@
'dom/WeakIdentifierMap.h',
'dom/XMLDocument.cpp',
'dom/XMLDocument.h',
+ 'dom/custom/CEReactionsScope.cpp',
+ 'dom/custom/CEReactionsScope.h',
'dom/custom/CustomElement.cpp',
'dom/custom/CustomElement.h',
'dom/custom/CustomElementDefinition.cpp',
@@ -2642,6 +2644,8 @@
'dom/custom/CustomElementReactionQueue.h',
'dom/custom/CustomElementReactionStack.cpp',
'dom/custom/CustomElementReactionStack.h',
+ 'dom/custom/CustomElementUpgradeReaction.cpp',
+ 'dom/custom/CustomElementUpgradeReaction.h',
'dom/custom/CustomElementUpgradeSorter.cpp',
'dom/custom/CustomElementUpgradeSorter.h',
'dom/custom/CustomElementsRegistry.cpp',
@@ -3996,6 +4000,7 @@
'dom/custom/CustomElementTest.cpp',
'dom/custom/CustomElementTestHelpers.h',
'dom/custom/CustomElementUpgradeSorterTest.cpp',
+ 'dom/custom/CustomElementsRegistryTest.cpp',
'dom/shadow/FlatTreeTraversalTest.cpp',
'editing/EditingCommandTest.cpp',
'editing/EditingStrategyTest.cpp',
diff --git a/third_party/WebKit/Source/core/dom/Element.cpp b/third_party/WebKit/Source/core/dom/Element.cpp
index 6c7a4d1..b51a9c1 100644
--- a/third_party/WebKit/Source/core/dom/Element.cpp
+++ b/third_party/WebKit/Source/core/dom/Element.cpp
@@ -73,6 +73,8 @@
#include "core/dom/StyleChangeReason.h"
#include "core/dom/StyleEngine.h"
#include "core/dom/Text.h"
+#include "core/dom/custom/CustomElement.h"
+#include "core/dom/custom/CustomElementsRegistry.h"
#include "core/dom/custom/V0CustomElement.h"
#include "core/dom/custom/V0CustomElementRegistrationContext.h"
#include "core/dom/shadow/InsertionPoint.h"
@@ -1421,8 +1423,14 @@
rareData->intersectionObserverData()->activateValidIntersectionObservers(*this);
}
- if (isUpgradedV0CustomElement() && inShadowIncludingDocument())
- V0CustomElement::didAttach(this, document());
+ if (inShadowIncludingDocument()) {
+ if (getCustomElementState() != CustomElementState::Custom && CustomElement::descriptorMayMatch(*this)) {
+ if (CustomElementsRegistry* registry = CustomElement::registry(*this))
+ registry->addCandidate(this);
+ }
+ if (isUpgradedV0CustomElement())
+ V0CustomElement::didAttach(this, document());
+ }
TreeScope& scope = insertionPoint->treeScope();
if (scope != treeScope())
diff --git a/third_party/WebKit/Source/core/dom/custom/CEReactionsScope.cpp b/third_party/WebKit/Source/core/dom/custom/CEReactionsScope.cpp
new file mode 100644
index 0000000..817a919
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/custom/CEReactionsScope.cpp
@@ -0,0 +1,32 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/dom/custom/CEReactionsScope.h"
+
+#include "core/dom/Document.h"
+#include "core/dom/Element.h"
+#include "core/dom/custom/CustomElementReactionStack.h"
+#include "core/frame/FrameHost.h"
+
+namespace blink {
+
+CEReactionsScope* CEReactionsScope::s_topOfStack = nullptr;
+
+void CEReactionsScope::enqueue(
+ Element* element,
+ CustomElementReaction* reaction)
+{
+ if (!m_frameHost.get()) {
+ m_frameHost = element->document().frameHost();
+ m_frameHost->customElementReactionStack().push();
+ }
+ m_frameHost->customElementReactionStack().enqueue(element, reaction);
+}
+
+void CEReactionsScope::invokeReactions()
+{
+ m_frameHost->customElementReactionStack().popInvokingReactions();
+}
+
+} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CEReactionsScope.h b/third_party/WebKit/Source/core/dom/custom/CEReactionsScope.h
new file mode 100644
index 0000000..d210c74
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/custom/CEReactionsScope.h
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CEReactionsScope_h
+#define CEReactionsScope_h
+
+#include "core/CoreExport.h"
+#include "platform/heap/Handle.h"
+#include "wtf/Allocator.h"
+#include "wtf/Noncopyable.h"
+#include "wtf/StdLibExtras.h"
+
+namespace blink {
+
+class CustomElementReaction;
+class Element;
+class FrameHost;
+
+// https://html.spec.whatwg.org/multipage/scripting.html#cereactions
+class CORE_EXPORT CEReactionsScope final {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(CEReactionsScope);
+public:
+ static CEReactionsScope* current()
+ {
+ return s_topOfStack;
+ }
+
+ CEReactionsScope()
+ : m_prev(s_topOfStack)
+ {
+ s_topOfStack = this;
+ }
+
+ ~CEReactionsScope()
+ {
+ s_topOfStack = s_topOfStack->m_prev;
+ if (m_frameHost.get())
+ invokeReactions();
+ }
+
+ void enqueue(Element*, CustomElementReaction*);
+
+private:
+ static CEReactionsScope* s_topOfStack;
+
+ void invokeReactions();
+
+ CEReactionsScope* m_prev;
+ Member<FrameHost> m_frameHost;
+};
+
+} // namespace blink
+
+#endif // CEReactionsScope_h
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElement.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElement.cpp
index 4baaeb9..12f220e 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElement.cpp
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElement.cpp
@@ -5,16 +5,23 @@
#include "core/dom/custom/CustomElement.h"
#include "core/dom/Document.h"
-#include "core/dom/Element.h"
#include "core/dom/QualifiedName.h"
#include "core/dom/custom/V0CustomElement.h"
#include "core/dom/custom/V0CustomElementRegistrationContext.h"
+#include "core/frame/LocalDOMWindow.h"
#include "core/html/HTMLElement.h"
#include "platform/text/Character.h"
#include "wtf/text/AtomicStringHash.h"
namespace blink {
+CustomElementsRegistry* CustomElement::registry(const Element& element)
+{
+ if (LocalDOMWindow* window = element.document().domWindow())
+ return window->customElements();
+ return nullptr;
+}
+
bool CustomElement::isValidName(const AtomicString& name)
{
if (!name.length() || name[0] < 'a' || name[0] > 'z')
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElement.h b/third_party/WebKit/Source/core/dom/custom/CustomElement.h
index 7b9dccc..92e8304 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElement.h
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElement.h
@@ -6,7 +6,8 @@
#define CustomElement_h
#include "core/CoreExport.h"
-#include "core/dom/Document.h"
+#include "core/HTMLNames.h"
+#include "core/dom/Element.h"
#include "wtf/Allocator.h"
#include "wtf/text/AtomicString.h"
@@ -15,10 +16,36 @@
class Document;
class HTMLElement;
class QualifiedName;
+class CustomElementRegistry;
class CORE_EXPORT CustomElement {
STATIC_ONLY(CustomElement);
public:
+ // Retrieves the CustomElementsRegistry for Element, if any. This
+ // may be a different object for a given element over its lifetime
+ // as it moves between documents.
+ static CustomElementsRegistry* registry(const Element&);
+
+ // Returns true if element could possibly match a custom element
+ // descriptor *now*. See CustomElementDescriptor::matches for the
+ // meaning of "match". Custom element processing which depends on
+ // matching a descriptor, such as upgrade, can be skipped for
+ // elements that fail this test.
+ //
+ // Although this result is currently constant for a given element,
+ // when customized built-in elements are implemented the result
+ // will depend on the value of the 'is' attribute. In addition,
+ // these elements may stop matching descriptors after being
+ // upgraded, so use Node::getCustomElementState to detect
+ // customized elements.
+ static bool descriptorMayMatch(const Element& element)
+ {
+ // TODO(dominicc): Broaden this check when customized built-in
+ // elements are implemented.
+ return isValidName(element.localName())
+ && element.namespaceURI() == HTMLNames::xhtmlNamespaceURI;
+ }
+
static bool isValidName(const AtomicString& name);
static bool shouldCreateCustomElement(Document&, const AtomicString& localName);
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.cpp
index 0e9fb98..4c7626d3 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.cpp
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.cpp
@@ -16,4 +16,42 @@
{
}
+DEFINE_TRACE(CustomElementDefinition)
+{
+ visitor->trace(m_constructionStack);
+}
+
+// https://html.spec.whatwg.org/multipage/scripting.html#concept-upgrade-an-element
+void CustomElementDefinition::upgrade(Element* element)
+{
+ m_constructionStack.append(element);
+ size_t depth = m_constructionStack.size();
+
+ bool succeeded = runConstructor(element);
+
+ // Pop the construction stack.
+ if (m_constructionStack.last().get())
+ DCHECK_EQ(m_constructionStack.last(), element);
+ DCHECK_EQ(m_constructionStack.size(), depth); // It's a *stack*.
+ m_constructionStack.removeLast();
+
+ if (!succeeded)
+ return;
+
+ // TODO(dominicc): Turn this into an assertion when setting
+ // 'custom' moves to the HTMLElement constructor. We will need to
+ // add a bit for MARQUEE to be custom-gets-callbacks-yet-not-custom.
+ element->setCustomElementState(CustomElementState::Custom);
+
+ // TODO(dominicc): When the attributeChangedCallback is implemented,
+ // enqueue reactions for attributes here.
+ // TODO(dominicc): When the connectedCallback is implemented, enqueue
+ // reactions here, if applicable.
+}
+
+bool CustomElementDefinition::runConstructor(Element*)
+{
+ return true;
+}
+
} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.h b/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.h
index aca75ab..d922f14a 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.h
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementDefinition.h
@@ -14,6 +14,7 @@
namespace blink {
class ScriptState;
+class Element;
class CORE_EXPORT CustomElementDefinition
: public GarbageCollectedFinalized<CustomElementDefinition> {
@@ -22,19 +23,34 @@
CustomElementDefinition(const CustomElementDescriptor&);
virtual ~CustomElementDefinition();
+ DECLARE_VIRTUAL_TRACE();
+
const CustomElementDescriptor& descriptor() { return m_descriptor; }
- // TODO(yosin): To support Web Module, once we introduce abstract class
- // |CustomElementConstructor|, allows us to have JavaScript and C++
- // constructor, and ask binding layer to convert |CustomElementConstructor|
- // to |ScriptValue|, we should replace |getConstructorForScript()| by
- // |getConstructor() -> CustomElementConstructor|.
+ // TODO(yosin): To support Web Modules, introduce an abstract
+ // class |CustomElementConstructor| to allow us to have JavaScript
+ // and C++ constructors and ask the binding layer to convert
+ // |CustomElementConstructor| to |ScriptValue|. Replace
+ // |getConstructorForScript()| by |getConstructor() ->
+ // CustomElementConstructor|.
virtual ScriptValue getConstructorForScript() = 0;
- DEFINE_INLINE_VIRTUAL_TRACE() { }
+ using ConstructionStack = HeapVector<Member<Element>, 1>;
+ ConstructionStack& constructionStack()
+ {
+ return m_constructionStack;
+ }
+
+ void upgrade(Element*);
+
+protected:
+ // TODO(dominicc): Make this pure virtual when the script side is
+ // implemented.
+ virtual bool runConstructor(Element*);
private:
const CustomElementDescriptor m_descriptor;
+ ConstructionStack m_constructionStack;
};
} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptor.h b/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptor.h
index 520c559..ab51fc6 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptor.h
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptor.h
@@ -6,6 +6,7 @@
#define CustomElementDescriptor_h
#include "core/CoreExport.h"
+#include "core/dom/Element.h"
#include "wtf/Allocator.h"
#include "wtf/HashTableDeletedValueType.h"
#include "wtf/text/AtomicString.h"
@@ -13,18 +14,21 @@
namespace blink {
// Describes what elements a custom element definition applies to.
+// https://html.spec.whatwg.org/multipage/scripting.html#custom-elements-core-concepts
//
-// There are two kinds of definitions: The first has its own tag name;
-// in this case the "name" (definition name) and local name (tag name)
-// are the same. The second kind customizes a built-in element; in
-// that case, the descriptor's local name will be a built-in element
-// name, or an unknown element name that is *not* a valid custom
-// element name.
+// There are two kinds of definitions:
//
-// This type is used when the kind of custom element definition is
-// known, and generally the difference is important. For example, a
-// definition for "my-element", "my-element" must not be applied to an
-// element <button is="my-element">.
+// [Autonomous] These have their own tag name. In that case "name"
+// (the definition name) and local name (the tag name) are identical.
+//
+// [Customized built-in] The name is still a valid custom element
+// name; but the local name will be a built-in element name, or an
+// unknown element name that is *not* a valid custom element name.
+//
+// CustomElementDescriptor used when the kind of custom element
+// definition is known, and generally the difference is important. For
+// example, a definition for "my-element", "my-element" must not be
+// applied to an element <button is="my-element">.
class CORE_EXPORT CustomElementDescriptor final {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
public:
@@ -58,6 +62,16 @@
const AtomicString& name() const { return m_name; }
const AtomicString& localName() const { return m_localName; }
+ bool matches(const Element& element) const
+ {
+ return localName() == element.localName()
+ && (isAutonomous()
+ || name() == element.getAttribute(HTMLNames::isAttr))
+ && element.namespaceURI() == HTMLNames::xhtmlNamespaceURI;
+ }
+
+ bool isAutonomous() const { return m_name == m_localName; }
+
private:
AtomicString m_name;
AtomicString m_localName;
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptorTest.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptorTest.cpp
index d077ad4f..6113a51 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptorTest.cpp
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementDescriptorTest.cpp
@@ -5,12 +5,15 @@
#include "core/dom/custom/CustomElementDescriptor.h"
#include "core/dom/custom/CustomElementDescriptorHash.h"
+#include "core/dom/custom/CustomElementTestHelpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/HashSet.h"
#include "wtf/text/AtomicString.h"
namespace blink {
+class Element;
+
TEST(CustomElementDescriptorTest, equal)
{
CustomElementDescriptor myTypeExtension("my-button", "button");
@@ -40,4 +43,41 @@
<< "an unrelated descriptor should not be found in the hash set";
}
+TEST(CustomElementDescriptorTest, matches_autonomous)
+{
+ CustomElementDescriptor descriptor("a-b", "a-b");
+ Element* element = CreateElement("a-b");
+ EXPECT_TRUE(descriptor.matches(*element));
+}
+
+TEST(CustomElementDescriptorTest,
+ matches_autonomous_shouldNotMatchCustomizedBuiltInElement)
+{
+ CustomElementDescriptor descriptor("a-b", "a-b");
+ Element* element = CreateElement("futuretag").withIsAttribute("a-b");
+ EXPECT_FALSE(descriptor.matches(*element));
+}
+
+TEST(CustomElementDescriptorTest, matches_customizedBuiltIn)
+{
+ CustomElementDescriptor descriptor("a-b", "button");
+ Element* element = CreateElement("button").withIsAttribute("a-b");
+ EXPECT_TRUE(descriptor.matches(*element));
+}
+
+TEST(CustomElementDescriptorTest,
+ matches_customizedBuiltIn_shouldNotMatchAutonomousElement)
+{
+ CustomElementDescriptor descriptor("a-b", "button");
+ Element* element = CreateElement("a-b");
+ EXPECT_FALSE(descriptor.matches(*element));
+}
+
+TEST(CustomElementDescriptorTest, matches_elementNotInHTMLNamespaceDoesNotMatch)
+{
+ CustomElementDescriptor descriptor("a-b", "a-b");
+ Element* element = CreateElement("a-b").inNamespace("data:text/plain,foo");
+ EXPECT_FALSE(descriptor.matches(*element));
+}
+
} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementReactionTestHelpers.h b/third_party/WebKit/Source/core/dom/custom/CustomElementReactionTestHelpers.h
index 3ab89c4..e98e808 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementReactionTestHelpers.h
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementReactionTestHelpers.h
@@ -17,8 +17,8 @@
class Command : public GarbageCollectedFinalized<Command> {
WTF_MAKE_NONCOPYABLE(Command);
public:
- Command() { }
- virtual ~Command() { }
+ Command() = default;
+ virtual ~Command() = default;
DEFINE_INLINE_VIRTUAL_TRACE() { }
virtual void run(Element*) = 0;
};
@@ -27,7 +27,7 @@
WTF_MAKE_NONCOPYABLE(Log);
public:
Log(char what, std::vector<char>& where) : m_what(what), m_where(where) { }
- virtual ~Log() { }
+ ~Log() override = default;
void run(Element*) override { m_where.push_back(m_what); }
private:
char m_what;
@@ -38,7 +38,7 @@
WTF_MAKE_NONCOPYABLE(Recurse);
public:
Recurse(CustomElementReactionQueue* queue) : m_queue(queue) { }
- virtual ~Recurse() { }
+ ~Recurse() override = default;
DEFINE_INLINE_VIRTUAL_TRACE()
{
Command::trace(visitor);
@@ -57,7 +57,7 @@
, m_reaction(reaction)
{
}
- virtual ~Enqueue() { }
+ ~Enqueue() override = default;
DEFINE_INLINE_VIRTUAL_TRACE()
{
Command::trace(visitor);
@@ -83,7 +83,7 @@
for (auto& command : commands)
m_commands.append(command);
}
- virtual ~TestReaction() = default;
+ ~TestReaction() override = default;
DEFINE_INLINE_VIRTUAL_TRACE()
{
CustomElementReaction::trace(visitor);
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeReaction.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeReaction.cpp
new file mode 100644
index 0000000..7198c6f
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeReaction.cpp
@@ -0,0 +1,31 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/dom/custom/CustomElementUpgradeReaction.h"
+
+#include "core/dom/Element.h"
+#include "core/dom/custom/CustomElementDefinition.h"
+
+namespace blink {
+
+CustomElementUpgradeReaction::CustomElementUpgradeReaction(
+ CustomElementDefinition* definition)
+ : m_definition(definition)
+{
+}
+
+CustomElementUpgradeReaction::~CustomElementUpgradeReaction() = default;
+
+DEFINE_TRACE(CustomElementUpgradeReaction)
+{
+ CustomElementReaction::trace(visitor);
+ visitor->trace(m_definition);
+}
+
+void CustomElementUpgradeReaction::invoke(Element* element)
+{
+ m_definition->upgrade(element);
+}
+
+} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeReaction.h b/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeReaction.h
new file mode 100644
index 0000000..407cb668
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeReaction.h
@@ -0,0 +1,33 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CustomElementUpgradeReaction_h
+#define CustomElementUpgradeReaction_h
+
+#include "core/CoreExport.h"
+#include "core/dom/custom/CustomElementReaction.h"
+#include "platform/heap/Handle.h"
+#include "wtf/Noncopyable.h"
+
+namespace blink {
+
+class CustomElementDefinition;
+class Element;
+
+class CORE_EXPORT CustomElementUpgradeReaction final
+ : public CustomElementReaction {
+ WTF_MAKE_NONCOPYABLE(CustomElementUpgradeReaction);
+public:
+ CustomElementUpgradeReaction(CustomElementDefinition*);
+ ~CustomElementUpgradeReaction() override;
+ DECLARE_VIRTUAL_TRACE();
+private:
+ void invoke(Element*) override;
+
+ Member<CustomElementDefinition> m_definition;
+};
+
+} // namespace blink
+
+#endif // CustomElementUpgradeReaction_h
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeSorterTest.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeSorterTest.cpp
index 03d5ffd5..22b80ad 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeSorterTest.cpp
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementUpgradeSorterTest.cpp
@@ -20,7 +20,17 @@
namespace blink {
class CustomElementUpgradeSorterTest : public ::testing::Test {
-public:
+protected:
+ void SetUp() override
+ {
+ m_page = DummyPageHolder::create(IntSize(1, 1));
+ }
+
+ void TearDown() override
+ {
+ m_page = nullptr;
+ }
+
Element* createElementWithId(const char* localName, const char* id)
{
NonThrowableExceptionState noExceptions;
@@ -45,17 +55,6 @@
element->attachShadow(scriptState(), shadowRootInit, noExceptions);
}
-protected:
- void SetUp() override
- {
- m_page = DummyPageHolder::create(IntSize(1, 1));
- }
-
- void TearDown() override
- {
- m_page = nullptr;
- }
-
private:
OwnPtr<DummyPageHolder> m_page;
};
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.cpp
index e8fbde3..937f923 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.cpp
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.cpp
@@ -7,36 +7,40 @@
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptCustomElementDefinitionBuilder.h"
#include "core/dom/Document.h"
+#include "core/dom/Element.h"
#include "core/dom/ElementRegistrationOptions.h"
#include "core/dom/ExceptionCode.h"
+#include "core/dom/custom/CEReactionsScope.h"
#include "core/dom/custom/CustomElement.h"
#include "core/dom/custom/CustomElementDefinition.h"
#include "core/dom/custom/CustomElementDefinitionBuilder.h"
+#include "core/dom/custom/CustomElementDescriptor.h"
+#include "core/dom/custom/CustomElementUpgradeReaction.h"
+#include "core/dom/custom/CustomElementUpgradeSorter.h"
#include "core/dom/custom/V0CustomElementRegistrationContext.h"
namespace blink {
CustomElementsRegistry* CustomElementsRegistry::create(
- V0CustomElementRegistrationContext* v0)
+ Document* document)
{
- // TODO(dominicc): The window could install a new document; add a signal
- // when a window installs a new document to notify that V0 context, too.
- CustomElementsRegistry* registry = new CustomElementsRegistry(v0);
- if (v0)
- v0->setV1(registry);
+ CustomElementsRegistry* registry = new CustomElementsRegistry(document);
+ if (V0CustomElementRegistrationContext* v0Context = registry->v0())
+ v0Context->setV1(registry);
return registry;
}
-CustomElementsRegistry::CustomElementsRegistry(
- const V0CustomElementRegistrationContext* v0)
- : m_v0(v0)
+CustomElementsRegistry::CustomElementsRegistry(Document* document)
+ : m_document(document)
+ , m_upgradeCandidates(new UpgradeCandidateMap())
{
}
DEFINE_TRACE(CustomElementsRegistry)
{
visitor->trace(m_definitions);
- visitor->trace(m_v0);
+ visitor->trace(m_document);
+ visitor->trace(m_upgradeCandidates);
}
void CustomElementsRegistry::define(
@@ -61,6 +65,8 @@
const ElementRegistrationOptions& options,
ExceptionState& exceptionState)
{
+ CEReactionsScope reactions;
+
if (!builder.checkConstructorIntrinsics())
return;
@@ -108,6 +114,14 @@
m_definitions.add(descriptor.name(), definition);
CHECK(result.isNewEntry);
+ HeapVector<Member<Element>> candidates;
+ collectCandidates(descriptor, &candidates);
+ for (Element* candidate : candidates) {
+ reactions.enqueue(
+ candidate,
+ new CustomElementUpgradeReaction(definition));
+ }
+
// TODO(dominicc): Implement steps:
// 20: when-defined promise processing
}
@@ -124,11 +138,21 @@
return definition->getConstructorForScript();
}
-bool CustomElementsRegistry::v0NameIsDefined(const AtomicString& name) const
+bool CustomElementsRegistry::nameIsDefined(const AtomicString& name) const
{
- if (!m_v0)
- return false;
- return m_v0->nameIsDefined(name);
+ return m_definitions.contains(name);
+}
+
+V0CustomElementRegistrationContext* CustomElementsRegistry::v0()
+{
+ return m_document->registrationContext();
+}
+
+bool CustomElementsRegistry::v0NameIsDefined(const AtomicString& name)
+{
+ if (V0CustomElementRegistrationContext* v0Context = v0())
+ return v0Context->nameIsDefined(name);
+ return false;
}
CustomElementDefinition* CustomElementsRegistry::definitionForName(
@@ -137,4 +161,39 @@
return m_definitions.get(name);
}
+void CustomElementsRegistry::addCandidate(Element* candidate)
+{
+ const AtomicString& name = candidate->localName();
+ if (nameIsDefined(name) || v0NameIsDefined(name))
+ return;
+ UpgradeCandidateMap::iterator it = m_upgradeCandidates->find(name);
+ UpgradeCandidateSet* set;
+ if (it != m_upgradeCandidates->end()) {
+ set = it->value;
+ } else {
+ set = m_upgradeCandidates->add(name, new UpgradeCandidateSet())
+ .storedValue
+ ->value;
+ }
+ set->add(candidate);
+}
+
+void CustomElementsRegistry::collectCandidates(
+ const CustomElementDescriptor& desc,
+ HeapVector<Member<Element>>* elements)
+{
+ UpgradeCandidateMap::iterator it = m_upgradeCandidates->find(desc.name());
+ if (it == m_upgradeCandidates->end())
+ return;
+ CustomElementUpgradeSorter sorter;
+ for (Element* element : *it.get()->value) {
+ if (!element || !desc.matches(*element))
+ continue;
+ sorter.add(element);
+ }
+
+ m_upgradeCandidates->remove(it);
+ sorter.sorted(elements, m_document.get());
+}
+
} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.h b/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.h
index a3caf35..da65a760 100644
--- a/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.h
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistry.h
@@ -5,6 +5,7 @@
#ifndef CustomElementsRegistry_h
#define CustomElementsRegistry_h
+#include "base/gtest_prod_util.h"
#include "bindings/core/v8/ScriptWrappable.h"
#include "core/CoreExport.h"
#include "platform/heap/Handle.h"
@@ -16,6 +17,9 @@
class CustomElementDefinition;
class CustomElementDefinitionBuilder;
+class CustomElementDescriptor;
+class Document;
+class Element;
class ElementRegistrationOptions;
class ExceptionState;
class ScriptState;
@@ -28,8 +32,7 @@
DEFINE_WRAPPERTYPEINFO();
WTF_MAKE_NONCOPYABLE(CustomElementsRegistry);
public:
- static CustomElementsRegistry* create(
- V0CustomElementRegistrationContext*);
+ static CustomElementsRegistry* create(Document*);
void define(
ScriptState*,
@@ -45,25 +48,38 @@
ExceptionState&);
ScriptValue get(const AtomicString& name);
-
- bool nameIsDefined(const AtomicString& name) const
- {
- return m_definitions.contains(name);
- }
-
+ bool nameIsDefined(const AtomicString& name) const;
CustomElementDefinition* definitionForName(const AtomicString& name) const;
+ // TODO(dominicc): Consider broadening this API when type extensions are
+ // implemented.
+ void addCandidate(Element*);
+
DECLARE_TRACE();
private:
- CustomElementsRegistry(const V0CustomElementRegistrationContext*);
- bool v0NameIsDefined(const AtomicString&) const;
+ friend class CustomElementsRegistryTestBase;
+
+ CustomElementsRegistry(Document*);
+
+ V0CustomElementRegistrationContext* v0();
+ bool v0NameIsDefined(const AtomicString& name);
+
+ void collectCandidates(
+ const CustomElementDescriptor&,
+ HeapVector<Member<Element>>*);
using DefinitionMap =
HeapHashMap<AtomicString, Member<CustomElementDefinition>>;
DefinitionMap m_definitions;
- Member<const V0CustomElementRegistrationContext> m_v0;
+ Member<Document> m_document;
+
+ using UpgradeCandidateSet = HeapHashSet<WeakMember<Element>>;
+ using UpgradeCandidateMap = HeapHashMap<
+ AtomicString,
+ Member<UpgradeCandidateSet>>;
+ Member<UpgradeCandidateMap> m_upgradeCandidates;
};
} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistryTest.cpp b/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistryTest.cpp
new file mode 100644
index 0000000..11b541c
--- /dev/null
+++ b/third_party/WebKit/Source/core/dom/custom/CustomElementsRegistryTest.cpp
@@ -0,0 +1,319 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/dom/custom/CustomElementsRegistry.h"
+
+#include "bindings/core/v8/ExceptionState.h"
+#include "bindings/core/v8/ScriptValue.h"
+#include "core/dom/Document.h"
+#include "core/dom/Element.h"
+#include "core/dom/ElementRegistrationOptions.h"
+#include "core/dom/custom/CustomElementDefinition.h"
+#include "core/dom/custom/CustomElementDefinitionBuilder.h"
+#include "core/dom/custom/CustomElementDescriptor.h"
+#include "core/dom/custom/CustomElementTestHelpers.h"
+#include "core/dom/shadow/ShadowRoot.h"
+#include "core/dom/shadow/ShadowRootInit.h"
+#include "core/html/HTMLDocument.h"
+#include "core/testing/DummyPageHolder.h"
+#include "platform/ScriptForbiddenScope.h"
+#include "platform/heap/Handle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "wtf/text/AtomicString.h"
+#include <memory>
+
+namespace blink {
+
+class CustomElementsRegistryTestBase : public ::testing::Test {
+protected:
+ virtual Document& document() = 0;
+ virtual CustomElementsRegistry& registry() = 0;
+
+ void collectCandidates(
+ const CustomElementDescriptor& desc,
+ HeapVector<Member<Element>>* elements)
+ {
+ registry().collectCandidates(desc, elements);
+ }
+};
+
+class CustomElementsRegistryTest : public CustomElementsRegistryTestBase {
+protected:
+ void SetUp() override
+ {
+ CustomElementsRegistryTestBase::SetUp();
+
+ m_document = HTMLDocument::create();
+ m_document->appendChild(CreateElement("html").inDocument(m_document));
+
+ m_registry = CustomElementsRegistry::create(m_document);
+ }
+
+ void TearDown() override
+ {
+ m_document = nullptr;
+ m_registry = nullptr;
+ CustomElementsRegistryTestBase::TearDown();
+ }
+
+ Document& document() override { return *m_document; }
+ CustomElementsRegistry& registry() override { return *m_registry; }
+
+private:
+ Persistent<Document> m_document;
+ Persistent<CustomElementsRegistry> m_registry;
+};
+
+class CustomElementsRegistryFrameTest : public CustomElementsRegistryTestBase {
+protected:
+ void SetUp() override
+ {
+ CustomElementsRegistryTestBase::SetUp();
+ m_page.reset(DummyPageHolder::create(IntSize(1, 1)).leakPtr());
+ }
+
+ void TearDown() override
+ {
+ m_page = nullptr;
+ CustomElementsRegistryTestBase::TearDown();
+ }
+
+ Document& document() override { return m_page->document(); }
+
+ CustomElementsRegistry& registry() override
+ {
+ return *m_page->frame().localDOMWindow()->customElements();
+ }
+
+ ScriptState* scriptState()
+ {
+ return ScriptState::forMainWorld(&m_page->frame());
+ }
+
+ ShadowRoot* attachShadowTo(Element* element)
+ {
+ NonThrowableExceptionState noExceptions;
+ ShadowRootInit shadowRootInit;
+ return
+ element->attachShadow(scriptState(), shadowRootInit, noExceptions);
+ }
+
+private:
+ std::unique_ptr<DummyPageHolder> m_page;
+};
+
+TEST_F(
+ CustomElementsRegistryTest,
+ collectCandidates_shouldNotIncludeElementsRemovedFromDocument)
+{
+ Element* element = CreateElement("a-a").inDocument(&document());
+ registry().addCandidate(element);
+
+ HeapVector<Member<Element>> elements;
+ collectCandidates(
+ CustomElementDescriptor("a-a", "a-a"),
+ &elements);
+
+ EXPECT_TRUE(elements.isEmpty())
+ << "no candidates should have been found, but we have "
+ << elements.size();
+ EXPECT_FALSE(elements.contains(element))
+ << "the out-of-document candidate should not have been found";
+}
+
+TEST_F(
+ CustomElementsRegistryTest,
+ collectCandidates_shouldNotIncludeElementsInDifferentDocument)
+{
+ Element* element = CreateElement("a-a").inDocument(&document());
+ registry().addCandidate(element);
+
+ Document* otherDocument = HTMLDocument::create();
+ otherDocument->appendChild(element);
+ EXPECT_EQ(otherDocument, element->ownerDocument())
+ << "sanity: another document should have adopted an element on append";
+
+ HeapVector<Member<Element>> elements;
+ collectCandidates(
+ CustomElementDescriptor("a-a", "a-a"),
+ &elements);
+
+ EXPECT_TRUE(elements.isEmpty())
+ << "no candidates should have been found, but we have "
+ << elements.size();
+ EXPECT_FALSE(elements.contains(element))
+ << "the adopted-away candidate should not have been found";
+}
+
+TEST_F(
+ CustomElementsRegistryTest,
+ collectCandidates_shouldOnlyIncludeCandidatesMatchingDescriptor)
+{
+ CustomElementDescriptor descriptor("hello-world", "hello-world");
+
+ // Does not match: namespace is not HTML
+ Element* elementA = CreateElement("hello-world")
+ .inDocument(&document())
+ .inNamespace("data:text/date,1981-03-10");
+ // Matches
+ Element* elementB = CreateElement("hello-world").inDocument(&document());
+ // Does not match: local name is not hello-world
+ Element* elementC = CreateElement("button")
+ .inDocument(&document())
+ .withIsAttribute("hello-world");
+ document().documentElement()->appendChild(elementA);
+ elementA->appendChild(elementB);
+ elementA->appendChild(elementC);
+
+ registry().addCandidate(elementA);
+ registry().addCandidate(elementB);
+ registry().addCandidate(elementC);
+
+ HeapVector<Member<Element>> elements;
+ collectCandidates(descriptor, &elements);
+
+ EXPECT_EQ(1u, elements.size())
+ << "only one candidates should have been found";
+ EXPECT_EQ(elementB, elements[0])
+ << "the matching element should have been found";
+}
+
+TEST_F(CustomElementsRegistryTest, collectCandidates_oneCandidate)
+{
+ Element* element = CreateElement("a-a").inDocument(&document());
+ registry().addCandidate(element);
+ document().documentElement()->appendChild(element);
+
+ HeapVector<Member<Element>> elements;
+ collectCandidates(
+ CustomElementDescriptor("a-a", "a-a"),
+ &elements);
+
+ EXPECT_EQ(1u, elements.size())
+ << "exactly one candidate should have been found";
+ EXPECT_TRUE(elements.contains(element))
+ << "the candidate should be the element that was added";
+}
+
+TEST_F(CustomElementsRegistryTest, collectCandidates_shouldBeInDocumentOrder)
+{
+ CreateElement factory = CreateElement("a-a");
+ factory.inDocument(&document());
+ Element* elementA = factory.withId("a");
+ Element* elementB = factory.withId("b");
+ Element* elementC = factory.withId("c");
+
+ registry().addCandidate(elementB);
+ registry().addCandidate(elementA);
+ registry().addCandidate(elementC);
+
+ document().documentElement()->appendChild(elementA);
+ elementA->appendChild(elementB);
+ document().documentElement()->appendChild(elementC);
+
+ HeapVector<Member<Element>> elements;
+ collectCandidates(
+ CustomElementDescriptor("a-a", "a-a"),
+ &elements);
+
+ EXPECT_EQ(elementA, elements[0].get());
+ EXPECT_EQ(elementB, elements[1].get());
+ EXPECT_EQ(elementC, elements[2].get());
+}
+
+class TestCustomElementDefinition : public CustomElementDefinition {
+ WTF_MAKE_NONCOPYABLE(TestCustomElementDefinition);
+public:
+ TestCustomElementDefinition(const CustomElementDescriptor& descriptor)
+ : CustomElementDefinition(descriptor)
+ {
+ }
+
+ ~TestCustomElementDefinition() override = default;
+
+ ScriptValue getConstructorForScript() override
+ {
+ return ScriptValue();
+ }
+
+ bool runConstructor(Element* element) override
+ {
+ if (constructionStack().isEmpty()
+ || constructionStack().last() != element)
+ return false;
+ constructionStack().last().clear();
+ return true;
+ }
+};
+
+// Classes which use trace macros cannot be local because of the
+// traceImpl template.
+class LogUpgradeDefinition : public TestCustomElementDefinition {
+ WTF_MAKE_NONCOPYABLE(LogUpgradeDefinition);
+public:
+ LogUpgradeDefinition(const CustomElementDescriptor& descriptor)
+ : TestCustomElementDefinition(descriptor)
+ {
+ }
+
+ DEFINE_INLINE_VIRTUAL_TRACE()
+ {
+ TestCustomElementDefinition::trace(visitor);
+ visitor->trace(m_element);
+ }
+
+ // TODO(dominicc): Make this class collect a vector of what's
+ // upgraded; it will be useful in more tests.
+ Member<Element> m_element;
+ uint32_t m_invocationCount;
+
+ bool runConstructor(Element* element) override
+ {
+ m_invocationCount++;
+ m_element = element;
+ return TestCustomElementDefinition::runConstructor(element);
+ }
+};
+
+class LogUpgradeBuilder final : public CustomElementDefinitionBuilder {
+ STACK_ALLOCATED();
+ WTF_MAKE_NONCOPYABLE(LogUpgradeBuilder);
+public:
+ LogUpgradeBuilder() { }
+
+ bool checkConstructorIntrinsics() override { return true; }
+ bool checkConstructorNotRegistered() override { return true; }
+ bool checkPrototype() override { return true; }
+ CustomElementDefinition* build(
+ const CustomElementDescriptor& descriptor) {
+ return new LogUpgradeDefinition(descriptor);
+ }
+};
+
+TEST_F(CustomElementsRegistryFrameTest, define_upgradesInDocumentElements)
+{
+ ScriptForbiddenScope doNotRelyOnScript;
+
+ Element* element = CreateElement("a-a").inDocument(&document());
+ document().documentElement()->appendChild(element);
+
+ LogUpgradeBuilder builder;
+ NonThrowableExceptionState shouldNotThrow;
+ registry().define(
+ "a-a",
+ builder,
+ ElementRegistrationOptions(),
+ shouldNotThrow);
+ LogUpgradeDefinition* definition =
+ static_cast<LogUpgradeDefinition*>(registry().definitionForName("a-a"));
+ EXPECT_EQ(1u, definition->m_invocationCount)
+ << "defining the element should have 'upgraded' the existing element";
+ EXPECT_EQ(element, definition->m_element)
+ << "the existing a-a element should have been upgraded";
+}
+
+// TODO(dominicc): Add tests which adjust the "is" attribute when type
+// extensions are implemented.
+
+} // namespace blink
diff --git a/third_party/WebKit/Source/core/frame/FrameHost.cpp b/third_party/WebKit/Source/core/frame/FrameHost.cpp
index 7d30a33..f709370 100644
--- a/third_party/WebKit/Source/core/frame/FrameHost.cpp
+++ b/third_party/WebKit/Source/core/frame/FrameHost.cpp
@@ -30,6 +30,7 @@
#include "core/frame/FrameHost.h"
+#include "core/dom/custom/CustomElementReactionStack.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameView.h"
#include "core/frame/PageScaleConstraints.h"
@@ -61,6 +62,7 @@
m_page->chromeClient()))
, m_eventHandlerRegistry(new EventHandlerRegistry(*this))
, m_consoleMessageStorage(ConsoleMessageStorage::create())
+ , m_customElementReactionStack(new CustomElementReactionStack())
, m_subframeCount(0)
{
}
@@ -205,6 +207,16 @@
return *m_consoleMessageStorage;
}
+CustomElementReactionStack& FrameHost::customElementReactionStack()
+{
+ return *m_customElementReactionStack;
+}
+
+const CustomElementReactionStack& FrameHost::customElementReactionStack() const
+{
+ return *m_customElementReactionStack;
+}
+
DEFINE_TRACE(FrameHost)
{
visitor->trace(m_page);
@@ -214,6 +226,7 @@
visitor->trace(m_overscrollController);
visitor->trace(m_eventHandlerRegistry);
visitor->trace(m_consoleMessageStorage);
+ visitor->trace(m_customElementReactionStack);
}
#if ENABLE(ASSERT)
diff --git a/third_party/WebKit/Source/core/frame/FrameHost.h b/third_party/WebKit/Source/core/frame/FrameHost.h
index e73f3f53..247e5005 100644
--- a/third_party/WebKit/Source/core/frame/FrameHost.h
+++ b/third_party/WebKit/Source/core/frame/FrameHost.h
@@ -43,6 +43,7 @@
class ChromeClient;
class ConsoleMessageStorage;
+class CustomElementReactionStack;
class Deprecation;
class EventHandlerRegistry;
class OverscrollController;
@@ -121,6 +122,9 @@
ConsoleMessageStorage& consoleMessageStorage();
const ConsoleMessageStorage& consoleMessageStorage() const;
+ CustomElementReactionStack& customElementReactionStack();
+ const CustomElementReactionStack& customElementReactionStack() const;
+
DECLARE_TRACE();
// Don't allow more than a certain number of frames in a page.
@@ -146,6 +150,7 @@
const Member<OverscrollController> m_overscrollController;
const Member<EventHandlerRegistry> m_eventHandlerRegistry;
const Member<ConsoleMessageStorage> m_consoleMessageStorage;
+ const Member<CustomElementReactionStack> m_customElementReactionStack;
AtomicString m_overrideEncoding;
int m_subframeCount;
diff --git a/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp b/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
index 15d7f1f9..0e6f2bc 100644
--- a/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
+++ b/third_party/WebKit/Source/core/frame/LocalDOMWindow.cpp
@@ -1336,8 +1336,8 @@
CustomElementsRegistry* LocalDOMWindow::customElements() const
{
- if (!m_customElements)
- m_customElements = CustomElementsRegistry::create(document()->registrationContext());
+ if (!m_customElements && m_document)
+ m_customElements = CustomElementsRegistry::create(document());
return m_customElements;
}