// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include #include "base/run_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/bindings/core/v8/script_function.h" #include "third_party/blink/renderer/bindings/core/v8/script_value.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/testing/dummy_page_holder.h" #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h" #include "third_party/blink/renderer/platform/scheduler/public/thread.h" #include "v8/include/v8.h" namespace blink { namespace { class TestHelperFunction : public ScriptFunction { public: static v8::Local CreateFunction(ScriptState* script_state, String* value) { TestHelperFunction* self = MakeGarbageCollected(script_state, value); return self->BindToV8Function(); } TestHelperFunction(ScriptState* script_state, String* value) : ScriptFunction(script_state), value_(value) {} private: ScriptValue Call(ScriptValue value) override { DCHECK(!value.IsEmpty()); *value_ = ToCoreString(value.V8Value() ->ToString(GetScriptState()->GetContext()) .ToLocalChecked()); return value; } String* value_; }; class ScriptPromiseResolverTest : public testing::Test { public: ScriptPromiseResolverTest() : page_holder_(std::make_unique()) {} ~ScriptPromiseResolverTest() override { // Execute all pending microtasks v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); } std::unique_ptr page_holder_; ScriptState* GetScriptState() const { return ToScriptStateForMainWorld(&page_holder_->GetFrame()); } ExecutionContext* GetExecutionContext() const { return page_holder_->GetFrame().DomWindow(); } v8::Isolate* GetIsolate() const { return GetScriptState()->GetIsolate(); } }; TEST_F(ScriptPromiseResolverTest, construct) { ASSERT_FALSE(GetExecutionContext()->IsContextDestroyed()); ScriptState::Scope scope(GetScriptState()); MakeGarbageCollected(GetScriptState()); } TEST_F(ScriptPromiseResolverTest, resolve) { ScriptPromiseResolver* resolver = nullptr; ScriptPromise promise; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); promise = resolver->Promise(); } String on_fulfilled, on_rejected; ASSERT_FALSE(promise.IsEmpty()); { ScriptState::Scope scope(GetScriptState()); promise.Then( TestHelperFunction::CreateFunction(GetScriptState(), &on_fulfilled), TestHelperFunction::CreateFunction(GetScriptState(), &on_rejected)); } EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); resolver->Resolve("hello"); { ScriptState::Scope scope(GetScriptState()); EXPECT_TRUE(resolver->Promise().IsEmpty()); } EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ("hello", on_fulfilled); EXPECT_EQ(String(), on_rejected); resolver->Resolve("bye"); resolver->Reject("bye"); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ("hello", on_fulfilled); EXPECT_EQ(String(), on_rejected); } TEST_F(ScriptPromiseResolverTest, reject) { ScriptPromiseResolver* resolver = nullptr; ScriptPromise promise; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); promise = resolver->Promise(); } String on_fulfilled, on_rejected; ASSERT_FALSE(promise.IsEmpty()); { ScriptState::Scope scope(GetScriptState()); promise.Then( TestHelperFunction::CreateFunction(GetScriptState(), &on_fulfilled), TestHelperFunction::CreateFunction(GetScriptState(), &on_rejected)); } EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); resolver->Reject("hello"); { ScriptState::Scope scope(GetScriptState()); EXPECT_TRUE(resolver->Promise().IsEmpty()); } EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ("hello", on_rejected); resolver->Resolve("bye"); resolver->Reject("bye"); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ("hello", on_rejected); } TEST_F(ScriptPromiseResolverTest, stop) { ScriptPromiseResolver* resolver = nullptr; ScriptPromise promise; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); promise = resolver->Promise(); } String on_fulfilled, on_rejected; ASSERT_FALSE(promise.IsEmpty()); { ScriptState::Scope scope(GetScriptState()); promise.Then( TestHelperFunction::CreateFunction(GetScriptState(), &on_fulfilled), TestHelperFunction::CreateFunction(GetScriptState(), &on_rejected)); } GetExecutionContext()->NotifyContextDestroyed(); { ScriptState::Scope scope(GetScriptState()); EXPECT_TRUE(resolver->Promise().IsEmpty()); } resolver->Resolve("hello"); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ(String(), on_rejected); } class ScriptPromiseResolverKeepAlive : public ScriptPromiseResolver { public: explicit ScriptPromiseResolverKeepAlive(ScriptState* script_state) : ScriptPromiseResolver(script_state) {} ~ScriptPromiseResolverKeepAlive() override { destructor_calls_++; } static void Reset() { destructor_calls_ = 0; } static bool IsAlive() { return !destructor_calls_; } static int destructor_calls_; }; int ScriptPromiseResolverKeepAlive::destructor_calls_ = 0; TEST_F(ScriptPromiseResolverTest, keepAliveUntilResolved) { ScriptPromiseResolverKeepAlive::Reset(); ScriptPromiseResolver* resolver = nullptr; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); } resolver->KeepAliveWhilePending(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); ASSERT_TRUE(ScriptPromiseResolverKeepAlive::IsAlive()); resolver->Resolve("hello"); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_FALSE(ScriptPromiseResolverKeepAlive::IsAlive()); } TEST_F(ScriptPromiseResolverTest, keepAliveUntilRejected) { ScriptPromiseResolverKeepAlive::Reset(); ScriptPromiseResolver* resolver = nullptr; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); } resolver->KeepAliveWhilePending(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); ASSERT_TRUE(ScriptPromiseResolverKeepAlive::IsAlive()); resolver->Reject("hello"); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_FALSE(ScriptPromiseResolverKeepAlive::IsAlive()); } TEST_F(ScriptPromiseResolverTest, keepAliveWhileScriptForbidden) { ScriptPromiseResolverKeepAlive::Reset(); ScriptPromiseResolver* resolver = nullptr; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); } { ScriptForbiddenScope forbidden; resolver->Resolve("hello"); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_TRUE(ScriptPromiseResolverKeepAlive::IsAlive()); } base::RunLoop().RunUntilIdle(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_FALSE(ScriptPromiseResolverKeepAlive::IsAlive()); } TEST_F(ScriptPromiseResolverTest, keepAliveUntilStopped) { ScriptPromiseResolverKeepAlive::Reset(); ScriptPromiseResolver* resolver = nullptr; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); } resolver->KeepAliveWhilePending(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_TRUE(ScriptPromiseResolverKeepAlive::IsAlive()); GetExecutionContext()->NotifyContextDestroyed(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_FALSE(ScriptPromiseResolverKeepAlive::IsAlive()); } TEST_F(ScriptPromiseResolverTest, suspend) { ScriptPromiseResolverKeepAlive::Reset(); ScriptPromiseResolver* resolver = nullptr; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); } resolver->KeepAliveWhilePending(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); ASSERT_TRUE(ScriptPromiseResolverKeepAlive::IsAlive()); page_holder_->GetPage().SetPaused(true); resolver->Resolve("hello"); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_TRUE(ScriptPromiseResolverKeepAlive::IsAlive()); GetExecutionContext()->NotifyContextDestroyed(); ThreadState::Current()->CollectAllGarbageForTesting( BlinkGC::kNoHeapPointersOnStack); EXPECT_FALSE(ScriptPromiseResolverKeepAlive::IsAlive()); } TEST_F(ScriptPromiseResolverTest, resolveVoid) { ScriptPromiseResolver* resolver = nullptr; ScriptPromise promise; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); promise = resolver->Promise(); } String on_fulfilled, on_rejected; ASSERT_FALSE(promise.IsEmpty()); { ScriptState::Scope scope(GetScriptState()); promise.Then( TestHelperFunction::CreateFunction(GetScriptState(), &on_fulfilled), TestHelperFunction::CreateFunction(GetScriptState(), &on_rejected)); } resolver->Resolve(); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ("undefined", on_fulfilled); EXPECT_EQ(String(), on_rejected); } TEST_F(ScriptPromiseResolverTest, rejectVoid) { ScriptPromiseResolver* resolver = nullptr; ScriptPromise promise; { ScriptState::Scope scope(GetScriptState()); resolver = MakeGarbageCollected(GetScriptState()); promise = resolver->Promise(); } String on_fulfilled, on_rejected; ASSERT_FALSE(promise.IsEmpty()); { ScriptState::Scope scope(GetScriptState()); promise.Then( TestHelperFunction::CreateFunction(GetScriptState(), &on_fulfilled), TestHelperFunction::CreateFunction(GetScriptState(), &on_rejected)); } resolver->Reject(); v8::MicrotasksScope::PerformCheckpoint(GetIsolate()); EXPECT_EQ(String(), on_fulfilled); EXPECT_EQ("undefined", on_rejected); } } // namespace } // namespace blink