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 "bindings/core/v8/ScriptPromiseProperty.h"
7
8#include "bindings/core/v8/DOMWrapperWorld.h"
9#include "bindings/core/v8/ScriptFunction.h"
10#include "bindings/core/v8/ScriptPromise.h"
11#include "bindings/core/v8/ScriptState.h"
12#include "bindings/core/v8/ScriptValue.h"
13#include "bindings/core/v8/V8Binding.h"
14#include "bindings/core/v8/V8GCController.h"
15#include "core/dom/Document.h"
16#include "core/testing/DummyPageHolder.h"
17#include "core/testing/GCObservation.h"
18#include "core/testing/GarbageCollectedScriptWrappable.h"
19#include "core/testing/RefCountedScriptWrappable.h"
20#include "platform/heap/Handle.h"
21#include "wtf/OwnPtr.h"
22#include "wtf/PassOwnPtr.h"
23#include "wtf/PassRefPtr.h"
24#include "wtf/RefPtr.h"
25#include <gtest/gtest.h>
26#include <v8.h>
27
28using namespace blink;
29
30namespace {
31
32class NotReached : public ScriptFunction {
33public:
34    static v8::Handle<v8::Function> createFunction(ScriptState* scriptState)
35    {
36        NotReached* self = new NotReached(scriptState);
37        return self->bindToV8Function();
38    }
39
40private:
41    explicit NotReached(ScriptState* scriptState)
42        : ScriptFunction(scriptState)
43    {
44    }
45
46    virtual ScriptValue call(ScriptValue) OVERRIDE;
47};
48
49ScriptValue NotReached::call(ScriptValue)
50{
51    EXPECT_TRUE(false) << "'Unreachable' code was reached";
52    return ScriptValue();
53}
54
55class StubFunction : public ScriptFunction {
56public:
57    static v8::Handle<v8::Function> createFunction(ScriptState* scriptState, ScriptValue& value, size_t& callCount)
58    {
59        StubFunction* self = new StubFunction(scriptState, value, callCount);
60        return self->bindToV8Function();
61    }
62
63private:
64    StubFunction(ScriptState* scriptState, ScriptValue& value, size_t& callCount)
65        : ScriptFunction(scriptState)
66        , m_value(value)
67        , m_callCount(callCount)
68    {
69    }
70
71    virtual ScriptValue call(ScriptValue arg) OVERRIDE
72    {
73        m_value = arg;
74        m_callCount++;
75        return ScriptValue();
76    }
77
78    ScriptValue& m_value;
79    size_t& m_callCount;
80};
81
82class GarbageCollectedHolder : public GarbageCollectedScriptWrappable {
83public:
84    typedef ScriptPromiseProperty<Member<GarbageCollectedScriptWrappable>, Member<GarbageCollectedScriptWrappable>, Member<GarbageCollectedScriptWrappable> > Property;
85    GarbageCollectedHolder(ExecutionContext* executionContext)
86        : GarbageCollectedScriptWrappable("holder")
87        , m_property(new Property(executionContext, toGarbageCollectedScriptWrappable(), Property::Ready)) { }
88
89    Property* property() { return m_property; }
90    GarbageCollectedScriptWrappable* toGarbageCollectedScriptWrappable() { return this; }
91
92    virtual void trace(Visitor *visitor) OVERRIDE
93    {
94        GarbageCollectedScriptWrappable::trace(visitor);
95        visitor->trace(m_property);
96    }
97
98private:
99    Member<Property> m_property;
100};
101
102class RefCountedHolder : public RefCountedScriptWrappable {
103public:
104    // Do not resolve or reject the property with the holder itself. It leads
105    // to a leak.
106    typedef ScriptPromiseProperty<RefCountedScriptWrappable*, RefPtr<RefCountedScriptWrappable>, RefPtr<RefCountedScriptWrappable> > Property;
107    static PassRefPtr<RefCountedHolder> create(ExecutionContext* executionContext)
108    {
109        return adoptRef(new RefCountedHolder(executionContext));
110    }
111    Property* property() { return m_property; }
112    RefCountedScriptWrappable* toRefCountedScriptWrappable() { return this; }
113
114private:
115    RefCountedHolder(ExecutionContext* executionContext)
116        : RefCountedScriptWrappable("holder")
117        , m_property(new Property(executionContext, toRefCountedScriptWrappable(), Property::Ready)) { }
118
119    Persistent<Property> m_property;
120};
121
122class ScriptPromisePropertyTestBase {
123public:
124    ScriptPromisePropertyTestBase()
125        : m_page(DummyPageHolder::create(IntSize(1, 1)))
126    {
127        v8::HandleScope handleScope(isolate());
128        m_otherScriptState = ScriptStateForTesting::create(v8::Context::New(isolate()), DOMWrapperWorld::create(1));
129    }
130
131    virtual ~ScriptPromisePropertyTestBase()
132    {
133        m_page.clear();
134        gc();
135        Heap::collectAllGarbage();
136    }
137
138    Document& document() { return m_page->document(); }
139    v8::Isolate* isolate() { return toIsolate(&document()); }
140    ScriptState* mainScriptState() { return ScriptState::forMainWorld(document().frame()); }
141    DOMWrapperWorld& mainWorld() { return mainScriptState()->world(); }
142    ScriptState* otherScriptState() { return m_otherScriptState.get(); }
143    DOMWrapperWorld& otherWorld() { return m_otherScriptState->world(); }
144    ScriptState* currentScriptState() { return ScriptState::current(isolate()); }
145
146    virtual void destroyContext()
147    {
148        m_page.clear();
149        m_otherScriptState.clear();
150        gc();
151        Heap::collectGarbage(ThreadState::HeapPointersOnStack);
152    }
153
154    void gc() { V8GCController::collectGarbage(v8::Isolate::GetCurrent()); }
155
156    v8::Handle<v8::Function> notReached(ScriptState* scriptState) { return NotReached::createFunction(scriptState); }
157    v8::Handle<v8::Function> stub(ScriptState* scriptState, ScriptValue& value, size_t& callCount) { return StubFunction::createFunction(scriptState, value, callCount); }
158
159    template <typename T>
160    ScriptValue wrap(DOMWrapperWorld& world, const T& value)
161    {
162        v8::HandleScope handleScope(isolate());
163        ScriptState* scriptState = ScriptState::from(toV8Context(&document(), world));
164        ScriptState::Scope scope(scriptState);
165        return ScriptValue(scriptState, V8ValueTraits<T>::toV8Value(value, scriptState->context()->Global(), isolate()));
166    }
167
168private:
169    OwnPtr<DummyPageHolder> m_page;
170    RefPtr<ScriptState> m_otherScriptState;
171};
172
173// This is the main test class.
174// If you want to examine a testcase independent of holder types, place the
175// test on this class.
176class ScriptPromisePropertyGarbageCollectedTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
177public:
178    typedef GarbageCollectedHolder::Property Property;
179
180    ScriptPromisePropertyGarbageCollectedTest()
181        : m_holder(new GarbageCollectedHolder(&document()))
182    {
183    }
184
185    GarbageCollectedHolder* holder() { return m_holder; }
186    Property* property() { return m_holder->property(); }
187    ScriptPromise promise(DOMWrapperWorld& world) { return property()->promise(world); }
188
189    virtual void destroyContext() OVERRIDE
190    {
191        m_holder = nullptr;
192        ScriptPromisePropertyTestBase::destroyContext();
193    }
194
195private:
196    Persistent<GarbageCollectedHolder> m_holder;
197};
198
199TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_IsStableObjectInMainWorld)
200{
201    ScriptPromise v = property()->promise(DOMWrapperWorld::mainWorld());
202    ScriptPromise w = property()->promise(DOMWrapperWorld::mainWorld());
203    EXPECT_EQ(v, w);
204    ASSERT_FALSE(v.isEmpty());
205    {
206        ScriptState::Scope scope(mainScriptState());
207        EXPECT_EQ(v.v8Value().As<v8::Object>()->CreationContext(), toV8Context(&document(), mainWorld()));
208    }
209    EXPECT_EQ(Property::Pending, property()->state());
210}
211
212TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_IsStableObjectInVariousWorlds)
213{
214    ScriptPromise u = property()->promise(otherWorld());
215    ScriptPromise v = property()->promise(DOMWrapperWorld::mainWorld());
216    ScriptPromise w = property()->promise(DOMWrapperWorld::mainWorld());
217    EXPECT_NE(mainScriptState(), otherScriptState());
218    EXPECT_NE(&mainWorld(), &otherWorld());
219    EXPECT_NE(u, v);
220    EXPECT_EQ(v, w);
221    ASSERT_FALSE(u.isEmpty());
222    ASSERT_FALSE(v.isEmpty());
223    {
224        ScriptState::Scope scope(otherScriptState());
225        EXPECT_EQ(u.v8Value().As<v8::Object>()->CreationContext(), toV8Context(&document(), otherWorld()));
226    }
227    {
228        ScriptState::Scope scope(mainScriptState());
229        EXPECT_EQ(v.v8Value().As<v8::Object>()->CreationContext(), toV8Context(&document(), mainWorld()));
230    }
231    EXPECT_EQ(Property::Pending, property()->state());
232}
233
234TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_IsStableObjectAfterSettling)
235{
236    ScriptPromise v = promise(DOMWrapperWorld::mainWorld());
237    GarbageCollectedScriptWrappable* value = new GarbageCollectedScriptWrappable("value");
238
239    property()->resolve(value);
240    EXPECT_EQ(Property::Resolved, property()->state());
241
242    ScriptPromise w = promise(DOMWrapperWorld::mainWorld());
243    EXPECT_EQ(v, w);
244    EXPECT_FALSE(v.isEmpty());
245}
246
247TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_DoesNotImpedeGarbageCollection)
248{
249    ScriptValue holderWrapper = wrap(mainWorld(), holder()->toGarbageCollectedScriptWrappable());
250
251    Persistent<GCObservation> observation;
252    {
253        ScriptState::Scope scope(mainScriptState());
254        observation = GCObservation::create(promise(DOMWrapperWorld::mainWorld()).v8Value());
255    }
256
257    gc();
258    EXPECT_FALSE(observation->wasCollected());
259
260    holderWrapper.clear();
261    gc();
262    EXPECT_TRUE(observation->wasCollected());
263
264    EXPECT_EQ(Property::Pending, property()->state());
265}
266
267TEST_F(ScriptPromisePropertyGarbageCollectedTest, Resolve_ResolvesScriptPromise)
268{
269    ScriptPromise promise = property()->promise(DOMWrapperWorld::mainWorld());
270    ScriptPromise otherPromise = property()->promise(otherWorld());
271    ScriptValue actual, otherActual;
272    size_t nResolveCalls = 0;
273    size_t nOtherResolveCalls = 0;
274
275    {
276        ScriptState::Scope scope(mainScriptState());
277        promise.then(stub(currentScriptState(), actual, nResolveCalls), notReached(currentScriptState()));
278    }
279
280    {
281        ScriptState::Scope scope(otherScriptState());
282        otherPromise.then(stub(currentScriptState(), otherActual, nOtherResolveCalls), notReached(currentScriptState()));
283    }
284
285    EXPECT_NE(promise, otherPromise);
286
287    GarbageCollectedScriptWrappable* value = new GarbageCollectedScriptWrappable("value");
288    property()->resolve(value);
289    EXPECT_EQ(Property::Resolved, property()->state());
290
291    isolate()->RunMicrotasks();
292    EXPECT_EQ(1u, nResolveCalls);
293    EXPECT_EQ(1u, nOtherResolveCalls);
294    EXPECT_EQ(wrap(mainWorld(), value), actual);
295    EXPECT_NE(actual, otherActual);
296    EXPECT_EQ(wrap(otherWorld(), value), otherActual);
297}
298
299TEST_F(ScriptPromisePropertyGarbageCollectedTest, ResolveAndGetPromiseOnOtherWorld)
300{
301    ScriptPromise promise = property()->promise(DOMWrapperWorld::mainWorld());
302    ScriptPromise otherPromise = property()->promise(otherWorld());
303    ScriptValue actual, otherActual;
304    size_t nResolveCalls = 0;
305    size_t nOtherResolveCalls = 0;
306
307    {
308        ScriptState::Scope scope(mainScriptState());
309        promise.then(stub(currentScriptState(), actual, nResolveCalls), notReached(currentScriptState()));
310    }
311
312    EXPECT_NE(promise, otherPromise);
313    GarbageCollectedScriptWrappable* value = new GarbageCollectedScriptWrappable("value");
314    property()->resolve(value);
315    EXPECT_EQ(Property::Resolved, property()->state());
316
317    isolate()->RunMicrotasks();
318    EXPECT_EQ(1u, nResolveCalls);
319    EXPECT_EQ(0u, nOtherResolveCalls);
320
321    {
322        ScriptState::Scope scope(otherScriptState());
323        otherPromise.then(stub(currentScriptState(), otherActual, nOtherResolveCalls), notReached(currentScriptState()));
324    }
325
326    isolate()->RunMicrotasks();
327    EXPECT_EQ(1u, nResolveCalls);
328    EXPECT_EQ(1u, nOtherResolveCalls);
329    EXPECT_EQ(wrap(mainWorld(), value), actual);
330    EXPECT_NE(actual, otherActual);
331    EXPECT_EQ(wrap(otherWorld(), value), otherActual);
332}
333
334TEST_F(ScriptPromisePropertyGarbageCollectedTest, Reject_RejectsScriptPromise)
335{
336    GarbageCollectedScriptWrappable* reason = new GarbageCollectedScriptWrappable("reason");
337    property()->reject(reason);
338    EXPECT_EQ(Property::Rejected, property()->state());
339
340    ScriptValue actual, otherActual;
341    size_t nRejectCalls = 0;
342    size_t nOtherRejectCalls = 0;
343    {
344        ScriptState::Scope scope(mainScriptState());
345        property()->promise(DOMWrapperWorld::mainWorld()).then(notReached(currentScriptState()), stub(currentScriptState(), actual, nRejectCalls));
346    }
347
348    {
349        ScriptState::Scope scope(otherScriptState());
350        property()->promise(otherWorld()).then(notReached(currentScriptState()), stub(currentScriptState(), otherActual, nOtherRejectCalls));
351    }
352
353    isolate()->RunMicrotasks();
354    EXPECT_EQ(1u, nRejectCalls);
355    EXPECT_EQ(wrap(mainWorld(), reason), actual);
356    EXPECT_EQ(1u, nOtherRejectCalls);
357    EXPECT_NE(actual, otherActual);
358    EXPECT_EQ(wrap(otherWorld(), reason), otherActual);
359}
360
361TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_DeadContext)
362{
363    Property* property = this->property();
364    property->resolve(new GarbageCollectedScriptWrappable("value"));
365    EXPECT_EQ(Property::Resolved, property->state());
366
367    destroyContext();
368
369    EXPECT_TRUE(property->promise(DOMWrapperWorld::mainWorld()).isEmpty());
370}
371
372TEST_F(ScriptPromisePropertyGarbageCollectedTest, Resolve_DeadContext)
373{
374    Property* property = this->property();
375
376    {
377        ScriptState::Scope scope(mainScriptState());
378        property->promise(DOMWrapperWorld::mainWorld()).then(notReached(currentScriptState()), notReached(currentScriptState()));
379    }
380
381    destroyContext();
382    EXPECT_TRUE(!property->executionContext() || property->executionContext()->activeDOMObjectsAreStopped());
383
384    property->resolve(new GarbageCollectedScriptWrappable("value"));
385    EXPECT_EQ(Property::Pending, property->state());
386
387    v8::Isolate::GetCurrent()->RunMicrotasks();
388}
389
390TEST_F(ScriptPromisePropertyGarbageCollectedTest, Reset)
391{
392    ScriptPromise oldPromise, newPromise;
393    ScriptValue oldActual, newActual;
394    GarbageCollectedScriptWrappable* oldValue = new GarbageCollectedScriptWrappable("old");
395    GarbageCollectedScriptWrappable* newValue = new GarbageCollectedScriptWrappable("new");
396    size_t nOldResolveCalls = 0;
397    size_t nNewRejectCalls = 0;
398
399    {
400        ScriptState::Scope scope(mainScriptState());
401        property()->resolve(oldValue);
402        oldPromise = property()->promise(mainWorld());
403        oldPromise.then(stub(currentScriptState(), oldActual, nOldResolveCalls), notReached(currentScriptState()));
404    }
405
406    property()->reset();
407
408    {
409        ScriptState::Scope scope(mainScriptState());
410        newPromise = property()->promise(mainWorld());
411        newPromise.then(notReached(currentScriptState()), stub(currentScriptState(), newActual, nNewRejectCalls));
412        property()->reject(newValue);
413    }
414
415    EXPECT_EQ(0u, nOldResolveCalls);
416    EXPECT_EQ(0u, nNewRejectCalls);
417
418    isolate()->RunMicrotasks();
419    EXPECT_EQ(1u, nOldResolveCalls);
420    EXPECT_EQ(1u, nNewRejectCalls);
421    EXPECT_NE(oldPromise, newPromise);
422    EXPECT_EQ(wrap(mainWorld(), oldValue), oldActual);
423    EXPECT_EQ(wrap(mainWorld(), newValue), newActual);
424    EXPECT_NE(oldActual, newActual);
425}
426
427// Tests that ScriptPromiseProperty works with a ref-counted holder.
428class ScriptPromisePropertyRefCountedTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
429public:
430    typedef RefCountedHolder::Property Property;
431
432    ScriptPromisePropertyRefCountedTest()
433        : m_holder(RefCountedHolder::create(&document()))
434    {
435    }
436
437    RefCountedHolder* holder() { return m_holder.get(); }
438    Property* property() { return m_holder->property(); }
439
440private:
441    RefPtr<RefCountedHolder> m_holder;
442};
443
444TEST_F(ScriptPromisePropertyRefCountedTest, Resolve)
445{
446    ScriptValue actual;
447    size_t nResolveCalls = 0;
448
449    {
450        ScriptState::Scope scope(mainScriptState());
451        property()->promise(DOMWrapperWorld::mainWorld()).then(stub(currentScriptState(), actual, nResolveCalls), notReached(currentScriptState()));
452    }
453
454    RefPtr<RefCountedScriptWrappable> value = RefCountedScriptWrappable::create("value");
455    property()->resolve(value.get());
456    EXPECT_EQ(Property::Resolved, property()->state());
457
458    isolate()->RunMicrotasks();
459    EXPECT_EQ(1u, nResolveCalls);
460    EXPECT_EQ(wrap(mainWorld(), value), actual);
461}
462
463TEST_F(ScriptPromisePropertyRefCountedTest, Reject)
464{
465    ScriptValue actual;
466    size_t nRejectCalls = 0;
467
468    {
469        ScriptState::Scope scope(mainScriptState());
470        property()->promise(DOMWrapperWorld::mainWorld()).then(notReached(currentScriptState()), stub(currentScriptState(), actual, nRejectCalls));
471    }
472
473    RefPtr<RefCountedScriptWrappable> reason = RefCountedScriptWrappable::create("reason");
474    property()->reject(reason);
475    EXPECT_EQ(Property::Rejected, property()->state());
476
477    isolate()->RunMicrotasks();
478    EXPECT_EQ(1u, nRejectCalls);
479    EXPECT_EQ(wrap(mainWorld(), reason), actual);
480}
481
482TEST_F(ScriptPromisePropertyRefCountedTest, ReSolveAndReset)
483{
484    RefPtr<RefCountedScriptWrappable> value = RefCountedScriptWrappable::create("value");
485
486    {
487        ScriptState::Scope scope(mainScriptState());
488        property()->resolve(value);
489    }
490
491    EXPECT_EQ(2, value->refCount());
492    property()->reset();
493    EXPECT_EQ(1, value->refCount());
494}
495
496TEST_F(ScriptPromisePropertyRefCountedTest, RejectAndReset)
497{
498    RefPtr<RefCountedScriptWrappable> value = RefCountedScriptWrappable::create("value");
499
500    {
501        ScriptState::Scope scope(mainScriptState());
502        property()->reject(value.get());
503    }
504
505    EXPECT_EQ(2, value->refCount());
506    property()->reset();
507    EXPECT_EQ(1, value->refCount());
508}
509
510// Tests that ScriptPromiseProperty works with a non ScriptWrappable resolution
511// target.
512class ScriptPromisePropertyNonScriptWrappableResolutionTargetTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
513public:
514    template <typename T>
515    void test(const T& value, const char* expected, const char* file, size_t line)
516    {
517        typedef ScriptPromiseProperty<Member<GarbageCollectedScriptWrappable>, T, V8UndefinedType> Property;
518        Property* property = new Property(&document(), new GarbageCollectedScriptWrappable("holder"), Property::Ready);
519        size_t nResolveCalls = 0;
520        ScriptValue actualValue;
521        String actual;
522        {
523            ScriptState::Scope scope(mainScriptState());
524            property->promise(DOMWrapperWorld::mainWorld()).then(stub(currentScriptState(), actualValue, nResolveCalls), notReached(currentScriptState()));
525        }
526        property->resolve(value);
527        isolate()->RunMicrotasks();
528        {
529            ScriptState::Scope scope(mainScriptState());
530            actual = toCoreString(actualValue.v8Value()->ToString());
531        }
532        if (expected != actual) {
533            ADD_FAILURE_AT(file, line) << "toV8Value returns an incorrect value.\n  Actual: " << actual.utf8().data() << "\nExpected: " << expected;
534            return;
535        }
536    }
537};
538
539TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithUndefined)
540{
541    test(V8UndefinedType(), "undefined", __FILE__, __LINE__);
542}
543
544TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithString)
545{
546    test(String("hello"), "hello", __FILE__, __LINE__);
547}
548
549TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithInteger)
550{
551    test<int>(-1, "-1", __FILE__, __LINE__);
552}
553
554} // namespace
555