1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef ScriptPromiseResolver_h
6#define ScriptPromiseResolver_h
7
8#include "bindings/core/v8/ScopedPersistent.h"
9#include "bindings/core/v8/ScriptPromise.h"
10#include "bindings/core/v8/ScriptState.h"
11#include "bindings/core/v8/V8Binding.h"
12#include "core/dom/ActiveDOMObject.h"
13#include "core/dom/ExecutionContext.h"
14#include "platform/Timer.h"
15#include "wtf/RefCounted.h"
16#include <v8.h>
17
18namespace blink {
19
20// This class wraps v8::Promise::Resolver and provides the following
21// functionalities.
22//  - A ScriptPromiseResolver retains a ScriptState. A caller
23//    can call resolve or reject from outside of a V8 context.
24//  - This class is an ActiveDOMObject and keeps track of the associated
25//    ExecutionContext state. When the ExecutionContext is suspended,
26//    resolve or reject will be delayed. When it is stopped, resolve or reject
27//    will be ignored.
28class ScriptPromiseResolver : public ActiveDOMObject, public RefCounted<ScriptPromiseResolver> {
29    WTF_MAKE_NONCOPYABLE(ScriptPromiseResolver);
30
31public:
32    static PassRefPtr<ScriptPromiseResolver> create(ScriptState* scriptState)
33    {
34        RefPtr<ScriptPromiseResolver> resolver = adoptRef(new ScriptPromiseResolver(scriptState));
35        resolver->suspendIfNeeded();
36        return resolver.release();
37    }
38
39    virtual ~ScriptPromiseResolver()
40    {
41        // This assertion fails if:
42        //  - promise() is called at least once and
43        //  - this resolver is destructed before it is resolved, rejected or
44        //    the associated ExecutionContext is stopped.
45        ASSERT(m_state == ResolvedOrRejected || !m_isPromiseCalled);
46    }
47
48    // Anything that can be passed to toV8Value can be passed to this function.
49    template <typename T>
50    void resolve(T value)
51    {
52        resolveOrReject(value, Resolving);
53    }
54
55    // Anything that can be passed to toV8Value can be passed to this function.
56    template <typename T>
57    void reject(T value)
58    {
59        resolveOrReject(value, Rejecting);
60    }
61
62    void resolve() { resolve(V8UndefinedType()); }
63    void reject() { reject(V8UndefinedType()); }
64
65    ScriptState* scriptState() { return m_scriptState.get(); }
66
67    // Note that an empty ScriptPromise will be returned after resolve or
68    // reject is called.
69    ScriptPromise promise()
70    {
71#if ENABLE(ASSERT)
72        m_isPromiseCalled = true;
73#endif
74        return m_resolver.promise();
75    }
76
77    ScriptState* scriptState() const { return m_scriptState.get(); }
78
79    // ActiveDOMObject implementation.
80    virtual void suspend() OVERRIDE;
81    virtual void resume() OVERRIDE;
82    virtual void stop() OVERRIDE;
83
84    // Once this function is called this resolver stays alive while the
85    // promise is pending and the associated ExecutionContext isn't stopped.
86    void keepAliveWhilePending();
87
88protected:
89    // You need to call suspendIfNeeded after the construction because
90    // this is an ActiveDOMObject.
91    explicit ScriptPromiseResolver(ScriptState*);
92
93private:
94    typedef ScriptPromise::InternalResolver Resolver;
95    enum ResolutionState {
96        Pending,
97        Resolving,
98        Rejecting,
99        ResolvedOrRejected,
100    };
101    enum LifetimeMode {
102        Default,
103        KeepAliveWhilePending,
104    };
105
106    template<typename T>
107    v8::Handle<v8::Value> toV8Value(const T& value)
108    {
109        return V8ValueTraits<T>::toV8Value(value, m_scriptState->context()->Global(), m_scriptState->isolate());
110    }
111
112    template <typename T>
113    void resolveOrReject(T value, ResolutionState newState)
114    {
115        if (m_state != Pending || !executionContext() || executionContext()->activeDOMObjectsAreStopped())
116            return;
117        ASSERT(newState == Resolving || newState == Rejecting);
118        m_state = newState;
119        // Retain this object until it is actually resolved or rejected.
120        // |deref| will be called in |clear|.
121        ref();
122
123        ScriptState::Scope scope(m_scriptState.get());
124        m_value.set(m_scriptState->isolate(), toV8Value(value));
125        if (!executionContext()->activeDOMObjectsAreSuspended())
126            resolveOrRejectImmediately();
127    }
128
129    void resolveOrRejectImmediately();
130    void onTimerFired(Timer<ScriptPromiseResolver>*);
131    void clear();
132
133    ResolutionState m_state;
134    const RefPtr<ScriptState> m_scriptState;
135    LifetimeMode m_mode;
136    Timer<ScriptPromiseResolver> m_timer;
137    Resolver m_resolver;
138    ScopedPersistent<v8::Value> m_value;
139#if ENABLE(ASSERT)
140    // True if promise() is called.
141    bool m_isPromiseCalled;
142#endif
143};
144
145} // namespace blink
146
147#endif // #ifndef ScriptPromiseResolver_h
148