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