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;
 }