customElements.define should retrieve lifecycle callbacks
[WebKit-https.git] / Source / WebCore / bindings / js / JSCustomElementInterface.cpp
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  * Copyright (C) 2015 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27
28 #include "config.h"
29 #include "JSCustomElementInterface.h"
30
31 #if ENABLE(CUSTOM_ELEMENTS)
32
33 #include "DOMWrapperWorld.h"
34 #include "JSDOMGlobalObject.h"
35 #include "JSElement.h"
36 #include "JSHTMLElement.h"
37 #include "JSMainThreadExecState.h"
38 #include "JSMainThreadExecStateInstrumentation.h"
39 #include "ScriptExecutionContext.h"
40 #include <heap/WeakInlines.h>
41 #include <runtime/JSLock.h>
42
43 using namespace JSC;
44
45 namespace WebCore {
46
47 JSCustomElementInterface::JSCustomElementInterface(const QualifiedName& name, JSObject* constructor, JSDOMGlobalObject* globalObject)
48     : ActiveDOMCallback(globalObject->scriptExecutionContext())
49     , m_name(name)
50     , m_constructor(constructor)
51     , m_isolatedWorld(&globalObject->world())
52 {
53 }
54
55 JSCustomElementInterface::~JSCustomElementInterface()
56 {
57 }
58
59 RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& tagName, ShouldClearException shouldClearException)
60 {
61     if (!canInvokeCallback())
62         return nullptr;
63
64     Ref<JSCustomElementInterface> protectedThis(*this);
65
66     JSLockHolder lock(m_isolatedWorld->vm());
67
68     if (!m_constructor)
69         return nullptr;
70
71     ScriptExecutionContext* context = scriptExecutionContext();
72     if (!context)
73         return nullptr;
74     ASSERT(context->isDocument());
75     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(context, *m_isolatedWorld);
76     ExecState* state = globalObject->globalExec();
77
78     ConstructData constructData;
79     ConstructType constructType = m_constructor->methodTable()->getConstructData(m_constructor.get(), constructData);
80     if (constructType == ConstructType::None) {
81         ASSERT_NOT_REACHED();
82         return nullptr;
83     }
84
85     MarkedArgumentBuffer args;
86     args.append(jsStringWithCache(state, tagName));
87
88     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(context, constructType, constructData);
89     JSValue newElement = construct(state, m_constructor.get(), constructType, constructData, args);
90     InspectorInstrumentation::didCallFunction(cookie, context);
91
92     if (shouldClearException == ShouldClearException::Clear && state->hadException())
93         state->clearException();
94
95     if (newElement.isEmpty())
96         return nullptr;
97
98     Element* wrappedElement = JSElement::toWrapped(newElement);
99     if (!wrappedElement)
100         return nullptr;
101     wrappedElement->setCustomElementIsResolved();
102     return wrappedElement;
103 }
104
105 void JSCustomElementInterface::upgradeElement(Element& element)
106 {
107     ASSERT(element.isUnresolvedCustomElement());
108     if (!canInvokeCallback())
109         return;
110
111     Ref<JSCustomElementInterface> protectedThis(*this);
112     JSLockHolder lock(m_isolatedWorld->vm());
113
114     if (!m_constructor)
115         return;
116
117     ScriptExecutionContext* context = scriptExecutionContext();
118     if (!context)
119         return;
120     ASSERT(context->isDocument());
121     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(context, *m_isolatedWorld);
122     ExecState* state = globalObject->globalExec();
123     if (state->hadException())
124         return;
125
126     ConstructData constructData;
127     ConstructType constructType = m_constructor->methodTable()->getConstructData(m_constructor.get(), constructData);
128     if (constructType == ConstructType::None) {
129         ASSERT_NOT_REACHED();
130         return;
131     }
132
133     m_constructionStack.append(&element);
134
135     MarkedArgumentBuffer args;
136     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(context, constructType, constructData);
137     JSValue returnedElement = construct(state, m_constructor.get(), constructType, constructData, args);
138     InspectorInstrumentation::didCallFunction(cookie, context);
139
140     m_constructionStack.removeLast();
141
142     if (state->hadException())
143         return;
144
145     Element* wrappedElement = JSElement::toWrapped(returnedElement);
146     if (!wrappedElement || wrappedElement != &element) {
147         throwInvalidStateError(*state, "Custom element constructor failed to upgrade an element");
148         return;
149     }
150     wrappedElement->setCustomElementIsResolved();
151     ASSERT(wrappedElement->isCustomElement());
152 }
153
154 void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes)
155 {
156     m_attributeChangedCallback = callback;
157     m_observedAttributes.clear();
158     for (auto& name : observedAttributes)
159         m_observedAttributes.add(name);
160 }
161
162 void JSCustomElementInterface::attributeChanged(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
163 {
164     if (!canInvokeCallback())
165         return;
166
167     Ref<JSCustomElementInterface> protectedThis(*this);
168
169     JSLockHolder lock(m_isolatedWorld->vm());
170
171     ScriptExecutionContext* context = scriptExecutionContext();
172     if (!context)
173         return;
174
175     ASSERT(context->isDocument());
176     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(context, *m_isolatedWorld);
177     ExecState* state = globalObject->globalExec();
178
179     JSObject* jsElement = asObject(toJS(state, globalObject, element));
180
181     CallData callData;
182     CallType callType = m_attributeChangedCallback->methodTable()->getCallData(m_attributeChangedCallback.get(), callData);
183     ASSERT(callType != CallType::None);
184
185     const AtomicString& namespaceURI = attributeName.namespaceURI();
186     MarkedArgumentBuffer args;
187     args.append(jsStringWithCache(state, attributeName.localName()));
188     args.append(oldValue == nullAtom ? jsNull() : jsStringWithCache(state, oldValue));
189     args.append(newValue == nullAtom ? jsNull() : jsStringWithCache(state, newValue));
190     args.append(namespaceURI == nullAtom ? jsNull() : jsStringWithCache(state, attributeName.namespaceURI()));
191
192     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(context, callType, callData);
193
194     NakedPtr<Exception> exception;
195     JSMainThreadExecState::call(state, m_attributeChangedCallback.get(), callType, callData, jsElement, args, exception);
196
197     InspectorInstrumentation::didCallFunction(cookie, context);
198
199     if (exception)
200         reportException(state, exception);
201 }
202     
203 void JSCustomElementInterface::didUpgradeLastElementInConstructionStack()
204 {
205     m_constructionStack.last() = nullptr;
206 }
207
208 } // namespace WebCore
209
210 #endif