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.
6 #include "bindings/core/v8/ScriptPromiseProperty.h"
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>
28 using namespace blink;
32 class NotReached : public ScriptFunction {
34 static v8::Handle<v8::Function> createFunction(ScriptState* scriptState)
36 NotReached* self = new NotReached(scriptState);
37 return self->bindToV8Function();
41 explicit NotReached(ScriptState* scriptState)
42 : ScriptFunction(scriptState)
46 virtual ScriptValue call(ScriptValue) OVERRIDE;
49 ScriptValue NotReached::call(ScriptValue)
51 EXPECT_TRUE(false) << "'Unreachable' code was reached";
55 class StubFunction : public ScriptFunction {
57 static v8::Handle<v8::Function> createFunction(ScriptState* scriptState, ScriptValue& value, size_t& callCount)
59 StubFunction* self = new StubFunction(scriptState, value, callCount);
60 return self->bindToV8Function();
64 StubFunction(ScriptState* scriptState, ScriptValue& value, size_t& callCount)
65 : ScriptFunction(scriptState)
67 , m_callCount(callCount)
71 virtual ScriptValue call(ScriptValue arg) OVERRIDE
82 class GarbageCollectedHolder : public GarbageCollectedScriptWrappable {
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)) { }
89 Property* property() { return m_property; }
90 GarbageCollectedScriptWrappable* toGarbageCollectedScriptWrappable() { return this; }
92 virtual void trace(Visitor *visitor) OVERRIDE
94 GarbageCollectedScriptWrappable::trace(visitor);
95 visitor->trace(m_property);
99 Member<Property> m_property;
102 class RefCountedHolder : public RefCountedScriptWrappable {
104 // Do not resolve or reject the property with the holder itself. It leads
106 typedef ScriptPromiseProperty<RefCountedScriptWrappable*, RefPtr<RefCountedScriptWrappable>, RefPtr<RefCountedScriptWrappable> > Property;
107 static PassRefPtr<RefCountedHolder> create(ExecutionContext* executionContext)
109 return adoptRef(new RefCountedHolder(executionContext));
111 Property* property() { return m_property; }
112 RefCountedScriptWrappable* toRefCountedScriptWrappable() { return this; }
115 RefCountedHolder(ExecutionContext* executionContext)
116 : RefCountedScriptWrappable("holder")
117 , m_property(new Property(executionContext, toRefCountedScriptWrappable(), Property::Ready)) { }
119 Persistent<Property> m_property;
122 class ScriptPromisePropertyTestBase {
124 ScriptPromisePropertyTestBase()
125 : m_page(DummyPageHolder::create(IntSize(1, 1)))
127 v8::HandleScope handleScope(isolate());
128 m_otherScriptState = ScriptStateForTesting::create(v8::Context::New(isolate()), DOMWrapperWorld::create(1));
131 virtual ~ScriptPromisePropertyTestBase()
135 Heap::collectAllGarbage();
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()); }
146 virtual void destroyContext()
149 m_otherScriptState.clear();
151 Heap::collectGarbage(ThreadState::HeapPointersOnStack);
154 void gc() { V8GCController::collectGarbage(v8::Isolate::GetCurrent()); }
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); }
159 template <typename T>
160 ScriptValue wrap(DOMWrapperWorld& world, const T& value)
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()));
169 OwnPtr<DummyPageHolder> m_page;
170 RefPtr<ScriptState> m_otherScriptState;
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 {
178 typedef GarbageCollectedHolder::Property Property;
180 ScriptPromisePropertyGarbageCollectedTest()
181 : m_holder(new GarbageCollectedHolder(&document()))
185 GarbageCollectedHolder* holder() { return m_holder; }
186 Property* property() { return m_holder->property(); }
187 ScriptPromise promise(DOMWrapperWorld& world) { return property()->promise(world); }
189 virtual void destroyContext() OVERRIDE
192 ScriptPromisePropertyTestBase::destroyContext();
196 Persistent<GarbageCollectedHolder> m_holder;
199 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_IsStableObjectInMainWorld)
201 ScriptPromise v = property()->promise(DOMWrapperWorld::mainWorld());
202 ScriptPromise w = property()->promise(DOMWrapperWorld::mainWorld());
204 ASSERT_FALSE(v.isEmpty());
206 ScriptState::Scope scope(mainScriptState());
207 EXPECT_EQ(v.v8Value().As<v8::Object>()->CreationContext(), toV8Context(&document(), mainWorld()));
209 EXPECT_EQ(Property::Pending, property()->state());
212 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_IsStableObjectInVariousWorlds)
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());
221 ASSERT_FALSE(u.isEmpty());
222 ASSERT_FALSE(v.isEmpty());
224 ScriptState::Scope scope(otherScriptState());
225 EXPECT_EQ(u.v8Value().As<v8::Object>()->CreationContext(), toV8Context(&document(), otherWorld()));
228 ScriptState::Scope scope(mainScriptState());
229 EXPECT_EQ(v.v8Value().As<v8::Object>()->CreationContext(), toV8Context(&document(), mainWorld()));
231 EXPECT_EQ(Property::Pending, property()->state());
234 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_IsStableObjectAfterSettling)
236 ScriptPromise v = promise(DOMWrapperWorld::mainWorld());
237 GarbageCollectedScriptWrappable* value = new GarbageCollectedScriptWrappable("value");
239 property()->resolve(value);
240 EXPECT_EQ(Property::Resolved, property()->state());
242 ScriptPromise w = promise(DOMWrapperWorld::mainWorld());
244 EXPECT_FALSE(v.isEmpty());
247 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_DoesNotImpedeGarbageCollection)
249 ScriptValue holderWrapper = wrap(mainWorld(), holder()->toGarbageCollectedScriptWrappable());
251 Persistent<GCObservation> observation;
253 ScriptState::Scope scope(mainScriptState());
254 observation = GCObservation::create(promise(DOMWrapperWorld::mainWorld()).v8Value());
258 EXPECT_FALSE(observation->wasCollected());
260 holderWrapper.clear();
262 EXPECT_TRUE(observation->wasCollected());
264 EXPECT_EQ(Property::Pending, property()->state());
267 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Resolve_ResolvesScriptPromise)
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;
276 ScriptState::Scope scope(mainScriptState());
277 promise.then(stub(currentScriptState(), actual, nResolveCalls), notReached(currentScriptState()));
281 ScriptState::Scope scope(otherScriptState());
282 otherPromise.then(stub(currentScriptState(), otherActual, nOtherResolveCalls), notReached(currentScriptState()));
285 EXPECT_NE(promise, otherPromise);
287 GarbageCollectedScriptWrappable* value = new GarbageCollectedScriptWrappable("value");
288 property()->resolve(value);
289 EXPECT_EQ(Property::Resolved, property()->state());
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);
299 TEST_F(ScriptPromisePropertyGarbageCollectedTest, ResolveAndGetPromiseOnOtherWorld)
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;
308 ScriptState::Scope scope(mainScriptState());
309 promise.then(stub(currentScriptState(), actual, nResolveCalls), notReached(currentScriptState()));
312 EXPECT_NE(promise, otherPromise);
313 GarbageCollectedScriptWrappable* value = new GarbageCollectedScriptWrappable("value");
314 property()->resolve(value);
315 EXPECT_EQ(Property::Resolved, property()->state());
317 isolate()->RunMicrotasks();
318 EXPECT_EQ(1u, nResolveCalls);
319 EXPECT_EQ(0u, nOtherResolveCalls);
322 ScriptState::Scope scope(otherScriptState());
323 otherPromise.then(stub(currentScriptState(), otherActual, nOtherResolveCalls), notReached(currentScriptState()));
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);
334 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Reject_RejectsScriptPromise)
336 GarbageCollectedScriptWrappable* reason = new GarbageCollectedScriptWrappable("reason");
337 property()->reject(reason);
338 EXPECT_EQ(Property::Rejected, property()->state());
340 ScriptValue actual, otherActual;
341 size_t nRejectCalls = 0;
342 size_t nOtherRejectCalls = 0;
344 ScriptState::Scope scope(mainScriptState());
345 property()->promise(DOMWrapperWorld::mainWorld()).then(notReached(currentScriptState()), stub(currentScriptState(), actual, nRejectCalls));
349 ScriptState::Scope scope(otherScriptState());
350 property()->promise(otherWorld()).then(notReached(currentScriptState()), stub(currentScriptState(), otherActual, nOtherRejectCalls));
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);
361 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Promise_DeadContext)
363 Property* property = this->property();
364 property->resolve(new GarbageCollectedScriptWrappable("value"));
365 EXPECT_EQ(Property::Resolved, property->state());
369 EXPECT_TRUE(property->promise(DOMWrapperWorld::mainWorld()).isEmpty());
372 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Resolve_DeadContext)
374 Property* property = this->property();
377 ScriptState::Scope scope(mainScriptState());
378 property->promise(DOMWrapperWorld::mainWorld()).then(notReached(currentScriptState()), notReached(currentScriptState()));
382 EXPECT_TRUE(!property->executionContext() || property->executionContext()->activeDOMObjectsAreStopped());
384 property->resolve(new GarbageCollectedScriptWrappable("value"));
385 EXPECT_EQ(Property::Pending, property->state());
387 v8::Isolate::GetCurrent()->RunMicrotasks();
390 TEST_F(ScriptPromisePropertyGarbageCollectedTest, Reset)
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;
400 ScriptState::Scope scope(mainScriptState());
401 property()->resolve(oldValue);
402 oldPromise = property()->promise(mainWorld());
403 oldPromise.then(stub(currentScriptState(), oldActual, nOldResolveCalls), notReached(currentScriptState()));
409 ScriptState::Scope scope(mainScriptState());
410 newPromise = property()->promise(mainWorld());
411 newPromise.then(notReached(currentScriptState()), stub(currentScriptState(), newActual, nNewRejectCalls));
412 property()->reject(newValue);
415 EXPECT_EQ(0u, nOldResolveCalls);
416 EXPECT_EQ(0u, nNewRejectCalls);
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);
427 // Tests that ScriptPromiseProperty works with a ref-counted holder.
428 class ScriptPromisePropertyRefCountedTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
430 typedef RefCountedHolder::Property Property;
432 ScriptPromisePropertyRefCountedTest()
433 : m_holder(RefCountedHolder::create(&document()))
437 RefCountedHolder* holder() { return m_holder.get(); }
438 Property* property() { return m_holder->property(); }
441 RefPtr<RefCountedHolder> m_holder;
444 TEST_F(ScriptPromisePropertyRefCountedTest, Resolve)
447 size_t nResolveCalls = 0;
450 ScriptState::Scope scope(mainScriptState());
451 property()->promise(DOMWrapperWorld::mainWorld()).then(stub(currentScriptState(), actual, nResolveCalls), notReached(currentScriptState()));
454 RefPtr<RefCountedScriptWrappable> value = RefCountedScriptWrappable::create("value");
455 property()->resolve(value.get());
456 EXPECT_EQ(Property::Resolved, property()->state());
458 isolate()->RunMicrotasks();
459 EXPECT_EQ(1u, nResolveCalls);
460 EXPECT_EQ(wrap(mainWorld(), value), actual);
463 TEST_F(ScriptPromisePropertyRefCountedTest, Reject)
466 size_t nRejectCalls = 0;
469 ScriptState::Scope scope(mainScriptState());
470 property()->promise(DOMWrapperWorld::mainWorld()).then(notReached(currentScriptState()), stub(currentScriptState(), actual, nRejectCalls));
473 RefPtr<RefCountedScriptWrappable> reason = RefCountedScriptWrappable::create("reason");
474 property()->reject(reason);
475 EXPECT_EQ(Property::Rejected, property()->state());
477 isolate()->RunMicrotasks();
478 EXPECT_EQ(1u, nRejectCalls);
479 EXPECT_EQ(wrap(mainWorld(), reason), actual);
482 TEST_F(ScriptPromisePropertyRefCountedTest, ReSolveAndReset)
484 RefPtr<RefCountedScriptWrappable> value = RefCountedScriptWrappable::create("value");
487 ScriptState::Scope scope(mainScriptState());
488 property()->resolve(value);
491 EXPECT_EQ(2, value->refCount());
493 EXPECT_EQ(1, value->refCount());
496 TEST_F(ScriptPromisePropertyRefCountedTest, RejectAndReset)
498 RefPtr<RefCountedScriptWrappable> value = RefCountedScriptWrappable::create("value");
501 ScriptState::Scope scope(mainScriptState());
502 property()->reject(value.get());
505 EXPECT_EQ(2, value->refCount());
507 EXPECT_EQ(1, value->refCount());
510 // Tests that ScriptPromiseProperty works with a non ScriptWrappable resolution
512 class ScriptPromisePropertyNonScriptWrappableResolutionTargetTest : public ScriptPromisePropertyTestBase, public ::testing::Test {
514 template <typename T>
515 void test(const T& value, const char* expected, const char* file, size_t line)
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;
523 ScriptState::Scope scope(mainScriptState());
524 property->promise(DOMWrapperWorld::mainWorld()).then(stub(currentScriptState(), actualValue, nResolveCalls), notReached(currentScriptState()));
526 property->resolve(value);
527 isolate()->RunMicrotasks();
529 ScriptState::Scope scope(mainScriptState());
530 actual = toCoreString(actualValue.v8Value()->ToString());
532 if (expected != actual) {
533 ADD_FAILURE_AT(file, line) << "toV8Value returns an incorrect value.\n Actual: " << actual.utf8().data() << "\nExpected: " << expected;
539 TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithUndefined)
541 test(V8UndefinedType(), "undefined", __FILE__, __LINE__);
544 TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithString)
546 test(String("hello"), "hello", __FILE__, __LINE__);
549 TEST_F(ScriptPromisePropertyNonScriptWrappableResolutionTargetTest, ResolveWithInteger)
551 test<int>(-1, "-1", __FILE__, __LINE__);