Unreviewed, fix iOS Debug build after r251220.
[WebKit-https.git] / Source / WebCore / bindings / js / JSCustomElementInterface.cpp
1 /*
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  * Copyright (C) 2015-2017 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 "JSDOMWindow.h"
37 #include "JSElement.h"
38 #include "JSExecState.h"
39 #include "JSExecStateInstrumentation.h"
40 #include "JSHTMLElement.h"
41 #include "ScriptExecutionContext.h"
42 #include <JavaScriptCore/JSLock.h>
43 #include <JavaScriptCore/WeakInlines.h>
44
45 namespace WebCore {
46 using namespace JSC;
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() = default;
57
58 static RefPtr<Element> constructCustomElementSynchronously(Document&, VM&, ExecState&, JSObject* constructor, const AtomString& localName);
59
60 Ref<Element> JSCustomElementInterface::constructElementWithFallback(Document& document, const AtomString& localName)
61 {
62     if (auto element = tryToConstructCustomElement(document, localName))
63         return element.releaseNonNull();
64
65     auto element = HTMLUnknownElement::create(QualifiedName(nullAtom(), localName, HTMLNames::xhtmlNamespaceURI), document);
66     element->setIsCustomElementUpgradeCandidate();
67     element->setIsFailedCustomElement(*this);
68
69     return element;
70 }
71
72 Ref<Element> JSCustomElementInterface::constructElementWithFallback(Document& document, const QualifiedName& name)
73 {
74     if (auto element = tryToConstructCustomElement(document, name.localName())) {
75         if (!name.prefix().isNull())
76             element->setPrefix(name.prefix());
77         return element.releaseNonNull();
78     }
79
80     auto element = HTMLUnknownElement::create(name, document);
81     element->setIsCustomElementUpgradeCandidate();
82     element->setIsFailedCustomElement(*this);
83
84     return element;
85 }
86
87 RefPtr<Element> JSCustomElementInterface::tryToConstructCustomElement(Document& document, const AtomString& localName)
88 {
89     if (!canInvokeCallback())
90         return nullptr;
91
92     Ref<JSCustomElementInterface> protectedThis(*this);
93
94     VM& vm = m_isolatedWorld->vm();
95     JSLockHolder lock(vm);
96     auto scope = DECLARE_CATCH_SCOPE(vm);
97
98     if (!m_constructor)
99         return nullptr;
100
101     ASSERT(&document == scriptExecutionContext());
102     auto& state = *document.execState();
103     auto element = constructCustomElementSynchronously(document, vm, state, m_constructor.get(), localName);
104     EXCEPTION_ASSERT(!!scope.exception() == !element);
105     if (!element) {
106         auto* exception = scope.exception();
107         scope.clearException();
108         reportException(&state, exception);
109         return nullptr;
110     }
111
112     return element;
113 }
114
115 // https://dom.spec.whatwg.org/#concept-create-element
116 // 6. 1. If the synchronous custom elements flag is set
117 static RefPtr<Element> constructCustomElementSynchronously(Document& document, VM& vm, ExecState& state, JSObject* constructor, const AtomString& localName)
118 {
119     auto scope = DECLARE_THROW_SCOPE(vm);
120     ConstructData constructData;
121     ConstructType constructType = constructor->methodTable(vm)->getConstructData(constructor, constructData);
122     if (constructType == ConstructType::None) {
123         ASSERT_NOT_REACHED();
124         return nullptr;
125     }
126
127     JSExecState::instrumentFunctionConstruct(&document, constructType, constructData);
128     MarkedArgumentBuffer args;
129     ASSERT(!args.hasOverflowed());
130     JSValue newElement = construct(&state, constructor, constructType, constructData, args);
131     InspectorInstrumentation::didCallFunction(&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, "The result of constructing a custom element must be a HTMLElement"_s);
138         return nullptr;
139     }
140
141     if (wrappedElement->hasAttributes()) {
142         throwNotSupportedError(state, scope, "A newly constructed custom element must not have attributes"_s);
143         return nullptr;
144     }
145     if (wrappedElement->hasChildNodes()) {
146         throwNotSupportedError(state, scope, "A newly constructed custom element must not have child nodes"_s);
147         return nullptr;
148     }
149     if (wrappedElement->parentNode()) {
150         throwNotSupportedError(state, scope, "A newly constructed custom element must not have a parent node"_s);
151         return nullptr;
152     }
153     if (&wrappedElement->document() != &document) {
154         throwNotSupportedError(state, scope, "A newly constructed custom element belongs to a wrong document"_s);
155         return nullptr;
156     }
157     ASSERT(wrappedElement->namespaceURI() == HTMLNames::xhtmlNamespaceURI);
158     if (wrappedElement->localName() != localName) {
159         throwNotSupportedError(state, scope, "A newly constructed custom element has incorrect local name"_s);
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     auto* context = scriptExecutionContext();
182     if (!context)
183         return;
184     auto* globalObject = toJSDOMWindow(downcast<Document>(*context).frame(), m_isolatedWorld);
185     if (!globalObject)
186         return;
187     ExecState* state = globalObject->globalExec();
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     ASSERT(!args.hasOverflowed());
202     JSExecState::instrumentFunctionConstruct(context, constructType, constructData);
203     JSValue returnedElement = construct(state, m_constructor.get(), constructType, constructData, args);
204     InspectorInstrumentation::didCallFunction(context);
205
206     m_constructionStack.removeLast();
207
208     if (UNLIKELY(scope.exception())) {
209         element.setIsFailedCustomElement(*this);
210         reportException(state, scope.exception());
211         return;
212     }
213
214     Element* wrappedElement = JSElement::toWrapped(vm, returnedElement);
215     if (!wrappedElement || wrappedElement != &element) {
216         element.setIsFailedCustomElement(*this);
217         reportException(state, createDOMException(state, TypeError, "Custom element constructor returned a wrong element"));
218         return;
219     }
220     element.setIsDefinedCustomElement(*this);
221 }
222
223 void JSCustomElementInterface::invokeCallback(Element& element, JSObject* callback, const WTF::Function<void(ExecState*, JSDOMGlobalObject*, MarkedArgumentBuffer&)>& addArguments)
224 {
225     if (!canInvokeCallback())
226         return;
227
228     auto* context = scriptExecutionContext();
229     if (!context)
230         return;
231
232     Ref<JSCustomElementInterface> protectedThis(*this);
233     VM& vm = m_isolatedWorld->vm();
234     JSLockHolder lock(vm);
235
236     auto* globalObject = toJSDOMWindow(downcast<Document>(*context).frame(), m_isolatedWorld);
237     if (!globalObject)
238         return;
239     ExecState* state = globalObject->globalExec();
240
241     JSObject* jsElement = asObject(toJS(state, globalObject, element));
242
243     CallData callData;
244     CallType callType = callback->methodTable(vm)->getCallData(callback, callData);
245     ASSERT(callType != CallType::None);
246
247     MarkedArgumentBuffer args;
248     addArguments(state, globalObject, args);
249     RELEASE_ASSERT(!args.hasOverflowed());
250
251     JSExecState::instrumentFunctionCall(context, callType, callData);
252
253     NakedPtr<JSC::Exception> exception;
254     JSExecState::call(state, callback, callType, callData, jsElement, args, exception);
255
256     InspectorInstrumentation::didCallFunction(context);
257
258     if (exception)
259         reportException(state, exception);
260 }
261
262 void JSCustomElementInterface::setConnectedCallback(JSC::JSObject* callback)
263 {
264     m_connectedCallback = callback;
265 }
266
267 void JSCustomElementInterface::invokeConnectedCallback(Element& element)
268 {
269     invokeCallback(element, m_connectedCallback.get());
270 }
271
272 void JSCustomElementInterface::setDisconnectedCallback(JSC::JSObject* callback)
273 {
274     m_disconnectedCallback = callback;
275 }
276
277 void JSCustomElementInterface::invokeDisconnectedCallback(Element& element)
278 {
279     invokeCallback(element, m_disconnectedCallback.get());
280 }
281
282 void JSCustomElementInterface::setAdoptedCallback(JSC::JSObject* callback)
283 {
284     m_adoptedCallback = callback;
285 }
286
287 void JSCustomElementInterface::invokeAdoptedCallback(Element& element, Document& oldDocument, Document& newDocument)
288 {
289     invokeCallback(element, m_adoptedCallback.get(), [&](ExecState* state, JSDOMGlobalObject* globalObject, MarkedArgumentBuffer& args) {
290         args.append(toJS(state, globalObject, oldDocument));
291         args.append(toJS(state, globalObject, newDocument));
292     });
293 }
294
295 void JSCustomElementInterface::setAttributeChangedCallback(JSC::JSObject* callback, const Vector<String>& observedAttributes)
296 {
297     m_attributeChangedCallback = callback;
298     m_observedAttributes.clear();
299     for (auto& name : observedAttributes)
300         m_observedAttributes.add(name);
301 }
302
303 void JSCustomElementInterface::invokeAttributeChangedCallback(Element& element, const QualifiedName& attributeName, const AtomString& oldValue, const AtomString& newValue)
304 {
305     invokeCallback(element, m_attributeChangedCallback.get(), [&](ExecState* state, JSDOMGlobalObject*, MarkedArgumentBuffer& args) {
306         args.append(toJS<IDLDOMString>(*state, attributeName.localName()));
307         args.append(toJS<IDLNullable<IDLDOMString>>(*state, oldValue));
308         args.append(toJS<IDLNullable<IDLDOMString>>(*state, newValue));
309         args.append(toJS<IDLNullable<IDLDOMString>>(*state, attributeName.namespaceURI()));
310     });
311 }
312
313 void JSCustomElementInterface::didUpgradeLastElementInConstructionStack()
314 {
315     m_constructionStack.last() = nullptr;
316 }
317
318 } // namespace WebCore