Update the semantics of defined-ness of custom elements per spec changes
[WebKit-https.git] / Source / WebCore / bindings / js / JSCustomElementInterface.cpp
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  * Copyright (C) 2015-2016 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 "JSDOMBinding.h"
35 #include "JSDOMGlobalObject.h"
36 #include "JSElement.h"
37 #include "JSHTMLElement.h"
38 #include "JSMainThreadExecState.h"
39 #include "JSMainThreadExecStateInstrumentation.h"
40 #include "ScriptExecutionContext.h"
41 #include <heap/WeakInlines.h>
42 #include <runtime/JSLock.h>
43
44 using namespace JSC;
45
46 namespace WebCore {
47
48 JSCustomElementInterface::JSCustomElementInterface(const QualifiedName& name, JSObject* constructor, JSDOMGlobalObject* globalObject)
49     : ActiveDOMCallback(globalObject->scriptExecutionContext())
50     , m_name(name)
51     , m_constructor(constructor)
52     , m_isolatedWorld(&globalObject->world())
53 {
54 }
55
56 JSCustomElementInterface::~JSCustomElementInterface()
57 {
58 }
59
60 static RefPtr<Element> constructCustomElementSynchronously(Document&, VM&, ExecState&, JSObject* constructor, const AtomicString& localName);
61
62 RefPtr<Element> JSCustomElementInterface::constructElement(const AtomicString& localName, ShouldClearException shouldClearException)
63 {
64     if (!canInvokeCallback())
65         return nullptr;
66
67     Ref<JSCustomElementInterface> protectedThis(*this);
68
69     VM& vm = m_isolatedWorld->vm();
70     JSLockHolder lock(vm);
71
72     if (!m_constructor)
73         return nullptr;
74
75     ScriptExecutionContext* context = scriptExecutionContext();
76     if (!context)
77         return nullptr;
78     ASSERT(context->isDocument());
79
80     auto& state = *context->execState();
81     RefPtr<Element> element = constructCustomElementSynchronously(downcast<Document>(*context), vm, state, m_constructor.get(), localName);
82     if (!element) {
83         auto* exception = vm.exception();
84         ASSERT(exception);
85         if (shouldClearException == ShouldClearException::Clear) {
86             state.clearException();
87             reportException(&state, exception);
88         }
89         return nullptr;
90     }
91
92     return element;
93 }
94
95 // https://dom.spec.whatwg.org/#concept-create-element
96 // 6. 1. If the synchronous custom elements flag is set
97 static RefPtr<Element> constructCustomElementSynchronously(Document& document, VM& vm, ExecState& state, JSObject* constructor, const AtomicString& localName)
98 {
99     auto scope = DECLARE_THROW_SCOPE(vm);
100     ConstructData constructData;
101     ConstructType constructType = constructor->methodTable()->getConstructData(constructor, constructData);
102     if (constructType == ConstructType::None) {
103         ASSERT_NOT_REACHED();
104         return nullptr;
105     }
106
107     MarkedArgumentBuffer args;
108     args.append(jsStringWithCache(&state, localName));
109
110     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(&document, constructType, constructData);
111     JSValue newElement = construct(&state, constructor, constructType, constructData, args);
112     InspectorInstrumentation::didCallFunction(cookie, &document);
113     if (vm.exception())
114         return nullptr;
115
116     ASSERT(!newElement.isEmpty());
117     HTMLElement* wrappedElement = JSHTMLElement::toWrapped(newElement);
118     if (!wrappedElement) {
119         throwTypeError(&state, scope, ASCIILiteral("The result of constructing a custom element must be a HTMLElement"));
120         return nullptr;
121     }
122
123     if (wrappedElement->hasAttributes()) {
124         throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element must not have attributes"));
125         return nullptr;
126     }
127     if (wrappedElement->hasChildNodes()) {
128         throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element must not have child nodes"));
129         return nullptr;
130     }
131     if (wrappedElement->parentNode()) {
132         throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element must not have a parent node"));
133         return nullptr;
134     }
135     if (&wrappedElement->document() != &document) {
136         throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element belongs to a wrong docuemnt"));
137         return nullptr;
138     }
139     ASSERT(wrappedElement->namespaceURI() == HTMLNames::xhtmlNamespaceURI);
140     if (wrappedElement->localName() != localName) {
141         throwNotSupportedError(state, scope, ASCIILiteral("A newly constructed custom element belongs to a wrong docuemnt"));
142         return nullptr;
143     }
144
145     return wrappedElement;
146 }
147
148 void JSCustomElementInterface::upgradeElement(Element& element)
149 {
150     ASSERT(element.tagQName() == name());
151     ASSERT(element.isCustomElementUpgradeCandidate());
152     if (!canInvokeCallback())
153         return;
154
155     Ref<JSCustomElementInterface> protectedThis(*this);
156     VM& vm = m_isolatedWorld->vm();
157     JSLockHolder lock(vm);
158     auto scope = DECLARE_THROW_SCOPE(vm);
159
160     if (!m_constructor)
161         return;
162
163     ScriptExecutionContext* context = scriptExecutionContext();
164     if (!context)
165         return;
166     ASSERT(context->isDocument());
167     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(context, *m_isolatedWorld);
168     ExecState* state = globalObject->globalExec();
169     if (state->hadException())
170         return;
171
172     ConstructData constructData;
173     ConstructType constructType = m_constructor->methodTable()->getConstructData(m_constructor.get(), constructData);
174     if (constructType == ConstructType::None) {
175         ASSERT_NOT_REACHED();
176         return;
177     }
178
179     m_constructionStack.append(&element);
180
181     MarkedArgumentBuffer args;
182     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionConstruct(context, constructType, constructData);
183     JSValue returnedElement = construct(state, m_constructor.get(), constructType, constructData, args);
184     InspectorInstrumentation::didCallFunction(cookie, context);
185
186     m_constructionStack.removeLast();
187
188     if (state->hadException()) {
189         element.setIsFailedCustomElement(*this);
190         return;
191     }
192
193     Element* wrappedElement = JSElement::toWrapped(returnedElement);
194     if (!wrappedElement || wrappedElement != &element) {
195         element.setIsFailedCustomElement(*this);
196         throwInvalidStateError(*state, scope, "Custom element constructor failed to upgrade an element");
197         return;
198     }
199     element.setIsDefinedCustomElement(*this);
200 }
201
202 void JSCustomElementInterface::invokeCallback(Element& element, JSObject* callback, const WTF::Function<void(ExecState*, JSDOMGlobalObject*, MarkedArgumentBuffer&)>& addArguments)
203 {
204     if (!canInvokeCallback())
205         return;
206
207     auto* context = scriptExecutionContext();
208     if (!context)
209         return;
210
211     Ref<JSCustomElementInterface> protectedThis(*this);
212     JSLockHolder lock(m_isolatedWorld->vm());
213
214     ASSERT(context);
215     ASSERT(context->isDocument());
216     JSDOMGlobalObject* globalObject = toJSDOMGlobalObject(context, *m_isolatedWorld);
217     ExecState* state = globalObject->globalExec();
218
219     JSObject* jsElement = asObject(toJS(state, globalObject, element));
220
221     CallData callData;
222     CallType callType = callback->methodTable()->getCallData(callback, callData);
223     ASSERT(callType != CallType::None);
224
225     MarkedArgumentBuffer args;
226     addArguments(state, globalObject, args);
227
228     InspectorInstrumentationCookie cookie = JSMainThreadExecState::instrumentFunctionCall(context, callType, callData);
229
230     NakedPtr<Exception> exception;
231     JSMainThreadExecState::call(state, callback, callType, callData, jsElement, args, exception);
232
233     InspectorInstrumentation::didCallFunction(cookie, context);
234
235     if (exception)
236         reportException(state, exception);
237 }
238
239 void JSCustomElementInterface::setConnectedCallback(JSC::JSObject* callback)
240 {
241     m_connectedCallback = callback;
242 }
243
244 void JSCustomElementInterface::invokeConnectedCallback(Element& element)
245 {
246     invokeCallback(element, m_connectedCallback.get());
247 }
248
249 void JSCustomElementInterface::setDisconnectedCallback(JSC::JSObject* callback)
250 {
251     m_disconnectedCallback = callback;
252 }
253
254 void JSCustomElementInterface::invokeDisconnectedCallback(Element& element)
255 {
256     invokeCallback(element, m_disconnectedCallback.get());
257 }
258
259 void JSCustomElementInterface::setAdoptedCallback(JSC::JSObject* callback)
260 {
261     m_adoptedCallback = callback;
262 }
263
264 void JSCustomElementInterface::invokeAdoptedCallback(Element& element, Document& oldDocument, Document& newDocument)
265 {
266     invokeCallback(element, m_adoptedCallback.get(), [&](ExecState* state, JSDOMGlobalObject* globalObject, MarkedArgumentBuffer& args) {
267         args.append(toJS(state, globalObject, oldDocument));
268         args.append(toJS(state, globalObject, newDocument));
269     });
270 }
271
272 void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes)
273 {
274     m_attributeChangedCallback = callback;
275     m_observedAttributes.clear();
276     for (auto& name : observedAttributes)
277         m_observedAttributes.add(name);
278 }
279
280 void JSCustomElementInterface::invokeAttributeChangedCallback(Element& element, const QualifiedName& attributeName, const AtomicString& oldValue, const AtomicString& newValue)
281 {
282     invokeCallback(element, m_attributeChangedCallback.get(), [&](ExecState* state, JSDOMGlobalObject*, MarkedArgumentBuffer& args) {
283         args.append(jsStringWithCache(state, attributeName.localName()));
284         args.append(jsStringOrNull(state, oldValue));
285         args.append(jsStringOrNull(state, newValue));
286         args.append(jsStringOrNull(state, attributeName.namespaceURI()));
287     });
288 }
289
290 void JSCustomElementInterface::didUpgradeLastElementInConstructionStack()
291 {
292     m_constructionStack.last() = nullptr;
293 }
294
295 } // namespace WebCore
296
297 #endif