1/*
2 * Copyright (C) 2013-2017 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#include "config.h"
27#include "JSDOMPromiseDeferred.h"
28
29#include "DOMWindow.h"
30#include "JSDOMPromise.h"
31#include "JSDOMWindow.h"
32#include <JavaScriptCore/BuiltinNames.h>
33#include <JavaScriptCore/Exception.h>
34#include <JavaScriptCore/JSONObject.h>
35#include <JavaScriptCore/JSPromiseConstructor.h>
36
37namespace WebCore {
38using namespace JSC;
39
40JSC::JSValue DeferredPromise::promise() const
41{
42 ASSERT(deferred());
43 return deferred()->promise();
44}
45
46void DeferredPromise::callFunction(ExecState& exec, JSValue function, JSValue resolution)
47{
48 if (!canInvokeCallback())
49 return;
50
51 VM& vm = exec.vm();
52 auto scope = DECLARE_THROW_SCOPE(vm);
53
54 CallData callData;
55 CallType callType = getCallData(vm, function, callData);
56 ASSERT(callType != CallType::None);
57
58 MarkedArgumentBuffer arguments;
59 arguments.append(resolution);
60 ASSERT(!arguments.hasOverflowed());
61
62 call(&exec, function, callType, callData, jsUndefined(), arguments);
63
64 // DeferredPromise should only be used by internal implementations that are well behaved.
65 // In practice, the only exception we should ever see here is the TerminatedExecutionException.
66 EXCEPTION_ASSERT_UNUSED(scope, !scope.exception() || isTerminatedExecutionException(vm, scope.exception()));
67
68 if (m_mode == Mode::ClearPromiseOnResolve)
69 clear();
70}
71
72void DeferredPromise::whenSettled(std::function<void()>&& callback)
73{
74 DOMPromise::whenPromiseIsSettled(globalObject(), deferred()->promise(), WTFMove(callback));
75}
76
77void DeferredPromise::reject()
78{
79 if (isSuspended())
80 return;
81
82 ASSERT(deferred());
83 ASSERT(m_globalObject);
84 auto& state = *m_globalObject->globalExec();
85 JSC::JSLockHolder locker(&state);
86 reject(state, JSC::jsUndefined());
87}
88
89void DeferredPromise::reject(std::nullptr_t)
90{
91 if (isSuspended())
92 return;
93
94 ASSERT(deferred());
95 ASSERT(m_globalObject);
96 auto& state = *m_globalObject->globalExec();
97 JSC::JSLockHolder locker(&state);
98 reject(state, JSC::jsNull());
99}
100
101void DeferredPromise::reject(Exception exception)
102{
103 if (isSuspended())
104 return;
105
106 ASSERT(deferred());
107 ASSERT(m_globalObject);
108 auto& state = *m_globalObject->globalExec();
109 JSC::JSLockHolder locker(&state);
110
111 if (exception.code() == ExistingExceptionError) {
112 auto scope = DECLARE_CATCH_SCOPE(state.vm());
113
114 EXCEPTION_ASSERT(scope.exception());
115
116 auto error = scope.exception()->value();
117 scope.clearException();
118
119 reject<IDLAny>(error);
120 return;
121 }
122
123 auto scope = DECLARE_THROW_SCOPE(state.vm());
124 auto error = createDOMException(state, WTFMove(exception));
125 if (UNLIKELY(scope.exception())) {
126 ASSERT(isTerminatedExecutionException(state.vm(), scope.exception()));
127 return;
128 }
129
130 reject(state, error);
131}
132
133void DeferredPromise::reject(ExceptionCode ec, const String& message)
134{
135 if (isSuspended())
136 return;
137
138 ASSERT(deferred());
139 ASSERT(m_globalObject);
140 auto& state = *m_globalObject->globalExec();
141 JSC::JSLockHolder locker(&state);
142
143 if (ec == ExistingExceptionError) {
144 auto scope = DECLARE_CATCH_SCOPE(state.vm());
145
146 EXCEPTION_ASSERT(scope.exception());
147
148 auto error = scope.exception()->value();
149 scope.clearException();
150
151 reject<IDLAny>(error);
152 return;
153 }
154
155 auto scope = DECLARE_THROW_SCOPE(state.vm());
156 auto error = createDOMException(&state, ec, message);
157 if (UNLIKELY(scope.exception())) {
158 ASSERT(isTerminatedExecutionException(state.vm(), scope.exception()));
159 return;
160 }
161
162
163 reject(state, error);
164}
165
166void DeferredPromise::reject(const JSC::PrivateName& privateName)
167{
168 if (isSuspended())
169 return;
170
171 ASSERT(deferred());
172 ASSERT(m_globalObject);
173 JSC::ExecState* state = m_globalObject->globalExec();
174 JSC::JSLockHolder locker(state);
175 reject(*state, JSC::Symbol::create(state->vm(), privateName.uid()));
176}
177
178void rejectPromiseWithExceptionIfAny(JSC::ExecState& state, JSDOMGlobalObject& globalObject, JSPromiseDeferred& promiseDeferred)
179{
180 VM& vm = state.vm();
181 auto scope = DECLARE_CATCH_SCOPE(vm);
182
183 if (LIKELY(!scope.exception()))
184 return;
185
186 JSValue error = scope.exception()->value();
187 scope.clearException();
188
189 DeferredPromise::create(globalObject, promiseDeferred)->reject<IDLAny>(error);
190}
191
192Ref<DeferredPromise> createDeferredPromise(JSC::ExecState& state, JSDOMWindow& domWindow)
193{
194 JSC::JSPromiseDeferred* deferred = JSC::JSPromiseDeferred::tryCreate(&state, &domWindow);
195 // deferred can only be null in workers.
196 RELEASE_ASSERT(deferred);
197 return DeferredPromise::create(domWindow, *deferred);
198}
199
200JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::ExecState& state, const String& errorMessage, RejectedPromiseWithTypeErrorCause cause)
201{
202 ASSERT(state.lexicalGlobalObject());
203 auto& globalObject = *state.lexicalGlobalObject();
204
205 auto promiseConstructor = globalObject.promiseConstructor();
206 auto rejectFunction = promiseConstructor->get(&state, state.vm().propertyNames->builtinNames().rejectPrivateName());
207 auto* rejectionValue = static_cast<ErrorInstance*>(createTypeError(&state, errorMessage));
208 if (cause == RejectedPromiseWithTypeErrorCause::NativeGetter)
209 rejectionValue->setNativeGetterTypeError();
210
211 CallData callData;
212 auto callType = getCallData(state.vm(), rejectFunction, callData);
213 ASSERT(callType != CallType::None);
214
215 MarkedArgumentBuffer arguments;
216 arguments.append(rejectionValue);
217 ASSERT(!arguments.hasOverflowed());
218
219 return JSValue::encode(call(&state, rejectFunction, callType, callData, promiseConstructor, arguments));
220}
221
222static inline JSC::JSValue parseAsJSON(JSC::ExecState* state, const String& data)
223{
224 JSC::JSLockHolder lock(state);
225 return JSC::JSONParse(state, data);
226}
227
228void fulfillPromiseWithJSON(Ref<DeferredPromise>&& promise, const String& data)
229{
230 JSC::JSValue value = parseAsJSON(promise->globalObject()->globalExec(), data);
231 if (!value)
232 promise->reject(SyntaxError);
233 else
234 promise->resolve<IDLAny>(value);
235}
236
237void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&& promise, ArrayBuffer* arrayBuffer)
238{
239 if (!arrayBuffer) {
240 promise->reject<IDLAny>(createOutOfMemoryError(promise->globalObject()->globalExec()));
241 return;
242 }
243 promise->resolve<IDLInterface<ArrayBuffer>>(*arrayBuffer);
244}
245
246void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&& promise, const void* data, size_t length)
247{
248 fulfillPromiseWithArrayBuffer(WTFMove(promise), ArrayBuffer::tryCreate(data, length).get());
249}
250
251}
252