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 AtomicString& localName); |
59 | |
60 | Ref<Element> JSCustomElementInterface::constructElementWithFallback(Document& document, const AtomicString& 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 AtomicString& 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 AtomicString& 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 | InspectorInstrumentationCookie cookie = JSExecState::instrumentFunctionConstruct(&document, constructType, constructData); |
128 | MarkedArgumentBuffer args; |
129 | ASSERT(!args.hasOverflowed()); |
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, "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 | InspectorInstrumentationCookie cookie = JSExecState::instrumentFunctionConstruct(context, constructType, constructData); |
203 | JSValue returnedElement = construct(state, m_constructor.get(), constructType, constructData, args); |
204 | InspectorInstrumentation::didCallFunction(cookie, 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 | InspectorInstrumentationCookie cookie = 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(cookie, 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 AtomicString& oldValue, const AtomicString& 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 |
319 | |