Home | History | Annotate | Download | only in v8
      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 
     28 using namespace blink;
     29 
     30 namespace {
     31 
     32 class NotReached : public ScriptFunction {
     33 public:
     34     static v8::Handle<v8::Function> createFunction(ScriptState* scriptState)
     35     {
     36         NotReached* self = new NotReached(scriptState);
     37         return self->bindToV8Function();
     38     }
     39 
     40 private:
     41     explicit NotReached(ScriptState* scriptState)
     42         : ScriptFunction(scriptState)
     43     {
     44     }
     45 
     46     virtual ScriptValue call(ScriptValue) OVERRIDE;
     47 };
     48 
     49 ScriptValue NotReached::call(ScriptValue)
     50 {
     51     EXPECT_TRUE(false) << "'Unreachable' code was reached";
     52     return ScriptValue();
     53 }
     54 
     55 class StubFunction : public ScriptFunction {
     56 public:
     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 
     63 private:
     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 
     82 class GarbageCollectedHolder : public GarbageCollectedScriptWrappable {
     83 public:
     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 
     98 private:
     99     Member<Property> m_property;
    100 };
    101 
    102 class RefCountedHolder : public RefCountedScriptWrappable {
    103 public:
    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 
    114 private:
    115     RefCountedHolder(ExecutionContext* executionContext)
    116         : RefCountedScriptWrappable("holder")
    117         , m_property(new Property(executionContext, toRefCountedScriptWrappable(), Property::Ready)) { }
    118 
    119     Persistent<Property> m_property;
    120 };
    121 
    122 class ScriptPromisePropertyTestBase {
    123 public:
    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 
    168 private:
    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.
    176 class ScriptPromisePropertyGarbageCollectedTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
    177 public:
    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 
    195 private:
    196     Persistent<GarbageCollectedHolder> m_holder;
    197 };
    198 
    199 TEST_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 
    212 TEST_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 
    234 TEST_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 
    247 TEST_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 
    267 TEST_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 
    299 TEST_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 
    334 TEST_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 
    361 TEST_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 
    372 TEST_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 
    390 TEST_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.
    428 class ScriptPromisePropertyRefCountedTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
    429 public:
    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 
    440 private:
    441     RefPtr<RefCountedHolder> m_holder;
    442 };
    443 
    444 TEST_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 
    463 TEST_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 
    482 TEST_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 
    496 TEST_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.
    512 class ScriptPromisePropertyNonScriptWrappableResolutionTargetTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
    513 public:
    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 
    539 TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithUndefined)
    540 {
    541     test(V8UndefinedType(), "undefined", __FILE__, __LINE__);
    542 }
    543 
    544 TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithString)
    545 {
    546     test(String("hello"), "hello", __FILE__, __LINE__);
    547 }
    548 
    549 TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithInteger)
    550 {
    551     test<int>(-1, "-1", __FILE__, __LINE__);
    552 }
    553 
    554 } // namespace
    555