Source/WebCore:
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 27 Aug 2016 22:13:44 +0000 (22:13 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 27 Aug 2016 22:13:44 +0000 (22:13 +0000)
Add adopted callback for custom elements
https://bugs.webkit.org/show_bug.cgi?id=161284

Reviewed by Antti Koivisto.

Added the support for adoptedCallback: https://dom.spec.whatwg.org/#concept-node-adopt
For now, we only support this callback on appendChild.

Test: fast/custom-elements/adopted-callback.html

* bindings/js/JSCustomElementInterface.cpp:
(WebCore::JSCustomElementInterface::invokeCallback): Added JSDOMGlobalObject* as an argument to the callback so that
we can invoke toJS on Document in invokeAdoptedCallback.
(WebCore::JSCustomElementInterface::setAdoptedCallback): Added.
(WebCore::JSCustomElementInterface::invokeAdoptedCallback): Added.
(WebCore::JSCustomElementInterface::setAttributeChangedCallback):
* bindings/js/JSCustomElementInterface.h:
(WebCore::JSCustomElementInterface::hasConnectedCallback): Added.
(WebCore::JSCustomElementInterface::hasDisconnectedCallback): Added.
(WebCore::JSCustomElementInterface::hasAdoptedCallback): Added.
* bindings/js/JSCustomElementRegistryCustom.cpp:
(WebCore::JSCustomElementRegistry::define):
* dom/CustomElementReactionQueue.cpp:
(WebCore::CustomElementReactionQueueItem::CustomElementReactionQueueItem): Added a variant that takes two documents.
(WebCore::CustomElementReactionQueueItem::invoke):
(WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded): Fixed a bug that this function was always
enqueuing a callback even when the interface didn't have connectedCallback. Also, there is no need to check
the nullity of the interface since it should never be null.
(WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded): Ditto.
(WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded): Added.
(WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded): Assert that the interface is never
null instead of exiting early.
* dom/CustomElementReactionQueue.h:
* dom/Element.cpp:
(WebCore::Element::didMoveToNewDocument): Added a call to enqueueAdoptedCallbackIfNeeded.

LayoutTests:
adoptcallback

Add adopted callback for custom elements
https://bugs.webkit.org/show_bug.cgi?id=161284

Reviewed by Antti Koivisto.

* fast/custom-elements/adopted-callback-expected.txt: Added.
* fast/custom-elements/adopted-callback.html: Added.
* fast/custom-elements/resources/document-types.js:
(const.DocumentTypes.create):

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

LayoutTests/ChangeLog
LayoutTests/fast/custom-elements/adopted-callback-expected.txt [new file with mode: 0644]
LayoutTests/fast/custom-elements/adopted-callback.html [new file with mode: 0644]
LayoutTests/fast/custom-elements/resources/document-types.js
Source/WebCore/ChangeLog
Source/WebCore/bindings/js/JSCustomElementInterface.cpp
Source/WebCore/bindings/js/JSCustomElementInterface.h
Source/WebCore/bindings/js/JSCustomElementRegistryCustom.cpp
Source/WebCore/dom/CustomElementReactionQueue.cpp
Source/WebCore/dom/CustomElementReactionQueue.h
Source/WebCore/dom/Element.cpp

index c71d158..24210ab 100644 (file)
@@ -1,3 +1,17 @@
+2016-08-27  Ryosuke Niwa  <rniwa@webkit.org>
+
+        adoptcallback
+
+        Add adopted callback for custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=161284
+
+        Reviewed by Antti Koivisto.
+
+        * fast/custom-elements/adopted-callback-expected.txt: Added.
+        * fast/custom-elements/adopted-callback.html: Added.
+        * fast/custom-elements/resources/document-types.js:
+        (const.DocumentTypes.create):
+
 2016-08-27  Youenn Fablet  <youenn@apple.com>
 
         html/dom/interfaces.html is flaky due to WebSocket test
diff --git a/LayoutTests/fast/custom-elements/adopted-callback-expected.txt b/LayoutTests/fast/custom-elements/adopted-callback-expected.txt
new file mode 100644 (file)
index 0000000..52456a4
--- /dev/null
@@ -0,0 +1,59 @@
+
+PASS Inserting a custom element into the owner document must not enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a document of a template element must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a document of a template element must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a document of a template element must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a document of a template element must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a document of a template element must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a document of a template element must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a document of a template element must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a document of a template element must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a new document must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a new document must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a new document must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a new document must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a new document must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a new document must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a new document must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a new document must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a cloned document must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a cloned document must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a cloned document must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a cloned document must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a cloned document must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a cloned document must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a cloned document must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a cloned document must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a document created by createHTMLDocument must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a HTML document created by createDocument must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a document in an iframe must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Moving a custom element from the owner document into a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Inserting an ancestor of custom element into a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Moving an ancestor of custom element from the owner document into a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a shadow tree in a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Inserting the shadow host of a custom element into a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Moving the shadow host of a custom element from the owner document into a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
+PASS Inserting a custom element into a detached shadow tree that belongs to a HTML document fetched by XHR must enqueue and invoke adoptedCallback 
diff --git a/LayoutTests/fast/custom-elements/adopted-callback.html b/LayoutTests/fast/custom-elements/adopted-callback.html
new file mode 100644 (file)
index 0000000..2dc25b3
--- /dev/null
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Custom Elements: adoptedCallback</title>
+<meta name="author" title="Ryosuke Niwa" href="mailto:rniwa@webkit.org">
+<meta name="assert" content="adoptedCallback must be enqueued whenever custom element is adopted into a new document">
+<link rel="help" href="https://w3c.github.io/webcomponents/spec/custom/#dfn-connected-callback">
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="./resources/document-types.js"></script>
+<link rel='stylesheet' href='../../resources/testharness.css'>
+</head>
+<body>
+<div id="log"></div>
+<script>
+
+var calls = [];
+class MyCustomElement extends HTMLElement {
+    connectedCallback() { calls.push('connected'); }
+    adoptedCallback(oldDocument, newDocument) { calls.push('adopted'); calls.push(oldDocument); calls.push(newDocument); }
+    disconnectedCallback() { calls.push('disconnected'); }
+}
+customElements.define('my-custom-element', MyCustomElement);
+
+test(function () {
+    var instance = document.createElement('my-custom-element');
+    calls = [];
+    document.body.appendChild(instance);
+    assert_array_equals(calls, ['connected']);
+}, 'Inserting a custom element into the owner document must not enqueue and invoke adoptedCallback');
+
+DocumentTypes.forEach(function (entry) {
+    if (entry.isOwner)
+        return;
+
+    var documentName = entry.name;
+    var getDocument = entry.create;
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            calls = [];
+            doc.documentElement.appendChild(instance);
+            assert_array_equals(calls, ['adopted', document, doc, 'connected']);
+        });
+    }, 'Inserting a custom element into a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            document.body.appendChild(instance);
+            calls = [];
+            doc.documentElement.appendChild(instance);
+            assert_array_equals(calls, ['disconnected', 'adopted', document, doc, 'connected']);
+        });
+    }, 'Moving a custom element from the owner document into a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            var parent = document.createElement('div');
+            parent.appendChild(instance);
+            calls = [];
+            doc.documentElement.appendChild(parent);
+            assert_array_equals(calls, ['adopted', document, doc, 'connected']);
+        });
+    }, 'Inserting an ancestor of custom element into a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            var parent = document.createElement('div');
+            parent.appendChild(instance);
+            document.body.appendChild(parent);
+            calls = [];
+            doc.documentElement.appendChild(parent);
+            assert_array_equals(calls, ['disconnected', 'adopted', document, doc, 'connected']);
+        });
+    }, 'Moving an ancestor of custom element from the owner document into a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
+            var shadowRoot = host.attachShadow({mode: 'closed'});
+            doc.documentElement.appendChild(host);
+
+            calls = [];
+            shadowRoot.appendChild(instance);
+            assert_array_equals(calls, ['adopted', document, doc, 'connected']);
+        });
+    }, 'Inserting a custom element into a shadow tree in a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            var host = document.createElement('div');
+            var shadowRoot = host.attachShadow({mode: 'closed'});
+            shadowRoot.appendChild(instance);
+
+            calls = [];
+            doc.documentElement.appendChild(host);
+            assert_array_equals(calls, ['adopted', document, doc, 'connected']);
+        });
+    }, 'Inserting the shadow host of a custom element into a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            var host = document.createElement('div');
+            var shadowRoot = host.attachShadow({mode: 'closed'});
+            shadowRoot.appendChild(instance);
+            document.body.appendChild(host);
+
+            calls = [];
+            doc.documentElement.appendChild(host);
+            assert_array_equals(calls, ['disconnected', 'adopted', document, doc, 'connected']);
+        });
+    }, 'Moving the shadow host of a custom element from the owner document into a ' + documentName + ' must enqueue and invoke adoptedCallback');
+
+    promise_test(function () {
+        return getDocument().then(function (doc) {
+            var instance = document.createElement('my-custom-element');
+            var host = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
+            var shadowRoot = host.attachShadow({mode: 'closed'});
+
+            calls = [];
+            shadowRoot.appendChild(instance);
+            assert_array_equals(calls, ['adopted', document, doc]);
+        });
+    }, 'Inserting a custom element into a detached shadow tree that belongs to a ' + documentName + ' must enqueue and invoke adoptedCallback');
+});
+
+</script>
+</body>
+</html>
index 07ee5a9..5749bad 100644 (file)
@@ -1,7 +1,8 @@
 const DocumentTypes = [
     {
         name: 'document',
-        create: function () { return Promise.resolve(document); }
+        create: function () { return Promise.resolve(document); },
+        isOwner: true,
     },
     {
         name: 'document of a template element',
index f920afc..4298667 100644 (file)
@@ -1,3 +1,41 @@
+2016-08-27  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Add adopted callback for custom elements
+        https://bugs.webkit.org/show_bug.cgi?id=161284
+
+        Reviewed by Antti Koivisto.
+
+        Added the support for adoptedCallback: https://dom.spec.whatwg.org/#concept-node-adopt
+        For now, we only support this callback on appendChild.
+
+        Test: fast/custom-elements/adopted-callback.html
+
+        * bindings/js/JSCustomElementInterface.cpp:
+        (WebCore::JSCustomElementInterface::invokeCallback): Added JSDOMGlobalObject* as an argument to the callback so that
+        we can invoke toJS on Document in invokeAdoptedCallback.
+        (WebCore::JSCustomElementInterface::setAdoptedCallback): Added.
+        (WebCore::JSCustomElementInterface::invokeAdoptedCallback): Added.
+        (WebCore::JSCustomElementInterface::setAttributeChangedCallback):
+        * bindings/js/JSCustomElementInterface.h:
+        (WebCore::JSCustomElementInterface::hasConnectedCallback): Added.
+        (WebCore::JSCustomElementInterface::hasDisconnectedCallback): Added.
+        (WebCore::JSCustomElementInterface::hasAdoptedCallback): Added.
+        * bindings/js/JSCustomElementRegistryCustom.cpp:
+        (WebCore::JSCustomElementRegistry::define):
+        * dom/CustomElementReactionQueue.cpp:
+        (WebCore::CustomElementReactionQueueItem::CustomElementReactionQueueItem): Added a variant that takes two documents.
+        (WebCore::CustomElementReactionQueueItem::invoke):
+        (WebCore::CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded): Fixed a bug that this function was always
+        enqueuing a callback even when the interface didn't have connectedCallback. Also, there is no need to check
+        the nullity of the interface since it should never be null.
+        (WebCore::CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded): Ditto.
+        (WebCore::CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded): Added.
+        (WebCore::CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded): Assert that the interface is never
+        null instead of exiting early.
+        * dom/CustomElementReactionQueue.h:
+        * dom/Element.cpp:
+        (WebCore::Element::didMoveToNewDocument): Added a call to enqueueAdoptedCallbackIfNeeded.
+
 2016-08-27  Yoshiaki Jitsukawa  <Yoshiaki.Jitsukawa@sony.com>
 
         Fix the !PLATFORM(WIN) && USE(CURL) build.
index 14d770a..9ba64b0 100644 (file)
@@ -150,7 +150,7 @@ void JSCustomElementInterface::upgradeElement(Element& element)
     wrappedElement->setCustomElementIsResolved(*this);
 }
 
-void JSCustomElementInterface::invokeCallback(Element& element, JSObject* callback, const WTF::Function<void(ExecState*, MarkedArgumentBuffer&)>& addArguments)
+void JSCustomElementInterface::invokeCallback(Element& element, JSObject* callback, const WTF::Function<void(ExecState*, JSDOMGlobalObject*, MarkedArgumentBuffer&)>& addArguments)
 {
     if (!canInvokeCallback())
         return;
@@ -174,7 +174,7 @@ void JSCustomElementInterface::invokeCallback(Element& element, JSObject* callba
     ASSERT(callType != CallType::None);
 
     MarkedArgumentBuffer args;
-    addArguments(state, args);
+    addArguments(state, globalObject, args);
 
     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(context, callType, callData);
 
@@ -207,6 +207,19 @@ void JSCustomElementInterface::invokeDisconnectedCallback(Element& element)
     invokeCallback(element, m_disconnectedCallback.get());
 }
 
+void JSCustomElementInterface::setAdoptedCallback(JSC::JSObject* callback)
+{
+    m_adoptedCallback = callback;
+}
+
+void JSCustomElementInterface::invokeAdoptedCallback(Element& element, Document& oldDocument, Document& newDocument)
+{
+    invokeCallback(element, m_adoptedCallback.get(), [&](ExecState* state, JSDOMGlobalObject* globalObject, MarkedArgumentBuffer& args) {
+        args.append(toJS(state, globalObject, oldDocument));
+        args.append(toJS(state, globalObject, newDocument));
+    });
+}
+
 void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes)
 {
     m_attributeChangedCallback = callback;
@@ -217,7 +230,7 @@ void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callba
 
 void JSCustomElementInterface::invokeAttributeChangedCallback(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
 {
-    invokeCallback(element, m_attributeChangedCallback.get(), [&](ExecState* state, MarkedArgumentBuffer& args) {
+    invokeCallback(element, m_attributeChangedCallback.get(), [&](ExecState* state, JSDOMGlobalObject*, MarkedArgumentBuffer& args) {
         args.append(jsStringWithCache(state, attributeName.localName()));
         args.append(jsStringOrNull(state, oldValue));
         args.append(jsStringOrNull(state, newValue));
index 52bb79e..e6811a4 100644 (file)
@@ -50,6 +50,7 @@ class PrivateName;
 namespace WebCore {
 
 class DOMWrapperWorld;
+class Document;
 class Element;
 class JSDOMGlobalObject;
 class MathMLElement;
@@ -68,11 +69,17 @@ public:
     void upgradeElement(Element&);
 
     void setConnectedCallback(JSC::JSObject*);
+    bool hasConnectedCallback() const { return !!m_connectedCallback; }
     void invokeConnectedCallback(Element&);
 
     void setDisconnectedCallback(JSC::JSObject*);
+    bool hasDisconnectedCallback() const { return !!m_disconnectedCallback; }
     void invokeDisconnectedCallback(Element&);
 
+    void setAdoptedCallback(JSC::JSObject*);
+    bool hasAdoptedCallback() const { return !!m_adoptedCallback; }
+    void invokeAdoptedCallback(Element&, Document& oldDocument, Document& newDocument);
+
     void setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes);
     bool observesAttribute(const AtomicString& name) const { return m_observedAttributes.contains(name); }
     void invokeAttributeChangedCallback(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
@@ -91,12 +98,13 @@ public:
 private:
     JSCustomElementInterface(const QualifiedName&, JSC::JSObject* callback, JSDOMGlobalObject*);
 
-    void invokeCallback(Element&, JSC::JSObject* callback, const WTF::Function<void(JSC::ExecState*, JSC::MarkedArgumentBuffer&)>& addArguments = {});
+    void invokeCallback(Element&, JSC::JSObject* callback, const WTF::Function<void(JSC::ExecState*, JSDOMGlobalObject*, JSC::MarkedArgumentBuffer&)>& addArguments = { });
 
     QualifiedName m_name;
     JSC::Weak<JSC::JSObject> m_constructor;
     JSC::Weak<JSC::JSObject> m_connectedCallback;
     JSC::Weak<JSC::JSObject> m_disconnectedCallback;
+    JSC::Weak<JSC::JSObject> m_adoptedCallback;
     JSC::Weak<JSC::JSObject> m_attributeChangedCallback;
     RefPtr<DOMWrapperWorld> m_isolatedWorld;
     Vector<RefPtr<Element>, 1> m_constructionStack;
index ed1b627..4f496e0 100644 (file)
@@ -119,10 +119,11 @@ JSValue JSCustomElementRegistry::define(ExecState& state)
     if (disconnectedCallback)
         elementInterface->setDisconnectedCallback(disconnectedCallback);
 
-    // FIXME: Add the support for adoptedCallback.
-    getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback"));
+    auto* adoptedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "adoptedCallback"));
     if (state.hadException())
         return jsUndefined();
+    if (adoptedCallback)
+        elementInterface->setAdoptedCallback(adoptedCallback);
 
     auto* attributeChangedCallback = getCustomElementCallback(state, prototypeObject, Identifier::fromString(&vm, "attributeChangedCallback"));
     if (state.hadException())
index 9e2cfad..64a7bcc 100644 (file)
@@ -46,6 +46,7 @@ public:
         ElementUpgrade,
         Connected,
         Disconnected,
+        Adopted,
         AttributeChanged,
     };
 
@@ -55,6 +56,14 @@ public:
         , m_interface(elementInterface)
     { }
 
+    CustomElementReactionQueueItem(Element& element, JSCustomElementInterface& elementInterface, Document& oldDocument, Document& newDocument)
+        : m_type(Type::Adopted)
+        , m_element(element)
+        , m_interface(elementInterface)
+        , m_oldDocument(&oldDocument)
+        , m_newDocument(&newDocument)
+    { }
+
     CustomElementReactionQueueItem(Element& element, JSCustomElementInterface& elementInterface, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
         : m_type(Type::AttributeChanged)
         , m_element(element)
@@ -76,6 +85,9 @@ public:
         case Type::Disconnected:
             m_interface->invokeDisconnectedCallback(m_element.get());
             break;
+        case Type::Adopted:
+            m_interface->invokeAdoptedCallback(m_element.get(), *m_oldDocument, *m_newDocument);
+            break;
         case Type::AttributeChanged:
             ASSERT(m_attributeName);
             m_interface->invokeAttributeChangedCallback(m_element.get(), m_attributeName.value(), m_oldValue, m_newValue);
@@ -87,6 +99,8 @@ private:
     Type m_type;
     Ref<Element> m_element;
     Ref<JSCustomElementInterface> m_interface;
+    RefPtr<Document> m_oldDocument;
+    RefPtr<Document> m_newDocument;
     Optional<QualifiedName> m_attributeName;
     AtomicString m_oldValue;
     AtomicString m_newValue;
@@ -108,8 +122,10 @@ void CustomElementReactionQueue::enqueueElementUpgrade(Element& element, JSCusto
 
 void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& element)
 {
+    ASSERT(element.isCustomElement());
     auto* elementInterface = element.customElementInterface();
-    if (!elementInterface)
+    ASSERT(elementInterface);
+    if (!elementInterface->hasConnectedCallback())
         return;
 
     if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
@@ -118,18 +134,34 @@ void CustomElementReactionQueue::enqueueConnectedCallbackIfNeeded(Element& eleme
 
 void CustomElementReactionQueue::enqueueDisconnectedCallbackIfNeeded(Element& element)
 {
+    ASSERT(element.isCustomElement());
     auto* elementInterface = element.customElementInterface();
-    if (!elementInterface)
+    ASSERT(elementInterface);
+    if (!elementInterface->hasDisconnectedCallback())
         return;
 
     if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
         queue->m_items.append({CustomElementReactionQueueItem::Type::Disconnected, element, *elementInterface});
 }
 
+void CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(Element& element, Document& oldDocument, Document& newDocument)
+{
+    ASSERT(element.isCustomElement());
+    auto* elementInterface = element.customElementInterface();
+    ASSERT(elementInterface);
+    if (!elementInterface->hasAdoptedCallback())
+        return;
+
+    if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
+        queue->m_items.append({element, *elementInterface, oldDocument, newDocument});
+}
+
 void CustomElementReactionQueue::enqueueAttributeChangedCallbackIfNeeded(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
 {
+    ASSERT(element.isCustomElement());
     auto* elementInterface = element.customElementInterface();
-    if (!elementInterface || !elementInterface->observesAttribute(attributeName.localName()))
+    ASSERT(elementInterface);
+    if (!elementInterface->observesAttribute(attributeName.localName()))
         return;
 
     if (auto* queue = CustomElementReactionStack::ensureCurrentQueue())
index 1f728bc..2ba913b 100644 (file)
@@ -34,6 +34,7 @@
 namespace WebCore {
 
 class CustomElementReactionQueueItem;
+class Document;
 class Element;
 class JSCustomElementInterface;
 class QualifiedName;
@@ -47,6 +48,7 @@ public:
     static void enqueueElementUpgrade(Element&, JSCustomElementInterface&);
     static void enqueueConnectedCallbackIfNeeded(Element&);
     static void enqueueDisconnectedCallbackIfNeeded(Element&);
+    static void enqueueAdoptedCallbackIfNeeded(Element&, Document& oldDocument, Document& newDocument);
     static void enqueueAttributeChangedCallbackIfNeeded(Element&, const QualifiedName&, const AtomicString& oldValue, const AtomicString& newValue);
 
     void invokeAll();
index 4e037e9..1e6bdaf 100644 (file)
@@ -1493,6 +1493,11 @@ void Element::didMoveToNewDocument(Document* oldDocument)
         if (hasClass())
             attributeChanged(classAttr, nullAtom, getAttribute(classAttr));
     }
+
+#if ENABLE(CUSTOM_ELEMENTS)
+    if (UNLIKELY(isCustomElement()))
+        CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(*this, *oldDocument, document());
+#endif
 }
 
 bool Element::hasAttributes() const