JSPromiseDeferred.cpp   [plain text]


/*
 * 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 "JSPromiseDeferred.h"

#if ENABLE(PROMISES)

#include "Error.h"
#include "JSCJSValueInlines.h"
#include "JSCellInlines.h"
#include "JSPromise.h"
#include "JSPromiseConstructor.h"
#include "JSPromiseFunctions.h"
#include "SlotVisitorInlines.h"
#include "StructureInlines.h"

namespace JSC {

const ClassInfo JSPromiseDeferred::s_info = { "JSPromiseDeferred", 0, 0, 0, CREATE_METHOD_TABLE(JSPromiseDeferred) };

JSPromiseDeferred* JSPromiseDeferred::create(ExecState* exec, JSGlobalObject* globalObject)
{
    VM& vm = exec->vm();
    
    JSFunction* resolver = createDeferredConstructionFunction(vm, globalObject);

    JSPromise* promise = constructPromise(exec, globalObject, resolver);
    JSValue resolve = resolver->get(exec, vm.propertyNames->resolvePrivateName);
    JSValue reject = resolver->get(exec, vm.propertyNames->rejectPrivateName);

    return JSPromiseDeferred::create(vm, promise, resolve, reject);
}

JSPromiseDeferred* JSPromiseDeferred::create(VM& vm, JSObject* promise, JSValue resolve, JSValue reject)
{
    JSPromiseDeferred* deferred = new (NotNull, allocateCell<JSPromiseDeferred>(vm.heap)) JSPromiseDeferred(vm);
    deferred->finishCreation(vm, promise, resolve, reject);
    return deferred;
}

JSPromiseDeferred::JSPromiseDeferred(VM& vm)
    : Base(vm, vm.promiseDeferredStructure.get())
{
}

void JSPromiseDeferred::finishCreation(VM& vm, JSObject* promise, JSValue resolve, JSValue reject)
{
    Base::finishCreation(vm);
    m_promise.set(vm, this, promise);
    m_resolve.set(vm, this, resolve);
    m_reject.set(vm, this, reject);
}

void JSPromiseDeferred::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
    JSPromiseDeferred* thisObject = jsCast<JSPromiseDeferred*>(cell);
    ASSERT_GC_OBJECT_INHERITS(thisObject, info());
    COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag);
    ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren());

    Base::visitChildren(thisObject, visitor);

    visitor.append(&thisObject->m_promise);
    visitor.append(&thisObject->m_resolve);
    visitor.append(&thisObject->m_reject);
}

JSValue createJSPromiseDeferredFromConstructor(ExecState* exec, JSValue C)
{
    // -- This implements the GetDeferred(C) abstract operation --

    // 1. If IsConstructor(C) is false, throw a TypeError.
    if (!C.isObject())
        return throwTypeError(exec);

    ConstructData constructData;
    ConstructType constructType = getConstructData(C, constructData);
    if (constructType == ConstructTypeNone)
        return throwTypeError(exec);

    VM& vm = exec->vm();

    // 2. Let 'resolver' be a new built-in function object as defined in Deferred Construction Functions.
    JSFunction* resolver = createDeferredConstructionFunction(vm, asObject(C)->globalObject());

    // 3. Let 'promise' be the result of calling the [[Construct]] internal method of 'C' with
    //    an argument list containing the single item resolver.
    MarkedArgumentBuffer constructArguments;
    constructArguments.append(resolver);
    JSObject* promise = construct(exec, C, constructType, constructData, constructArguments);

    // 4. ReturnIfAbrupt(promise).
    if (exec->hadException())
        return jsUndefined();

    // 5. Let 'resolve' be the value of resolver's [[Resolve]] internal slot.
    JSValue resolve = resolver->get(exec, vm.propertyNames->resolvePrivateName);

    // 6. If IsCallable(resolve) is false, throw a TypeError.
    CallData resolveCallData;
    CallType resolveCallType = getCallData(resolve, resolveCallData);
    if (resolveCallType == CallTypeNone)
        return throwTypeError(exec);

    // 7. Let 'reject' be the value of resolver's [[Reject]] internal slot.
    JSValue reject = resolver->get(exec, vm.propertyNames->rejectPrivateName);

    // 8. If IsCallable(reject) is false, throw a TypeError.
    CallData rejectCallData;
    CallType rejectCallType = getCallData(reject, rejectCallData);
    if (rejectCallType == CallTypeNone)
        return throwTypeError(exec);

    // 9. Return the Deferred { [[Promise]]: promise, [[Resolve]]: resolve, [[Reject]]: reject }.
    return JSPromiseDeferred::create(exec->vm(), promise, resolve, reject);
}

ThenableStatus updateDeferredFromPotentialThenable(ExecState* exec, JSValue x, JSPromiseDeferred* deferred)
{
    // 1. If Type(x) is not Object, return "not a thenable".
    if (!x.isObject())
        return NotAThenable;
    
    // 2. Let 'then' be the result of calling Get(x, "then").
    JSValue thenValue = x.get(exec, exec->vm().propertyNames->then);
    
    // 3. If then is an abrupt completion,
    if (exec->hadException()) {
        // i. Let 'rejectResult' be the result of calling the [[Call]] internal method of
        //    deferred.[[Reject]] with undefined as thisArgument and a List containing
        //    then.[[value]] as argumentsList.
        JSValue exception = exec->exception();
        exec->clearException();

        performDeferredReject(exec, deferred, exception);

        // ii. ReturnIfAbrupt(rejectResult).
        // NOTE: Nothing to do.

        // iii. Return.
        return WasAThenable;
    }

    // 4. Let 'then' be then.[[value]].
    // Note: Nothing to do.

    // 5. If IsCallable(then) is false, return "not a thenable".
    CallData thenCallData;
    CallType thenCallType = getCallData(thenValue, thenCallData);
    if (thenCallType == CallTypeNone)
        return NotAThenable;

    // 6. Let 'thenCallResult' be the result of calling the [[Call]] internal method of
    //    'then' passing x as thisArgument and a List containing deferred.[[Resolve]] and
    //    deferred.[[Reject]] as argumentsList.
    MarkedArgumentBuffer thenArguments;
    thenArguments.append(deferred->resolve());
    thenArguments.append(deferred->reject());
    
    call(exec, thenValue, thenCallType, thenCallData, x, thenArguments);

    // 7. If 'thenCallResult' is an abrupt completion,
    if (exec->hadException()) {
        // i. Let 'rejectResult' be the result of calling the [[Call]] internal method of
        //    deferred.[[Reject]] with undefined as thisArgument and a List containing
        //    thenCallResult.[[value]] as argumentsList.
        JSValue exception = exec->exception();
        exec->clearException();

        performDeferredReject(exec, deferred, exception);

        // ii. ReturnIfAbrupt(rejectResult).
        // NOTE: Nothing to do.
    }

    return WasAThenable;
}

void performDeferredResolve(ExecState* exec, JSPromiseDeferred* deferred, JSValue argument)
{
    JSValue deferredResolve = deferred->resolve();

    CallData resolveCallData;
    CallType resolveCallType = getCallData(deferredResolve, resolveCallData);
    ASSERT(resolveCallType != CallTypeNone);

    MarkedArgumentBuffer arguments;
    arguments.append(argument);

    call(exec, deferredResolve, resolveCallType, resolveCallData, jsUndefined(), arguments);
}

void performDeferredReject(ExecState* exec, JSPromiseDeferred* deferred, JSValue argument)
{
    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);
}

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.
    performDeferredReject(exec, deferred, argument);

    // ii. ReturnIfAbrupt(rejectResult).
    if (exec->hadException())
        return jsUndefined();

    // iii. Return deferred.[[Promise]].
    return deferred->promise();
}

} // namespace JSC

#endif // ENABLE(PROMISES)