Add a way to mark a rejected promise as handled
[WebKit-https.git] / Source / WebCore / bindings / js / JSDOMPromiseDeferred.h
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 "EventLoop.h"
29 #include "ExceptionOr.h"
30 #include "JSDOMConvert.h"
31 #include "JSDOMGuardedObject.h"
32 #include "ScriptExecutionContext.h"
33 #include <JavaScriptCore/CatchScope.h>
34 #include <JavaScriptCore/JSPromise.h>
35
36 namespace WebCore {
37
38 class JSDOMWindow;
39 enum class RejectAsHandled : uint8_t { No, Yes };
40
41 class DeferredPromise : public DOMGuarded<JSC::JSPromise> {
42 public:
43     enum class Mode {
44         ClearPromiseOnResolve,
45         RetainPromiseOnResolve
46     };
47
48     static RefPtr<DeferredPromise> create(JSDOMGlobalObject& globalObject, Mode mode = Mode::ClearPromiseOnResolve)
49     {
50         JSC::VM& vm = JSC::getVM(&globalObject);
51         auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure());
52         ASSERT(promise);
53         return adoptRef(new DeferredPromise(globalObject, *promise, mode));
54     }
55
56     static Ref<DeferredPromise> create(JSDOMGlobalObject& globalObject, JSC::JSPromise& deferred, Mode mode = Mode::ClearPromiseOnResolve)
57     {
58         return adoptRef(*new DeferredPromise(globalObject, deferred, mode));
59     }
60
61     template<class IDLType>
62     void resolve(typename IDLType::ParameterType value)
63     {
64         if (shouldIgnoreRequestToFulfill())
65             return;
66
67         ASSERT(deferred());
68         ASSERT(globalObject());
69         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
70         JSC::JSLockHolder locker(lexicalGlobalObject);
71         resolve(*lexicalGlobalObject, toJS<IDLType>(*lexicalGlobalObject, *globalObject(), std::forward<typename IDLType::ParameterType>(value)));
72     }
73
74     void resolve()
75     {
76         if (shouldIgnoreRequestToFulfill())
77             return;
78
79         ASSERT(deferred());
80         ASSERT(globalObject());
81         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
82         JSC::JSLockHolder locker(lexicalGlobalObject);
83         resolve(*lexicalGlobalObject, JSC::jsUndefined());
84     }
85
86     template<class IDLType>
87     void resolveWithNewlyCreated(typename IDLType::ParameterType value)
88     {
89         if (shouldIgnoreRequestToFulfill())
90             return;
91
92         ASSERT(deferred());
93         ASSERT(globalObject());
94         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
95         JSC::JSLockHolder locker(lexicalGlobalObject);
96         resolve(*lexicalGlobalObject, toJSNewlyCreated<IDLType>(*lexicalGlobalObject, *globalObject(), std::forward<typename IDLType::ParameterType>(value)));
97     }
98
99     template<class IDLType>
100     void resolveCallbackValueWithNewlyCreated(const Function<typename IDLType::InnerParameterType(ScriptExecutionContext&)>& createValue)
101     {
102         if (shouldIgnoreRequestToFulfill())
103             return;
104
105         ASSERT(deferred());
106         ASSERT(globalObject());
107         auto* lexicalGlobalObject = globalObject();
108         JSC::JSLockHolder locker(lexicalGlobalObject);
109         resolve(*lexicalGlobalObject, toJSNewlyCreated<IDLType>(*lexicalGlobalObject, *globalObject(), createValue(*globalObject()->scriptExecutionContext())));
110     }
111
112     template<class IDLType>
113     void reject(typename IDLType::ParameterType value, RejectAsHandled rejectAsHandled = RejectAsHandled::No)
114     {
115         if (shouldIgnoreRequestToFulfill())
116             return;
117
118         ASSERT(deferred());
119         ASSERT(globalObject());
120         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
121         JSC::JSLockHolder locker(lexicalGlobalObject);
122         reject(*lexicalGlobalObject, toJS<IDLType>(*lexicalGlobalObject, *globalObject(), std::forward<typename IDLType::ParameterType>(value)), rejectAsHandled);
123     }
124
125     void reject(RejectAsHandled = RejectAsHandled::No);
126     void reject(std::nullptr_t, RejectAsHandled = RejectAsHandled::No);
127     void reject(Exception, RejectAsHandled = RejectAsHandled::No);
128     WEBCORE_EXPORT void reject(ExceptionCode, const String& = { }, RejectAsHandled = RejectAsHandled::No);
129     void reject(const JSC::PrivateName&, RejectAsHandled = RejectAsHandled::No);
130
131     template<typename Callback>
132     void resolveWithCallback(Callback callback)
133     {
134         if (shouldIgnoreRequestToFulfill())
135             return;
136
137         ASSERT(deferred());
138         ASSERT(globalObject());
139         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
140         JSC::JSLockHolder locker(lexicalGlobalObject);
141         resolve(*lexicalGlobalObject, callback(*globalObject()));
142     }
143
144     template<typename Callback>
145     void rejectWithCallback(Callback callback, RejectAsHandled rejectAsHandled = RejectAsHandled::No)
146     {
147         if (shouldIgnoreRequestToFulfill())
148             return;
149
150         ASSERT(deferred());
151         ASSERT(globalObject());
152         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
153         JSC::JSLockHolder locker(lexicalGlobalObject);
154         reject(*lexicalGlobalObject, callback(*globalObject()), rejectAsHandled);
155     }
156
157     JSC::JSValue promise() const;
158
159     void whenSettled(Function<void()>&&);
160
161 private:
162     DeferredPromise(JSDOMGlobalObject& globalObject, JSC::JSPromise& deferred, Mode mode)
163         : DOMGuarded<JSC::JSPromise>(globalObject, deferred)
164         , m_mode(mode)
165     {
166     }
167
168     bool shouldIgnoreRequestToFulfill() const { return isEmpty() || activeDOMObjectAreStopped(); }
169
170     JSC::JSPromise* deferred() const { return guarded(); }
171
172     enum class ResolveMode { Resolve, Reject, RejectAsHandled };
173     WEBCORE_EXPORT void callFunction(JSC::JSGlobalObject&, ResolveMode, JSC::JSValue resolution);
174
175     void resolve(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution) { callFunction(lexicalGlobalObject, ResolveMode::Resolve, resolution); }
176     void reject(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution, RejectAsHandled rejectAsHandled)
177     {
178         callFunction(lexicalGlobalObject, rejectAsHandled == RejectAsHandled::Yes ? ResolveMode::RejectAsHandled : ResolveMode::Reject, resolution);
179     }
180
181     Mode m_mode;
182 };
183
184 class DOMPromiseDeferredBase {
185     WTF_MAKE_FAST_ALLOCATED;
186 public:
187     DOMPromiseDeferredBase(Ref<DeferredPromise>&& genericPromise)
188         : m_promise(WTFMove(genericPromise))
189     {
190     }
191
192     DOMPromiseDeferredBase(DOMPromiseDeferredBase&& promise)
193         : m_promise(WTFMove(promise.m_promise))
194     {
195     }
196
197     DOMPromiseDeferredBase(const DOMPromiseDeferredBase& other)
198         : m_promise(other.m_promise.copyRef())
199     {
200     }
201
202     DOMPromiseDeferredBase& operator=(const DOMPromiseDeferredBase& other)
203     {
204         m_promise = other.m_promise.copyRef();
205         return *this;
206     }
207
208     DOMPromiseDeferredBase& operator=(DOMPromiseDeferredBase&& other)
209     {
210         m_promise = WTFMove(other.m_promise);
211         return *this;
212     }
213
214     void reject(RejectAsHandled rejectAsHandled = RejectAsHandled::No)
215     {
216         m_promise->reject(rejectAsHandled);
217     }
218
219     template<typename... ErrorType> 
220     void reject(ErrorType&&... error)
221     {
222         m_promise->reject(std::forward<ErrorType>(error)...);
223     }
224
225     template<typename IDLType>
226     void rejectType(typename IDLType::ParameterType value, RejectAsHandled rejectAsHandled = RejectAsHandled::No)
227     {
228         m_promise->reject<IDLType>(std::forward<typename IDLType::ParameterType>(value), rejectAsHandled);
229     }
230
231     JSC::JSValue promise() const { return m_promise->promise(); };
232
233     void whenSettled(Function<void()>&& function)
234     {
235         m_promise->whenSettled(WTFMove(function));
236     }
237
238 protected:
239     Ref<DeferredPromise> m_promise;
240 };
241
242 template<typename IDLType> 
243 class DOMPromiseDeferred : public DOMPromiseDeferredBase {
244 public:
245     using DOMPromiseDeferredBase::DOMPromiseDeferredBase;
246     using DOMPromiseDeferredBase::operator=;
247     using DOMPromiseDeferredBase::promise;
248     using DOMPromiseDeferredBase::reject;
249
250     void resolve(typename IDLType::ParameterType value)
251     { 
252         m_promise->resolve<IDLType>(std::forward<typename IDLType::ParameterType>(value));
253     }
254
255     void settle(ExceptionOr<typename IDLType::ParameterType>&& result)
256     {
257         if (result.hasException()) {
258             reject(result.releaseException());
259             return;
260         }
261         resolve(result.releaseReturnValue());
262     }
263 };
264
265 template<> class DOMPromiseDeferred<void> : public DOMPromiseDeferredBase {
266 public:
267     using DOMPromiseDeferredBase::DOMPromiseDeferredBase;
268     using DOMPromiseDeferredBase::operator=;
269     using DOMPromiseDeferredBase::promise;
270     using DOMPromiseDeferredBase::reject;
271
272     void resolve()
273     { 
274         m_promise->resolve();
275     }
276
277     void settle(ExceptionOr<void>&& result)
278     {
279         if (result.hasException()) {
280             reject(result.releaseException());
281             return;
282         }
283         resolve();
284     }
285 };
286
287
288 Ref<DeferredPromise> createDeferredPromise(JSC::JSGlobalObject&, JSDOMWindow&);
289
290 void fulfillPromiseWithJSON(Ref<DeferredPromise>&&, const String&);
291 void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&&, ArrayBuffer*);
292 void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&&, const void*, size_t);
293 WEBCORE_EXPORT void rejectPromiseWithExceptionIfAny(JSC::JSGlobalObject&, JSDOMGlobalObject&, JSC::JSPromise&);
294
295 enum class RejectedPromiseWithTypeErrorCause { NativeGetter, InvalidThis };
296 JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::JSGlobalObject&, const String&, RejectedPromiseWithTypeErrorCause);
297
298 using PromiseFunction = void(JSC::JSGlobalObject&, JSC::CallFrame&, Ref<DeferredPromise>&&);
299
300 template<PromiseFunction promiseFunction>
301 inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame)
302 {
303     JSC::VM& vm = JSC::getVM(&lexicalGlobalObject);
304     auto scope = DECLARE_CATCH_SCOPE(vm);
305
306     auto& globalObject = callerGlobalObject(lexicalGlobalObject, callFrame);
307     auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure());
308     ASSERT(promise);
309
310     promiseFunction(lexicalGlobalObject, callFrame, DeferredPromise::create(globalObject, *promise));
311
312     rejectPromiseWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise);
313     // FIXME: We could have error since any JS call can throw stack-overflow errors.
314     // https://bugs.webkit.org/show_bug.cgi?id=203402
315     RETURN_IF_EXCEPTION(scope, JSC::jsUndefined());
316     return promise;
317 }
318
319 template<typename PromiseFunctor>
320 inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, PromiseFunctor functor)
321 {
322     JSC::VM& vm = JSC::getVM(&lexicalGlobalObject);
323     auto scope = DECLARE_CATCH_SCOPE(vm);
324
325     auto& globalObject = callerGlobalObject(lexicalGlobalObject, callFrame);
326     auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure());
327     ASSERT(promise);
328
329     functor(lexicalGlobalObject, callFrame, DeferredPromise::create(globalObject, *promise));
330
331     rejectPromiseWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise);
332     // FIXME: We could have error since any JS call can throw stack-overflow errors.
333     // https://bugs.webkit.org/show_bug.cgi?id=203402
334     RETURN_IF_EXCEPTION(scope, JSC::jsUndefined());
335     return promise;
336 }
337
338 using BindingPromiseFunction = JSC::EncodedJSValue(JSC::JSGlobalObject*, JSC::CallFrame*, Ref<DeferredPromise>&&);
339 template<BindingPromiseFunction bindingFunction>
340 inline void bindingPromiseFunctionAdapter(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, Ref<DeferredPromise>&& promise)
341 {
342     bindingFunction(&lexicalGlobalObject, &callFrame, WTFMove(promise));
343 }
344
345 template<BindingPromiseFunction bindingPromiseFunction>
346 inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame)
347 {
348     return callPromiseFunction<bindingPromiseFunctionAdapter<bindingPromiseFunction>>(lexicalGlobalObject, callFrame);
349 }
350
351 } // namespace WebCore