blob: 3157ebee5327adf2e43f225acc6a8eba31cb2e7c [file] [log] [blame]
/*
* Copyright (C) 2013 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSPromiseConstructor.h"
#if ENABLE(PROMISES)
#include "Error.h"
#include "JSCJSValueInlines.h"
#include "JSCellInlines.h"
#include "JSPromise.h"
#include "JSPromiseDeferred.h"
#include "JSPromiseFunctions.h"
#include "JSPromisePrototype.h"
#include "Lookup.h"
#include "StructureInlines.h"
namespace JSC {
STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSPromiseConstructor);
static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncCast(ExecState*);
static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncResolve(ExecState*);
static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncReject(ExecState*);
static EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncRace(ExecState*);
}
#include "JSPromiseConstructor.lut.h"
namespace JSC {
const ClassInfo JSPromiseConstructor::s_info = { "Function", &InternalFunction::s_info, 0, ExecState::promiseConstructorTable, CREATE_METHOD_TABLE(JSPromiseConstructor) };
/* Source for JSPromiseConstructor.lut.h
@begin promiseConstructorTable
cast JSPromiseConstructorFuncCast DontEnum|Function 1
resolve JSPromiseConstructorFuncResolve DontEnum|Function 1
reject JSPromiseConstructorFuncReject DontEnum|Function 1
race JSPromiseConstructorFuncRace DontEnum|Function 1
@end
*/
JSPromiseConstructor* JSPromiseConstructor::create(VM& vm, Structure* structure, JSPromisePrototype* promisePrototype)
{
JSPromiseConstructor* constructor = new (NotNull, allocateCell<JSPromiseConstructor>(vm.heap)) JSPromiseConstructor(vm, structure);
constructor->finishCreation(vm, promisePrototype);
return constructor;
}
Structure* JSPromiseConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
{
return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
}
JSPromiseConstructor::JSPromiseConstructor(VM& vm, Structure* structure)
: InternalFunction(vm, structure)
{
}
void JSPromiseConstructor::finishCreation(VM& vm, JSPromisePrototype* promisePrototype)
{
Base::finishCreation(vm, "Promise");
putDirectWithoutTransition(vm, vm.propertyNames->prototype, promisePrototype, DontEnum | DontDelete | ReadOnly);
putDirectWithoutTransition(vm, vm.propertyNames->length, jsNumber(1), ReadOnly | DontEnum | DontDelete);
}
static EncodedJSValue JSC_HOST_CALL constructPromise(ExecState* exec)
{
// NOTE: We ignore steps 1-4 as they only matter if you support subclassing, which we do not yet.
// 1. Let promise be the this value.
// 2. If Type(promise) is not Object, then throw a TypeError exception.
// 3. If promise does not have a [[PromiseStatus]] internal slot, then throw a TypeError exception.
// 4. If promise's [[PromiseStatus]] internal slot is not undefined, then throw a TypeError exception.
JSValue resolver = exec->argument(0);
// 5. IsCallable(resolver) is false, then throw a TypeError exception
CallData callData;
CallType callType = getCallData(resolver, callData);
if (callType == CallTypeNone)
return JSValue::encode(throwTypeError(exec, ASCIILiteral("Promise constructor takes a function argument")));
VM& vm = exec->vm();
JSGlobalObject* globalObject = exec->callee()->globalObject();
JSPromise* promise = JSPromise::create(vm, globalObject, jsCast<JSPromiseConstructor*>(exec->callee()));
// NOTE: Steps 6-8 are handled by JSPromise::create().
// 6. Set promise's [[PromiseStatus]] internal slot to "unresolved".
// 7. Set promise's [[ResolveReactions]] internal slot to a new empty List.
// 8. Set promise's [[RejectReactions]] internal slot to a new empty List.
// 9. Let 'resolve' be a new built-in function object as defined in Resolve Promise Functions.
JSFunction* resolve = createResolvePromiseFunction(vm, globalObject);
// 10. Set the [[Promise]] internal slot of 'resolve' to 'promise'.
resolve->putDirect(vm, vm.propertyNames->promisePrivateName, promise);
// 11. Let 'reject' be a new built-in function object as defined in Reject Promise Functions
JSFunction* reject = createRejectPromiseFunction(vm, globalObject);
// 12. Set the [[Promise]] internal slot of 'reject' to 'promise'.
reject->putDirect(vm, vm.propertyNames->promisePrivateName, promise);
// 13. Let 'result' be the result of calling the [[Call]] internal method of resolver with
// undefined as thisArgument and a List containing resolve and reject as argumentsList.
MarkedArgumentBuffer arguments;
arguments.append(resolve);
arguments.append(reject);
call(exec, resolver, callType, callData, jsUndefined(), arguments);
// 14. If result is an abrupt completion, call PromiseReject(promise, result.[[value]]).
if (exec->hadException()) {
JSValue exception = exec->exception();
exec->clearException();
promise->reject(vm, exception);
}
// 15. Return promise.
return JSValue::encode(promise);
}
ConstructType JSPromiseConstructor::getConstructData(JSCell*, ConstructData& constructData)
{
constructData.native.function = constructPromise;
return ConstructTypeHost;
}
CallType JSPromiseConstructor::getCallData(JSCell*, CallData&)
{
// FIXME: Implement
return CallTypeNone;
}
bool JSPromiseConstructor::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
{
return getStaticFunctionSlot<InternalFunction>(exec, ExecState::promiseConstructorTable(exec), jsCast<JSPromiseConstructor*>(object), propertyName, slot);
}
EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncCast(ExecState* exec)
{
// -- Promise.cast(x) --
JSValue x = exec->argument(0);
// 1. Let 'C' be the this value.
JSValue C = exec->thisValue();
// 2. If IsPromise(x) is true,
JSPromise* promise = jsDynamicCast<JSPromise*>(x);
if (promise) {
// i. Let 'constructor' be the value of x's [[PromiseConstructor]] internal slot.
JSValue constructor = promise->constructor();
// ii. If SameValue(constructor, C) is true, return x.
if (sameValue(exec, constructor, C))
return JSValue::encode(x);
}
// 3. Let 'deferred' be the result of calling GetDeferred(C).
JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C);
// 4. ReturnIfAbrupt(deferred).
if (exec->hadException())
return JSValue::encode(jsUndefined());
JSPromiseDeferred* deferred = jsCast<JSPromiseDeferred*>(deferredValue);
// 5. Let 'resolveResult' be the result of calling the [[Call]] internal method
// of deferred.[[Resolve]] with undefined as thisArgument and a List containing x
// as argumentsList.
JSValue deferredResolve = deferred->resolve();
CallData resolveCallData;
CallType resolveCallType = getCallData(deferredResolve, resolveCallData);
ASSERT(resolveCallType != CallTypeNone);
MarkedArgumentBuffer arguments;
arguments.append(x);
call(exec, deferredResolve, resolveCallType, resolveCallData, jsUndefined(), arguments);
// 6. ReturnIfAbrupt(resolveResult).
if (exec->hadException())
return JSValue::encode(jsUndefined());
// 7. Return deferred.[[Promise]].
return JSValue::encode(deferred->promise());
}
EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncResolve(ExecState* exec)
{
// -- Promise.resolve(x) --
JSValue x = exec->argument(0);
// 1. Let 'C' be the this value.
JSValue C = exec->thisValue();
// 2. Let 'deferred' be the result of calling GetDeferred(C).
JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C);
// 3. ReturnIfAbrupt(deferred).
if (exec->hadException())
return JSValue::encode(jsUndefined());
JSPromiseDeferred* deferred = jsCast<JSPromiseDeferred*>(deferredValue);
// 4. Let 'resolveResult' be the result of calling the [[Call]] internal method
// of deferred.[[Resolve]] with undefined as thisArgument and a List containing x
// as argumentsList.
JSValue deferredResolve = deferred->resolve();
CallData resolveCallData;
CallType resolveCallType = getCallData(deferredResolve, resolveCallData);
ASSERT(resolveCallType != CallTypeNone);
MarkedArgumentBuffer arguments;
arguments.append(x);
call(exec, deferredResolve, resolveCallType, resolveCallData, jsUndefined(), arguments);
// 5. ReturnIfAbrupt(resolveResult).
if (exec->hadException())
return JSValue::encode(jsUndefined());
// 6. Return deferred.[[Promise]].
return JSValue::encode(deferred->promise());
}
EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncReject(ExecState* exec)
{
// -- Promise.reject(x) --
JSValue r = exec->argument(0);
// 1. Let 'C' be the this value.
JSValue C = exec->thisValue();
// 2. Let 'deferred' be the result of calling GetDeferred(C).
JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C);
// 3. ReturnIfAbrupt(deferred).
if (exec->hadException())
return JSValue::encode(jsUndefined());
JSPromiseDeferred* deferred = jsCast<JSPromiseDeferred*>(deferredValue);
// 4. Let 'rejectResult' be the result of calling the [[Call]] internal method
// of deferred.[[Reject]] with undefined as thisArgument and a List containing r
// as argumentsList.
JSValue deferredReject = deferred->reject();
CallData rejectCallData;
CallType rejectCallType = getCallData(deferredReject, rejectCallData);
ASSERT(rejectCallType != CallTypeNone);
MarkedArgumentBuffer arguments;
arguments.append(r);
call(exec, deferredReject, rejectCallType, rejectCallData, jsUndefined(), arguments);
// 5. ReturnIfAbrupt(resolveResult).
if (exec->hadException())
return JSValue::encode(jsUndefined());
// 6. Return deferred.[[Promise]].
return JSValue::encode(deferred->promise());
}
static JSValue abruptRejection(ExecState* exec, JSPromiseDeferred* deferred)
{
ASSERT(exec->hadException());
JSValue argument = exec->exception();
exec->clearException();
// i. Let 'rejectResult' be the result of calling the [[Call]] internal method
// of deferred.[[Reject]] with undefined as thisArgument and a List containing
// argument.[[value]] as argumentsList.
JSValue deferredReject = deferred->reject();
CallData rejectCallData;
CallType rejectCallType = getCallData(deferredReject, rejectCallData);
ASSERT(rejectCallType != CallTypeNone);
MarkedArgumentBuffer arguments;
arguments.append(argument);
call(exec, deferredReject, rejectCallType, rejectCallData, jsUndefined(), arguments);
// ii. ReturnIfAbrupt(rejectResult).
if (exec->hadException())
return jsUndefined();
// iii. Return deferred.[[Promise]].
return deferred->promise();
}
EncodedJSValue JSC_HOST_CALL JSPromiseConstructorFuncRace(ExecState* exec)
{
// -- Promise.race(iterable) --
JSValue iterable = exec->argument(0);
VM& vm = exec->vm();
// 1. Let 'C' be the this value.
JSValue C = exec->thisValue();
// 2. Let 'deferred' be the result of calling GetDeferred(C).
JSValue deferredValue = createJSPromiseDeferredFromConstructor(exec, C);
// 3. ReturnIfAbrupt(deferred).
if (exec->hadException())
return JSValue::encode(jsUndefined());
JSPromiseDeferred* deferred = jsCast<JSPromiseDeferred*>(deferredValue);
// 4. Let 'iterator' be the result of calling GetIterator(iterable).
JSValue iteratorFunction = iterable.get(exec, vm.propertyNames->iteratorPrivateName);
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
CallData iteratorFunctionCallData;
CallType iteratorFunctionCallType = getCallData(iteratorFunction, iteratorFunctionCallData);
if (iteratorFunctionCallType == CallTypeNone) {
throwTypeError(exec);
return JSValue::encode(abruptRejection(exec, deferred));
}
ArgList iteratorFunctionArguments;
JSValue iterator = call(exec, iteratorFunction, iteratorFunctionCallType, iteratorFunctionCallData, iterable, iteratorFunctionArguments);
// 5. RejectIfAbrupt(iterator, deferred).
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
// 6. Repeat
do {
// i. Let 'next' be the result of calling IteratorStep(iterator).
JSValue nextFunction = iterator.get(exec, exec->vm().propertyNames->iteratorNextPrivateName);
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
CallData nextFunctionCallData;
CallType nextFunctionCallType = getCallData(nextFunction, nextFunctionCallData);
if (nextFunctionCallType == CallTypeNone) {
throwTypeError(exec);
return JSValue::encode(abruptRejection(exec, deferred));
}
MarkedArgumentBuffer nextFunctionArguments;
nextFunctionArguments.append(jsUndefined());
JSValue next = call(exec, nextFunction, nextFunctionCallType, nextFunctionCallData, iterator, nextFunctionArguments);
// ii. RejectIfAbrupt(next, deferred).
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
// iii. If 'next' is false, return deferred.[[Promise]].
// Note: We implement this as an iterationTerminator
if (next == vm.iterationTerminator.get())
return JSValue::encode(deferred->promise());
// iv. Let 'nextValue' be the result of calling IteratorValue(next).
// v. RejectIfAbrupt(nextValue, deferred).
// Note: 'next' is already the value, so there is nothing to do here.
// vi. Let 'nextPromise' be the result of calling Invoke(C, "cast", (nextValue)).
JSValue castFunction = C.get(exec, vm.propertyNames->cast);
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
CallData castFunctionCallData;
CallType castFunctionCallType = getCallData(castFunction, castFunctionCallData);
if (castFunctionCallType == CallTypeNone) {
throwTypeError(exec);
return JSValue::encode(abruptRejection(exec, deferred));
}
MarkedArgumentBuffer castFunctionArguments;
castFunctionArguments.append(next);
JSValue nextPromise = call(exec, castFunction, castFunctionCallType, castFunctionCallData, C, castFunctionArguments);
// vii. RejectIfAbrupt(nextPromise, deferred).
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
// viii. Let 'result' be the result of calling Invoke(nextPromise, "then", (deferred.[[Resolve]], deferred.[[Reject]])).
JSValue thenFunction = nextPromise.get(exec, vm.propertyNames->then);
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
CallData thenFunctionCallData;
CallType thenFunctionCallType = getCallData(thenFunction, thenFunctionCallData);
if (thenFunctionCallType == CallTypeNone) {
throwTypeError(exec);
return JSValue::encode(abruptRejection(exec, deferred));
}
MarkedArgumentBuffer thenFunctionArguments;
thenFunctionArguments.append(deferred->resolve());
thenFunctionArguments.append(deferred->reject());
call(exec, thenFunction, thenFunctionCallType, thenFunctionCallData, nextPromise, thenFunctionArguments);
// ix. RejectIfAbrupt(result, deferred).
if (exec->hadException())
return JSValue::encode(abruptRejection(exec, deferred));
} while (true);
}
JSPromise* constructPromise(ExecState* exec, JSGlobalObject* globalObject, JSFunction* resolver)
{
JSPromiseConstructor* promiseConstructor = globalObject->promiseConstructor();
ConstructData constructData;
ConstructType constructType = getConstructData(promiseConstructor, constructData);
ASSERT(constructType != ConstructTypeNone);
MarkedArgumentBuffer arguments;
arguments.append(resolver);
return jsCast<JSPromise*>(construct(exec, promiseConstructor, constructType, constructData, arguments));
}
} // namespace JSC
#endif // ENABLE(PROMISES)