1/*
2 * Copyright (C) 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#pragma once
27
28#include "ExceptionOr.h"
29#include "JSDOMConvert.h"
30#include "JSDOMGuardedObject.h"
31#include <JavaScriptCore/CatchScope.h>
32#include <JavaScriptCore/JSPromiseDeferred.h>
33
34namespace WebCore {
35
36class JSDOMWindow;
37
38class DeferredPromise : public DOMGuarded<JSC::JSPromiseDeferred> {
39public:
40 enum class Mode {
41 ClearPromiseOnResolve,
42 RetainPromiseOnResolve
43 };
44
45 static RefPtr<DeferredPromise> create(JSC::ExecState& state, JSDOMGlobalObject& globalObject, Mode mode = Mode::ClearPromiseOnResolve)
46 {
47 auto* promiseDeferred = JSC::JSPromiseDeferred::tryCreate(&state, &globalObject);
48 if (!promiseDeferred)
49 return nullptr;
50 return adoptRef(new DeferredPromise(globalObject, *promiseDeferred, mode));
51 }
52
53 static Ref<DeferredPromise> create(JSDOMGlobalObject& globalObject, JSC::JSPromiseDeferred& deferred, Mode mode = Mode::ClearPromiseOnResolve)
54 {
55 return adoptRef(*new DeferredPromise(globalObject, deferred, mode));
56 }
57
58 template<class IDLType>
59 void resolve(typename IDLType::ParameterType value)
60 {
61 if (isSuspended())
62 return;
63 ASSERT(deferred());
64 ASSERT(globalObject());
65 JSC::ExecState* exec = globalObject()->globalExec();
66 JSC::JSLockHolder locker(exec);
67 resolve(*exec, toJS<IDLType>(*exec, *globalObject(), std::forward<typename IDLType::ParameterType>(value)));
68 }
69
70 void resolve()
71 {
72 if (isSuspended())
73 return;
74 ASSERT(deferred());
75 ASSERT(globalObject());
76 JSC::ExecState* exec = globalObject()->globalExec();
77 JSC::JSLockHolder locker(exec);
78 resolve(*exec, JSC::jsUndefined());
79 }
80
81 template<class IDLType>
82 void resolveWithNewlyCreated(typename IDLType::ParameterType value)
83 {
84 if (isSuspended())
85 return;
86 ASSERT(deferred());
87 ASSERT(globalObject());
88 JSC::ExecState* exec = globalObject()->globalExec();
89 JSC::JSLockHolder locker(exec);
90 resolve(*exec, toJSNewlyCreated<IDLType>(*exec, *globalObject(), std::forward<typename IDLType::ParameterType>(value)));
91 }
92
93 template<class IDLType>
94 void reject(typename IDLType::ParameterType value)
95 {
96 if (isSuspended())
97 return;
98 ASSERT(deferred());
99 ASSERT(globalObject());
100 JSC::ExecState* exec = globalObject()->globalExec();
101 JSC::JSLockHolder locker(exec);
102 reject(*exec, toJS<IDLType>(*exec, *globalObject(), std::forward<typename IDLType::ParameterType>(value)));
103 }
104
105 void reject();
106 void reject(std::nullptr_t);
107 void reject(Exception);
108 WEBCORE_EXPORT void reject(ExceptionCode, const String& = { });
109 void reject(const JSC::PrivateName&);
110
111 template<typename Callback>
112 void resolveWithCallback(Callback callback)
113 {
114 if (isSuspended())
115 return;
116 ASSERT(deferred());
117 ASSERT(globalObject());
118 JSC::ExecState* exec = globalObject()->globalExec();
119 JSC::JSLockHolder locker(exec);
120 resolve(*exec, callback(*exec, *globalObject()));
121 }
122
123 template<typename Callback>
124 void rejectWithCallback(Callback callback)
125 {
126 if (isSuspended())
127 return;
128 ASSERT(deferred());
129 ASSERT(globalObject());
130 JSC::ExecState* exec = globalObject()->globalExec();
131 JSC::JSLockHolder locker(exec);
132 reject(*exec, callback(*exec, *globalObject()));
133 }
134
135 JSC::JSValue promise() const;
136
137 void whenSettled(std::function<void()>&&);
138
139private:
140 DeferredPromise(JSDOMGlobalObject& globalObject, JSC::JSPromiseDeferred& deferred, Mode mode)
141 : DOMGuarded<JSC::JSPromiseDeferred>(globalObject, deferred)
142 , m_mode(mode)
143 {
144 }
145
146 JSC::JSPromiseDeferred* deferred() const { return guarded(); }
147
148 WEBCORE_EXPORT void callFunction(JSC::ExecState&, JSC::JSValue function, JSC::JSValue resolution);
149
150 void resolve(JSC::ExecState& state, JSC::JSValue resolution) { callFunction(state, deferred()->resolve(), resolution); }
151 void reject(JSC::ExecState& state, JSC::JSValue resolution) { callFunction(state, deferred()->reject(), resolution); }
152
153 Mode m_mode;
154};
155
156class DOMPromiseDeferredBase {
157public:
158 DOMPromiseDeferredBase(Ref<DeferredPromise>&& genericPromise)
159 : m_promiseDeferred(WTFMove(genericPromise))
160 {
161 }
162
163 DOMPromiseDeferredBase(DOMPromiseDeferredBase&& promise)
164 : m_promiseDeferred(WTFMove(promise.m_promiseDeferred))
165 {
166 }
167
168 DOMPromiseDeferredBase(const DOMPromiseDeferredBase& other)
169 : m_promiseDeferred(other.m_promiseDeferred.copyRef())
170 {
171 }
172
173 DOMPromiseDeferredBase& operator=(const DOMPromiseDeferredBase& other)
174 {
175 m_promiseDeferred = other.m_promiseDeferred.copyRef();
176 return *this;
177 }
178
179 DOMPromiseDeferredBase& operator=(DOMPromiseDeferredBase&& other)
180 {
181 m_promiseDeferred = WTFMove(other.m_promiseDeferred);
182 return *this;
183 }
184
185 void reject()
186 {
187 m_promiseDeferred->reject();
188 }
189
190 template<typename... ErrorType>
191 void reject(ErrorType&&... error)
192 {
193 m_promiseDeferred->reject(std::forward<ErrorType>(error)...);
194 }
195
196 template<typename IDLType>
197 void rejectType(typename IDLType::ParameterType value)
198 {
199 m_promiseDeferred->reject<IDLType>(std::forward<typename IDLType::ParameterType>(value));
200 }
201
202 JSC::JSValue promise() const { return m_promiseDeferred->promise(); };
203
204protected:
205 Ref<DeferredPromise> m_promiseDeferred;
206};
207
208template<typename IDLType>
209class DOMPromiseDeferred : public DOMPromiseDeferredBase {
210public:
211 using DOMPromiseDeferredBase::DOMPromiseDeferredBase;
212 using DOMPromiseDeferredBase::operator=;
213 using DOMPromiseDeferredBase::promise;
214 using DOMPromiseDeferredBase::reject;
215
216 void resolve(typename IDLType::ParameterType value)
217 {
218 m_promiseDeferred->resolve<IDLType>(std::forward<typename IDLType::ParameterType>(value));
219 }
220
221 void settle(ExceptionOr<typename IDLType::ParameterType>&& result)
222 {
223 if (result.hasException()) {
224 reject(result.releaseException());
225 return;
226 }
227 resolve(result.releaseReturnValue());
228 }
229};
230
231template<> class DOMPromiseDeferred<void> : public DOMPromiseDeferredBase {
232public:
233 using DOMPromiseDeferredBase::DOMPromiseDeferredBase;
234 using DOMPromiseDeferredBase::operator=;
235 using DOMPromiseDeferredBase::promise;
236 using DOMPromiseDeferredBase::reject;
237
238 void resolve()
239 {
240 m_promiseDeferred->resolve();
241 }
242
243 void settle(ExceptionOr<void>&& result)
244 {
245 if (result.hasException()) {
246 reject(result.releaseException());
247 return;
248 }
249 resolve();
250 }
251};
252
253
254Ref<DeferredPromise> createDeferredPromise(JSC::ExecState&, JSDOMWindow&);
255
256void fulfillPromiseWithJSON(Ref<DeferredPromise>&&, const String&);
257void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&&, ArrayBuffer*);
258void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&&, const void*, size_t);
259WEBCORE_EXPORT void rejectPromiseWithExceptionIfAny(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromiseDeferred&);
260
261enum class RejectedPromiseWithTypeErrorCause { NativeGetter, InvalidThis };
262JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::ExecState&, const String&, RejectedPromiseWithTypeErrorCause);
263
264using PromiseFunction = void(JSC::ExecState&, Ref<DeferredPromise>&&);
265
266enum class PromiseExecutionScope { WindowOnly, WindowOrWorker };
267
268template<PromiseFunction promiseFunction, PromiseExecutionScope executionScope>
269inline JSC::JSValue callPromiseFunction(JSC::ExecState& state)
270{
271 JSC::VM& vm = state.vm();
272 auto scope = DECLARE_CATCH_SCOPE(vm);
273
274 auto& globalObject = callerGlobalObject(state);
275 JSC::JSPromiseDeferred* promiseDeferred = JSC::JSPromiseDeferred::tryCreate(&state, &globalObject);
276
277 // promiseDeferred can be null when terminating a Worker abruptly.
278 if (executionScope == PromiseExecutionScope::WindowOrWorker && !promiseDeferred)
279 return JSC::jsUndefined();
280
281 promiseFunction(state, DeferredPromise::create(globalObject, *promiseDeferred));
282
283 rejectPromiseWithExceptionIfAny(state, globalObject, *promiseDeferred);
284 EXCEPTION_ASSERT_UNUSED(scope, !scope.exception());
285 return promiseDeferred->promise();
286}
287
288template<PromiseExecutionScope executionScope, typename PromiseFunctor>
289inline JSC::JSValue callPromiseFunction(JSC::ExecState& state, PromiseFunctor functor)
290{
291 JSC::VM& vm = state.vm();
292 auto scope = DECLARE_CATCH_SCOPE(vm);
293
294 auto& globalObject = callerGlobalObject(state);
295 JSC::JSPromiseDeferred* promiseDeferred = JSC::JSPromiseDeferred::tryCreate(&state, &globalObject);
296
297 // promiseDeferred can be null when terminating a Worker abruptly.
298 if (executionScope == PromiseExecutionScope::WindowOrWorker && !promiseDeferred)
299 return JSC::jsUndefined();
300
301 functor(state, DeferredPromise::create(globalObject, *promiseDeferred));
302
303 rejectPromiseWithExceptionIfAny(state, globalObject, *promiseDeferred);
304 EXCEPTION_ASSERT_UNUSED(scope, !scope.exception());
305 return promiseDeferred->promise();
306}
307
308using BindingPromiseFunction = JSC::EncodedJSValue(JSC::ExecState*, Ref<DeferredPromise>&&);
309template<BindingPromiseFunction bindingFunction>
310inline void bindingPromiseFunctionAdapter(JSC::ExecState& state, Ref<DeferredPromise>&& promise)
311{
312 bindingFunction(&state, WTFMove(promise));
313}
314
315template<BindingPromiseFunction bindingPromiseFunction, PromiseExecutionScope executionScope>
316inline JSC::JSValue callPromiseFunction(JSC::ExecState& state)
317{
318 return callPromiseFunction<bindingPromiseFunctionAdapter<bindingPromiseFunction>, executionScope>(state);
319}
320
321} // namespace WebCore
322