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#include "config.h"
6#include "core/inspector/PromiseTracker.h"
7
8#include "bindings/core/v8/ScopedPersistent.h"
9#include "bindings/core/v8/ScriptCallStackFactory.h"
10#include "bindings/core/v8/ScriptState.h"
11#include "bindings/core/v8/ScriptValue.h"
12#include "wtf/PassOwnPtr.h"
13#include "wtf/WeakPtr.h"
14
15using blink::TypeBuilder::Array;
16using blink::TypeBuilder::Console::CallFrame;
17using blink::TypeBuilder::Debugger::PromiseDetails;
18
19namespace blink {
20
21class PromiseTracker::PromiseData FINAL : public RefCountedWillBeGarbageCollectedFinalized<PromiseData> {
22public:
23    static PassRefPtrWillBeRawPtr<PromiseData> create(ScriptState* scriptState, int promiseHash, int promiseId, v8::Handle<v8::Object> promise)
24    {
25        return adoptRefWillBeNoop(new PromiseData(scriptState, promiseHash, promiseId, promise));
26    }
27
28    int promiseHash() const { return m_promiseHash; }
29    ScopedPersistent<v8::Object>& promise() { return m_promise; }
30
31#if ENABLE(OILPAN)
32    void dispose()
33    {
34        m_promise.clear();
35        m_parentPromise.clear();
36    }
37#else
38    WeakPtr<PromiseData> createWeakPtr()
39    {
40        return m_weakPtrFactory.createWeakPtr();
41    }
42#endif
43
44    void trace(Visitor* visitor)
45    {
46        visitor->trace(m_callStack);
47    }
48
49private:
50    friend class PromiseTracker;
51
52    PromiseData(ScriptState* scriptState, int promiseHash, int promiseId, v8::Handle<v8::Object> promise)
53        : m_scriptState(scriptState)
54        , m_promiseHash(promiseHash)
55        , m_promiseId(promiseId)
56        , m_promise(scriptState->isolate(), promise)
57        , m_parentPromiseId(0)
58        , m_status(0)
59#if !ENABLE(OILPAN)
60        , m_weakPtrFactory(this)
61#endif
62    {
63    }
64
65    RefPtr<ScriptState> m_scriptState;
66    int m_promiseHash;
67    int m_promiseId;
68    ScopedPersistent<v8::Object> m_promise;
69    int m_parentPromiseId;
70    int m_status;
71    RefPtrWillBeMember<ScriptCallStack> m_callStack;
72    ScopedPersistent<v8::Object> m_parentPromise;
73#if !ENABLE(OILPAN)
74    WeakPtrFactory<PromiseData> m_weakPtrFactory;
75#endif
76};
77
78static int indexOf(PromiseTracker::PromiseDataVector* vector, const ScopedPersistent<v8::Object>& promise)
79{
80    for (size_t index = 0; index < vector->size(); ++index) {
81        if (vector->at(index)->promise() == promise)
82            return index;
83    }
84    return -1;
85}
86
87namespace {
88
89class PromiseDataWrapper FINAL : public NoBaseWillBeGarbageCollected<PromiseDataWrapper> {
90public:
91    static PassOwnPtrWillBeRawPtr<PromiseDataWrapper> create(PromiseTracker::PromiseData* data, PromiseTracker* tracker)
92    {
93#if ENABLE(OILPAN)
94        return new PromiseDataWrapper(data, tracker);
95#else
96        return adoptPtr(new PromiseDataWrapper(data->createWeakPtr(), tracker));
97#endif
98    }
99
100#if ENABLE(OILPAN)
101    static void didRemovePromise(const v8::WeakCallbackData<v8::Object, Persistent<PromiseDataWrapper> >& data)
102#else
103    static void didRemovePromise(const v8::WeakCallbackData<v8::Object, PromiseDataWrapper>& data)
104#endif
105    {
106#if ENABLE(OILPAN)
107        OwnPtr<Persistent<PromiseDataWrapper> > persistentWrapper = adoptPtr(data.GetParameter());
108        RawPtr<PromiseDataWrapper> wrapper = *persistentWrapper;
109#else
110        OwnPtr<PromiseDataWrapper> wrapper = adoptPtr(data.GetParameter());
111#endif
112        WeakPtrWillBeRawPtr<PromiseTracker::PromiseData> promiseData = wrapper->m_data;
113        if (!promiseData || !wrapper->m_tracker)
114            return;
115
116#if ENABLE(OILPAN)
117        // Oilpan: let go of ScopedPersistent<>s right here (and not wait until the
118        // PromiseDataWrapper is GCed later.) The v8 weak callback handling expects
119        // to see the callback data upon return.
120        promiseData->dispose();
121#endif
122        PromiseTracker::PromiseDataMap& map = wrapper->m_tracker->promiseDataMap();
123        int promiseHash = promiseData->promiseHash();
124
125        PromiseTracker::PromiseDataMap::iterator it = map.find(promiseHash);
126        // The PromiseTracker may have been disabled (and, possibly, re-enabled later),
127        // leaving the promiseHash as unmapped.
128        if (it == map.end())
129            return;
130
131        PromiseTracker::PromiseDataVector* vector = &it->value;
132        int index = indexOf(vector, promiseData->promise());
133        ASSERT(index >= 0);
134        vector->remove(index);
135        if (vector->isEmpty())
136            map.remove(promiseHash);
137    }
138
139    void trace(Visitor* visitor)
140    {
141#if ENABLE(OILPAN)
142        visitor->trace(m_data);
143        visitor->trace(m_tracker);
144#endif
145    }
146
147private:
148    PromiseDataWrapper(WeakPtrWillBeRawPtr<PromiseTracker::PromiseData> data, PromiseTracker* tracker)
149        : m_data(data)
150        , m_tracker(tracker)
151    {
152    }
153
154    WeakPtrWillBeWeakMember<PromiseTracker::PromiseData> m_data;
155    RawPtrWillBeWeakMember<PromiseTracker> m_tracker;
156};
157
158}
159
160PromiseTracker::PromiseTracker()
161    : m_circularSequentialId(0)
162    , m_isEnabled(false)
163{
164}
165
166DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(PromiseTracker);
167
168void PromiseTracker::trace(Visitor* visitor)
169{
170#if ENABLE(OILPAN)
171    visitor->trace(m_promiseDataMap);
172#endif
173}
174
175void PromiseTracker::setEnabled(bool enabled)
176{
177    m_isEnabled = enabled;
178    if (!enabled)
179        clear();
180}
181
182void PromiseTracker::clear()
183{
184    m_promiseDataMap.clear();
185}
186
187int PromiseTracker::circularSequentialId()
188{
189    ++m_circularSequentialId;
190    if (m_circularSequentialId <= 0)
191        m_circularSequentialId = 1;
192    return m_circularSequentialId;
193}
194
195PassRefPtrWillBeRawPtr<PromiseTracker::PromiseData> PromiseTracker::createPromiseDataIfNeeded(ScriptState* scriptState, v8::Handle<v8::Object> promise)
196{
197    int promiseHash = promise->GetIdentityHash();
198    RawPtr<PromiseDataVector> vector = nullptr;
199    PromiseDataMap::iterator it = m_promiseDataMap.find(promiseHash);
200    if (it != m_promiseDataMap.end())
201        vector = &it->value;
202    else
203        vector = &m_promiseDataMap.add(promiseHash, PromiseDataVector()).storedValue->value;
204
205    int index = indexOf(vector, ScopedPersistent<v8::Object>(scriptState->isolate(), promise));
206    if (index != -1)
207        return vector->at(index);
208
209    // FIXME: Consider using the ScriptState's DOMWrapperWorld instead
210    // to handle the lifetime of PromiseDataWrapper, avoiding all this
211    // manual labor to achieve the same, with and without Oilpan.
212    RefPtrWillBeRawPtr<PromiseData> data = PromiseData::create(scriptState, promiseHash, circularSequentialId(), promise);
213    OwnPtrWillBeRawPtr<PromiseDataWrapper> dataWrapper = PromiseDataWrapper::create(data.get(), this);
214#if ENABLE(OILPAN)
215    OwnPtr<Persistent<PromiseDataWrapper> > wrapper = adoptPtr(new Persistent<PromiseDataWrapper>(dataWrapper));
216#else
217    OwnPtr<PromiseDataWrapper> wrapper = dataWrapper.release();
218#endif
219    data->m_promise.setWeak(wrapper.leakPtr(), &PromiseDataWrapper::didRemovePromise);
220    vector->append(data);
221
222    return data.release();
223}
224
225void PromiseTracker::didReceiveV8PromiseEvent(ScriptState* scriptState, v8::Handle<v8::Object> promise, v8::Handle<v8::Value> parentPromise, int status)
226{
227    ASSERT(isEnabled());
228
229    RefPtrWillBeRawPtr<PromiseData> data = createPromiseDataIfNeeded(scriptState, promise);
230    if (!parentPromise.IsEmpty() && parentPromise->IsObject()) {
231        v8::Handle<v8::Object> handle = parentPromise->ToObject();
232        RefPtrWillBeRawPtr<PromiseData> parentData = createPromiseDataIfNeeded(scriptState, handle);
233        data->m_parentPromiseId = parentData->m_promiseId;
234        data->m_parentPromise.set(scriptState->isolate(), handle);
235    } else {
236        data->m_status = status;
237        if (!status && !data->m_callStack) {
238            RefPtrWillBeRawPtr<ScriptCallStack> stack = createScriptCallStack(1, true);
239            if (stack && stack->size())
240                data->m_callStack = stack;
241        }
242    }
243}
244
245PassRefPtr<Array<PromiseDetails> > PromiseTracker::promises()
246{
247    ASSERT(isEnabled());
248
249    RefPtr<Array<PromiseDetails> > result = Array<PromiseDetails>::create();
250    for (PromiseDataMap::iterator it = m_promiseDataMap.begin(); it != m_promiseDataMap.end(); ++it) {
251        PromiseDataVector* vector = &it->value;
252        for (size_t index = 0; index < vector->size(); ++index) {
253            RefPtrWillBeRawPtr<PromiseData> data = vector->at(index);
254            PromiseDetails::Status::Enum status;
255            if (!data->m_status)
256                status = PromiseDetails::Status::Pending;
257            else if (data->m_status == 1)
258                status = PromiseDetails::Status::Resolved;
259            else
260                status = PromiseDetails::Status::Rejected;
261            RefPtr<PromiseDetails> promiseDetails = PromiseDetails::create()
262                .setId(data->m_promiseId)
263                .setStatus(status);
264            if (data->m_parentPromiseId)
265                promiseDetails->setParentId(data->m_parentPromiseId);
266            if (data->m_callStack)
267                promiseDetails->setCallFrame(data->m_callStack->at(0).buildInspectorObject());
268            result->addItem(promiseDetails);
269        }
270    }
271
272    return result.release();
273}
274
275ScriptValue PromiseTracker::promiseById(int promiseId) const
276{
277    ASSERT(isEnabled());
278
279    for (PromiseDataMap::const_iterator it = m_promiseDataMap.begin(); it != m_promiseDataMap.end(); ++it) {
280        const PromiseDataVector* vector = &it->value;
281        for (size_t index = 0; index < vector->size(); ++index) {
282            RefPtrWillBeRawPtr<PromiseData> data = vector->at(index);
283            if (data->m_promiseId == promiseId) {
284                ScriptState* scriptState = data->m_scriptState.get();
285                return ScriptValue(scriptState, data->m_promise.newLocal(scriptState->isolate()));
286            }
287        }
288    }
289
290    return ScriptValue();
291}
292
293} // namespace blink
294