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