2018-11-27 08:32:53 +00:00
|
|
|
// Copyright 2018 the V8 project authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
|
2019-05-22 07:55:37 +00:00
|
|
|
#include "src/execution/microtask-queue.h"
|
2018-11-27 08:32:53 +00:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <functional>
|
|
|
|
#include <memory>
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
#include "src/heap/factory.h"
|
2018-12-17 12:27:53 +00:00
|
|
|
#include "src/objects/foreign.h"
|
2019-02-26 14:31:43 +00:00
|
|
|
#include "src/objects/js-array-inl.h"
|
|
|
|
#include "src/objects/js-objects-inl.h"
|
2019-05-23 08:51:46 +00:00
|
|
|
#include "src/objects/objects-inl.h"
|
2019-02-26 04:45:27 +00:00
|
|
|
#include "src/objects/promise-inl.h"
|
2019-05-24 13:51:59 +00:00
|
|
|
#include "src/objects/visitors.h"
|
2018-11-27 08:32:53 +00:00
|
|
|
#include "test/unittests/test-utils.h"
|
|
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
|
|
|
|
namespace v8 {
|
|
|
|
namespace internal {
|
|
|
|
|
|
|
|
using Closure = std::function<void()>;
|
|
|
|
|
|
|
|
void RunStdFunction(void* data) {
|
|
|
|
std::unique_ptr<Closure> f(static_cast<Closure*>(data));
|
|
|
|
(*f)();
|
|
|
|
}
|
|
|
|
|
2019-02-26 14:31:43 +00:00
|
|
|
template <typename TMixin>
|
|
|
|
class WithFinalizationGroupMixin : public TMixin {
|
|
|
|
public:
|
2019-03-08 12:19:31 +00:00
|
|
|
WithFinalizationGroupMixin() = default;
|
|
|
|
~WithFinalizationGroupMixin() override = default;
|
|
|
|
|
|
|
|
static void SetUpTestCase() {
|
|
|
|
CHECK_NULL(save_flags_);
|
|
|
|
save_flags_ = new SaveFlags();
|
2019-02-26 14:31:43 +00:00
|
|
|
FLAG_harmony_weak_refs = true;
|
|
|
|
FLAG_expose_gc = true;
|
2019-04-04 06:16:19 +00:00
|
|
|
FLAG_allow_natives_syntax = true;
|
2019-03-08 12:19:31 +00:00
|
|
|
TMixin::SetUpTestCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void TearDownTestCase() {
|
|
|
|
TMixin::TearDownTestCase();
|
|
|
|
CHECK_NOT_NULL(save_flags_);
|
|
|
|
delete save_flags_;
|
|
|
|
save_flags_ = nullptr;
|
2019-02-26 14:31:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2019-03-08 12:19:31 +00:00
|
|
|
static SaveFlags* save_flags_;
|
2019-02-26 14:31:43 +00:00
|
|
|
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(WithFinalizationGroupMixin);
|
|
|
|
};
|
|
|
|
|
2019-03-08 12:19:31 +00:00
|
|
|
template <typename TMixin>
|
|
|
|
SaveFlags* WithFinalizationGroupMixin<TMixin>::save_flags_ = nullptr;
|
|
|
|
|
2019-02-26 14:31:43 +00:00
|
|
|
using TestWithNativeContextAndFinalizationGroup = //
|
|
|
|
WithInternalIsolateMixin< //
|
|
|
|
WithContextMixin< //
|
|
|
|
WithFinalizationGroupMixin< //
|
|
|
|
WithIsolateScopeMixin< //
|
|
|
|
WithSharedIsolateMixin< //
|
|
|
|
::testing::Test>>>>>;
|
|
|
|
|
|
|
|
class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationGroup {
|
2018-11-27 08:32:53 +00:00
|
|
|
public:
|
|
|
|
template <typename F>
|
|
|
|
Handle<Microtask> NewMicrotask(F&& f) {
|
|
|
|
Handle<Foreign> runner =
|
|
|
|
factory()->NewForeign(reinterpret_cast<Address>(&RunStdFunction));
|
|
|
|
Handle<Foreign> data = factory()->NewForeign(
|
|
|
|
reinterpret_cast<Address>(new Closure(std::forward<F>(f))));
|
|
|
|
return factory()->NewCallbackTask(runner, data);
|
|
|
|
}
|
|
|
|
|
2019-01-17 11:31:28 +00:00
|
|
|
void SetUp() override {
|
|
|
|
microtask_queue_ = MicrotaskQueue::New(isolate());
|
|
|
|
native_context()->set_microtask_queue(microtask_queue());
|
|
|
|
}
|
|
|
|
|
2018-11-27 08:32:53 +00:00
|
|
|
void TearDown() override {
|
2019-01-17 11:31:28 +00:00
|
|
|
if (microtask_queue()) {
|
|
|
|
microtask_queue()->RunMicrotasks(isolate());
|
|
|
|
context()->DetachGlobal();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MicrotaskQueue* microtask_queue() const { return microtask_queue_.get(); }
|
|
|
|
|
|
|
|
void ClearTestMicrotaskQueue() {
|
|
|
|
context()->DetachGlobal();
|
|
|
|
microtask_queue_ = nullptr;
|
2018-11-27 08:32:53 +00:00
|
|
|
}
|
2019-01-17 11:31:28 +00:00
|
|
|
|
2019-02-26 14:31:43 +00:00
|
|
|
template <size_t N>
|
|
|
|
Handle<Name> NameFromChars(const char (&chars)[N]) {
|
|
|
|
return isolate()->factory()->NewStringFromStaticChars(chars);
|
|
|
|
}
|
|
|
|
|
2019-01-17 11:31:28 +00:00
|
|
|
private:
|
|
|
|
std::unique_ptr<MicrotaskQueue> microtask_queue_;
|
2018-11-27 08:32:53 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class RecordingVisitor : public RootVisitor {
|
|
|
|
public:
|
|
|
|
RecordingVisitor() = default;
|
|
|
|
~RecordingVisitor() override = default;
|
|
|
|
|
2018-12-06 17:53:25 +00:00
|
|
|
void VisitRootPointers(Root root, const char* description,
|
|
|
|
FullObjectSlot start, FullObjectSlot end) override {
|
|
|
|
for (FullObjectSlot current = start; current != end; ++current) {
|
2018-11-27 08:32:53 +00:00
|
|
|
visited_.push_back(*current);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-25 00:19:47 +00:00
|
|
|
const std::vector<Object>& visited() const { return visited_; }
|
2018-11-27 08:32:53 +00:00
|
|
|
|
|
|
|
private:
|
2018-12-25 00:19:47 +00:00
|
|
|
std::vector<Object> visited_;
|
2018-11-27 08:32:53 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// Sanity check. Ensure a microtask is stored in a queue and run.
|
|
|
|
TEST_F(MicrotaskQueueTest, EnqueueAndRun) {
|
|
|
|
bool ran = false;
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_EQ(0, microtask_queue()->capacity());
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
microtask_queue()->EnqueueMicrotask(*NewMicrotask([&ran] {
|
2018-11-27 08:32:53 +00:00
|
|
|
EXPECT_FALSE(ran);
|
|
|
|
ran = true;
|
|
|
|
}));
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue()->capacity());
|
|
|
|
EXPECT_EQ(1, microtask_queue()->size());
|
2019-01-21 14:57:10 +00:00
|
|
|
EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
|
2018-11-27 08:32:53 +00:00
|
|
|
EXPECT_TRUE(ran);
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
2018-11-27 08:32:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check for a buffer growth.
|
|
|
|
TEST_F(MicrotaskQueueTest, BufferGrowth) {
|
|
|
|
int count = 0;
|
|
|
|
|
|
|
|
// Enqueue and flush the queue first to have non-zero |start_|.
|
2019-01-17 11:31:28 +00:00
|
|
|
microtask_queue()->EnqueueMicrotask(
|
2018-11-27 08:32:53 +00:00
|
|
|
*NewMicrotask([&count] { EXPECT_EQ(0, count++); }));
|
2019-01-21 14:57:10 +00:00
|
|
|
EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
|
2018-11-27 08:32:53 +00:00
|
|
|
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_LT(0, microtask_queue()->capacity());
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
EXPECT_EQ(1, microtask_queue()->start());
|
2018-11-27 08:32:53 +00:00
|
|
|
|
|
|
|
// Fill the queue with Microtasks.
|
|
|
|
for (int i = 1; i <= MicrotaskQueue::kMinimumCapacity; ++i) {
|
2019-01-17 11:31:28 +00:00
|
|
|
microtask_queue()->EnqueueMicrotask(
|
2018-11-27 08:32:53 +00:00
|
|
|
*NewMicrotask([&count, i] { EXPECT_EQ(i, count++); }));
|
|
|
|
}
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue()->capacity());
|
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue()->size());
|
2018-11-27 08:32:53 +00:00
|
|
|
|
|
|
|
// Add another to grow the ring buffer.
|
2019-01-17 11:31:28 +00:00
|
|
|
microtask_queue()->EnqueueMicrotask(*NewMicrotask(
|
2018-11-27 08:32:53 +00:00
|
|
|
[&] { EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1, count++); }));
|
|
|
|
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_LT(MicrotaskQueue::kMinimumCapacity, microtask_queue()->capacity());
|
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1, microtask_queue()->size());
|
2018-11-27 08:32:53 +00:00
|
|
|
|
|
|
|
// Run all pending Microtasks to ensure they run in the proper order.
|
2019-01-21 14:57:10 +00:00
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1,
|
|
|
|
microtask_queue()->RunMicrotasks(isolate()));
|
2018-11-27 08:32:53 +00:00
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 2, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
// MicrotaskQueue instances form a doubly linked list.
|
|
|
|
TEST_F(MicrotaskQueueTest, InstanceChain) {
|
2019-01-17 11:31:28 +00:00
|
|
|
ClearTestMicrotaskQueue();
|
|
|
|
|
2018-11-27 08:32:53 +00:00
|
|
|
MicrotaskQueue* default_mtq = isolate()->default_microtask_queue();
|
|
|
|
ASSERT_TRUE(default_mtq);
|
|
|
|
EXPECT_EQ(default_mtq, default_mtq->next());
|
|
|
|
EXPECT_EQ(default_mtq, default_mtq->prev());
|
|
|
|
|
|
|
|
// Create two instances, and check their connection.
|
|
|
|
// The list contains all instances in the creation order, and the next of the
|
|
|
|
// last instance is the first instance:
|
|
|
|
// default_mtq -> mtq1 -> mtq2 -> default_mtq.
|
|
|
|
std::unique_ptr<MicrotaskQueue> mtq1 = MicrotaskQueue::New(isolate());
|
|
|
|
std::unique_ptr<MicrotaskQueue> mtq2 = MicrotaskQueue::New(isolate());
|
|
|
|
EXPECT_EQ(default_mtq->next(), mtq1.get());
|
|
|
|
EXPECT_EQ(mtq1->next(), mtq2.get());
|
|
|
|
EXPECT_EQ(mtq2->next(), default_mtq);
|
|
|
|
EXPECT_EQ(default_mtq, mtq1->prev());
|
|
|
|
EXPECT_EQ(mtq1.get(), mtq2->prev());
|
|
|
|
EXPECT_EQ(mtq2.get(), default_mtq->prev());
|
|
|
|
|
|
|
|
// Deleted item should be also removed from the list.
|
|
|
|
mtq1 = nullptr;
|
|
|
|
EXPECT_EQ(default_mtq->next(), mtq2.get());
|
|
|
|
EXPECT_EQ(mtq2->next(), default_mtq);
|
|
|
|
EXPECT_EQ(default_mtq, mtq2->prev());
|
|
|
|
EXPECT_EQ(mtq2.get(), default_mtq->prev());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pending Microtasks in MicrotaskQueues are strong roots. Ensure they are
|
|
|
|
// visited exactly once.
|
|
|
|
TEST_F(MicrotaskQueueTest, VisitRoot) {
|
|
|
|
// Ensure that the ring buffer has separate in-use region.
|
|
|
|
for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
|
2019-01-17 11:31:28 +00:00
|
|
|
microtask_queue()->EnqueueMicrotask(*NewMicrotask([] {}));
|
2018-11-27 08:32:53 +00:00
|
|
|
}
|
2019-01-21 14:57:10 +00:00
|
|
|
EXPECT_EQ(MicrotaskQueue::kMinimumCapacity / 2 + 1,
|
|
|
|
microtask_queue()->RunMicrotasks(isolate()));
|
2018-11-27 08:32:53 +00:00
|
|
|
|
2018-12-25 00:19:47 +00:00
|
|
|
std::vector<Object> expected;
|
2018-11-27 08:32:53 +00:00
|
|
|
for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
|
|
|
|
Handle<Microtask> microtask = NewMicrotask([] {});
|
|
|
|
expected.push_back(*microtask);
|
2019-01-17 11:31:28 +00:00
|
|
|
microtask_queue()->EnqueueMicrotask(*microtask);
|
2018-11-27 08:32:53 +00:00
|
|
|
}
|
2019-01-17 11:31:28 +00:00
|
|
|
EXPECT_GT(microtask_queue()->start() + microtask_queue()->size(),
|
|
|
|
microtask_queue()->capacity());
|
2018-11-27 08:32:53 +00:00
|
|
|
|
|
|
|
RecordingVisitor visitor;
|
2019-01-17 11:31:28 +00:00
|
|
|
microtask_queue()->IterateMicrotasks(&visitor);
|
2018-11-27 08:32:53 +00:00
|
|
|
|
2018-12-25 00:19:47 +00:00
|
|
|
std::vector<Object> actual = visitor.visited();
|
2018-11-27 08:32:53 +00:00
|
|
|
std::sort(expected.begin(), expected.end());
|
|
|
|
std::sort(actual.begin(), actual.end());
|
|
|
|
EXPECT_EQ(expected, actual);
|
|
|
|
}
|
|
|
|
|
2019-02-26 04:45:27 +00:00
|
|
|
TEST_F(MicrotaskQueueTest, PromiseHandlerContext) {
|
|
|
|
Local<v8::Context> v8_context2 = v8::Context::New(v8_isolate());
|
|
|
|
Local<v8::Context> v8_context3 = v8::Context::New(v8_isolate());
|
|
|
|
Local<v8::Context> v8_context4 = v8::Context::New(v8_isolate());
|
|
|
|
Handle<Context> context2 = Utils::OpenHandle(*v8_context2, isolate());
|
|
|
|
Handle<Context> context3 = Utils::OpenHandle(*v8_context3, isolate());
|
|
|
|
Handle<Context> context4 = Utils::OpenHandle(*v8_context3, isolate());
|
|
|
|
context2->native_context().set_microtask_queue(microtask_queue());
|
|
|
|
context3->native_context().set_microtask_queue(microtask_queue());
|
|
|
|
context4->native_context().set_microtask_queue(microtask_queue());
|
|
|
|
|
|
|
|
Handle<JSFunction> handler;
|
|
|
|
Handle<JSProxy> proxy;
|
|
|
|
Handle<JSProxy> revoked_proxy;
|
|
|
|
Handle<JSBoundFunction> bound;
|
|
|
|
|
|
|
|
// Create a JSFunction on |context2|
|
|
|
|
{
|
|
|
|
v8::Context::Scope scope(v8_context2);
|
|
|
|
handler = RunJS<JSFunction>("()=>{}");
|
|
|
|
EXPECT_EQ(*context2,
|
|
|
|
*JSReceiver::GetContextForMicrotask(handler).ToHandleChecked());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a JSProxy on |context3|.
|
|
|
|
{
|
|
|
|
v8::Context::Scope scope(v8_context3);
|
|
|
|
ASSERT_TRUE(
|
|
|
|
v8_context3->Global()
|
|
|
|
->Set(v8_context3, NewString("handler"), Utils::ToLocal(handler))
|
|
|
|
.FromJust());
|
|
|
|
proxy = RunJS<JSProxy>("new Proxy(handler, {})");
|
|
|
|
revoked_proxy = RunJS<JSProxy>(
|
|
|
|
"let {proxy, revoke} = Proxy.revocable(handler, {});"
|
|
|
|
"revoke();"
|
|
|
|
"proxy");
|
|
|
|
EXPECT_EQ(*context2,
|
|
|
|
*JSReceiver::GetContextForMicrotask(proxy).ToHandleChecked());
|
|
|
|
EXPECT_TRUE(JSReceiver::GetContextForMicrotask(revoked_proxy).is_null());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a JSBoundFunction on |context4|.
|
|
|
|
// Note that its CreationContext and ContextForTaskCancellation is |context2|.
|
|
|
|
{
|
|
|
|
v8::Context::Scope scope(v8_context4);
|
|
|
|
ASSERT_TRUE(
|
|
|
|
v8_context4->Global()
|
|
|
|
->Set(v8_context4, NewString("handler"), Utils::ToLocal(handler))
|
|
|
|
.FromJust());
|
|
|
|
bound = RunJS<JSBoundFunction>("handler.bind()");
|
|
|
|
EXPECT_EQ(*context2,
|
|
|
|
*JSReceiver::GetContextForMicrotask(bound).ToHandleChecked());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Give the objects to the main context.
|
|
|
|
SetGlobalProperty("handler", Utils::ToLocal(handler));
|
|
|
|
SetGlobalProperty("proxy", Utils::ToLocal(proxy));
|
|
|
|
SetGlobalProperty("revoked_proxy", Utils::ToLocal(revoked_proxy));
|
|
|
|
SetGlobalProperty("bound", Utils::ToLocal(Handle<JSReceiver>::cast(bound)));
|
|
|
|
RunJS(
|
|
|
|
"Promise.resolve().then(handler);"
|
|
|
|
"Promise.reject().catch(proxy);"
|
|
|
|
"Promise.resolve().then(revoked_proxy);"
|
|
|
|
"Promise.resolve().then(bound);");
|
|
|
|
|
|
|
|
ASSERT_EQ(4, microtask_queue()->size());
|
|
|
|
Handle<Microtask> microtask1(microtask_queue()->get(0), isolate());
|
|
|
|
ASSERT_TRUE(microtask1->IsPromiseFulfillReactionJobTask());
|
|
|
|
EXPECT_EQ(*context2,
|
|
|
|
Handle<PromiseFulfillReactionJobTask>::cast(microtask1)->context());
|
|
|
|
|
|
|
|
Handle<Microtask> microtask2(microtask_queue()->get(1), isolate());
|
|
|
|
ASSERT_TRUE(microtask2->IsPromiseRejectReactionJobTask());
|
|
|
|
EXPECT_EQ(*context2,
|
|
|
|
Handle<PromiseRejectReactionJobTask>::cast(microtask2)->context());
|
|
|
|
|
|
|
|
Handle<Microtask> microtask3(microtask_queue()->get(2), isolate());
|
|
|
|
ASSERT_TRUE(microtask3->IsPromiseFulfillReactionJobTask());
|
|
|
|
// |microtask3| corresponds to a PromiseReaction for |revoked_proxy|.
|
|
|
|
// As |revoked_proxy| doesn't have a context, the current context should be
|
|
|
|
// used as the fallback context.
|
|
|
|
EXPECT_EQ(*native_context(),
|
|
|
|
Handle<PromiseFulfillReactionJobTask>::cast(microtask3)->context());
|
|
|
|
|
|
|
|
Handle<Microtask> microtask4(microtask_queue()->get(3), isolate());
|
|
|
|
ASSERT_TRUE(microtask4->IsPromiseFulfillReactionJobTask());
|
|
|
|
EXPECT_EQ(*context2,
|
|
|
|
Handle<PromiseFulfillReactionJobTask>::cast(microtask4)->context());
|
|
|
|
|
|
|
|
v8_context4->DetachGlobal();
|
|
|
|
v8_context3->DetachGlobal();
|
|
|
|
v8_context2->DetachGlobal();
|
|
|
|
}
|
|
|
|
|
2019-02-26 14:31:43 +00:00
|
|
|
TEST_F(MicrotaskQueueTest, DetachGlobal_Enqueue) {
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
|
|
|
|
// Detach MicrotaskQueue from the current context.
|
|
|
|
context()->DetachGlobal();
|
|
|
|
|
|
|
|
// No microtask should be enqueued after DetachGlobal call.
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
RunJS("Promise.resolve().then(()=>{})");
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(MicrotaskQueueTest, DetachGlobal_Run) {
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
|
|
|
|
// Enqueue microtasks to the current context.
|
|
|
|
Handle<JSArray> ran = RunJS<JSArray>(
|
|
|
|
"var ran = [false, false, false, false];"
|
|
|
|
"Promise.resolve().then(() => { ran[0] = true; });"
|
|
|
|
"Promise.reject().catch(() => { ran[1] = true; });"
|
|
|
|
"ran");
|
|
|
|
|
|
|
|
Handle<JSFunction> function =
|
|
|
|
RunJS<JSFunction>("(function() { ran[2] = true; })");
|
|
|
|
Handle<CallableTask> callable =
|
|
|
|
factory()->NewCallableTask(function, Utils::OpenHandle(*context()));
|
|
|
|
microtask_queue()->EnqueueMicrotask(*callable);
|
|
|
|
|
|
|
|
// The handler should not run at this point.
|
|
|
|
const int kNumExpectedTasks = 3;
|
|
|
|
for (int i = 0; i < kNumExpectedTasks; ++i) {
|
|
|
|
EXPECT_TRUE(
|
|
|
|
Object::GetElement(isolate(), ran, i).ToHandleChecked()->IsFalse());
|
|
|
|
}
|
|
|
|
EXPECT_EQ(kNumExpectedTasks, microtask_queue()->size());
|
|
|
|
|
|
|
|
// Detach MicrotaskQueue from the current context.
|
|
|
|
context()->DetachGlobal();
|
|
|
|
|
|
|
|
// RunMicrotasks processes pending Microtasks, but Microtasks that are
|
|
|
|
// associated to a detached context should be cancelled and should not take
|
|
|
|
// effect.
|
|
|
|
microtask_queue()->RunMicrotasks(isolate());
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
for (int i = 0; i < kNumExpectedTasks; ++i) {
|
|
|
|
EXPECT_TRUE(
|
|
|
|
Object::GetElement(isolate(), ran, i).ToHandleChecked()->IsFalse());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
void DummyPromiseHook(PromiseHookType type, Local<Promise> promise,
|
|
|
|
Local<Value> parent) {}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
TEST_F(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) {
|
|
|
|
// Use a PromiseHook to switch the implementation to ResolvePromise runtime,
|
|
|
|
// instead of ResolvePromise builtin.
|
|
|
|
v8_isolate()->SetPromiseHook(&DummyPromiseHook);
|
|
|
|
|
|
|
|
RunJS(
|
|
|
|
"var resolve;"
|
|
|
|
"var promise = new Promise(r => { resolve = r; });"
|
|
|
|
"promise.then(() => {});"
|
|
|
|
"resolve({});");
|
|
|
|
|
|
|
|
// A PromiseResolveThenableJobTask is pending in the MicrotaskQueue.
|
|
|
|
EXPECT_EQ(1, microtask_queue()->size());
|
|
|
|
|
|
|
|
// Detach MicrotaskQueue from the current context.
|
|
|
|
context()->DetachGlobal();
|
|
|
|
|
|
|
|
// RunMicrotasks processes the pending Microtask, but Microtasks that are
|
|
|
|
// associated to a detached context should be cancelled and should not take
|
|
|
|
// effect.
|
|
|
|
// As PromiseResolveThenableJobTask queues another task for resolution,
|
|
|
|
// the return value is 2 if it ran.
|
|
|
|
EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
|
|
|
|
EXPECT_EQ(0, microtask_queue()->size());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_F(MicrotaskQueueTest, DetachGlobal_HandlerContext) {
|
|
|
|
// EnqueueMicrotask should use the context associated to the handler instead
|
|
|
|
// of the current context. E.g.
|
|
|
|
// // At Context A.
|
|
|
|
// let resolved = Promise.resolve();
|
|
|
|
// // Call DetachGlobal on A, so that microtasks associated to A is
|
|
|
|
// // cancelled.
|
|
|
|
//
|
|
|
|
// // At Context B.
|
|
|
|
// let handler = () => {
|
|
|
|
// console.log("here");
|
|
|
|
// };
|
|
|
|
// // The microtask to run |handler| should be associated to B instead of A,
|
|
|
|
// // so that handler runs even |resolved| is on the detached context A.
|
|
|
|
// resolved.then(handler);
|
|
|
|
|
|
|
|
Handle<JSReceiver> results = isolate()->factory()->NewJSObjectWithNullProto();
|
|
|
|
|
|
|
|
// These belong to a stale Context.
|
|
|
|
Handle<JSPromise> stale_resolved_promise;
|
|
|
|
Handle<JSPromise> stale_rejected_promise;
|
|
|
|
Handle<JSReceiver> stale_handler;
|
|
|
|
|
|
|
|
Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
|
|
|
|
{
|
|
|
|
v8::Context::Scope scope(sub_context);
|
|
|
|
stale_resolved_promise = RunJS<JSPromise>("Promise.resolve()");
|
|
|
|
stale_rejected_promise = RunJS<JSPromise>("Promise.reject()");
|
|
|
|
stale_handler = RunJS<JSReceiver>(
|
|
|
|
"(results, label) => {"
|
|
|
|
" results[label] = true;"
|
|
|
|
"}");
|
|
|
|
}
|
|
|
|
// DetachGlobal() cancells all microtasks associated to the context.
|
|
|
|
sub_context->DetachGlobal();
|
|
|
|
sub_context.Clear();
|
|
|
|
|
|
|
|
SetGlobalProperty("results", Utils::ToLocal(results));
|
|
|
|
SetGlobalProperty(
|
|
|
|
"stale_resolved_promise",
|
|
|
|
Utils::ToLocal(Handle<JSReceiver>::cast(stale_resolved_promise)));
|
|
|
|
SetGlobalProperty(
|
|
|
|
"stale_rejected_promise",
|
|
|
|
Utils::ToLocal(Handle<JSReceiver>::cast(stale_rejected_promise)));
|
|
|
|
SetGlobalProperty("stale_handler", Utils::ToLocal(stale_handler));
|
|
|
|
|
|
|
|
// Set valid handlers to stale promises.
|
|
|
|
RunJS(
|
|
|
|
"stale_resolved_promise.then(() => {"
|
|
|
|
" results['stale_resolved_promise'] = true;"
|
|
|
|
"})");
|
|
|
|
RunJS(
|
|
|
|
"stale_rejected_promise.catch(() => {"
|
|
|
|
" results['stale_rejected_promise'] = true;"
|
|
|
|
"})");
|
|
|
|
microtask_queue()->RunMicrotasks(isolate());
|
|
|
|
EXPECT_TRUE(
|
|
|
|
JSReceiver::HasProperty(results, NameFromChars("stale_resolved_promise"))
|
|
|
|
.FromJust());
|
|
|
|
EXPECT_TRUE(
|
|
|
|
JSReceiver::HasProperty(results, NameFromChars("stale_rejected_promise"))
|
|
|
|
.FromJust());
|
|
|
|
|
|
|
|
// Set stale handlers to valid promises.
|
|
|
|
RunJS(
|
|
|
|
"Promise.resolve("
|
|
|
|
" stale_handler.bind(null, results, 'stale_handler_resolve'))");
|
|
|
|
RunJS(
|
|
|
|
"Promise.reject("
|
|
|
|
" stale_handler.bind(null, results, 'stale_handler_reject'))");
|
|
|
|
microtask_queue()->RunMicrotasks(isolate());
|
|
|
|
EXPECT_FALSE(
|
|
|
|
JSReceiver::HasProperty(results, NameFromChars("stale_handler_resolve"))
|
|
|
|
.FromJust());
|
|
|
|
EXPECT_FALSE(
|
|
|
|
JSReceiver::HasProperty(results, NameFromChars("stale_handler_reject"))
|
|
|
|
.FromJust());
|
|
|
|
}
|
|
|
|
|
Use non-primary promise handler as a source of fallback microtask context
A microtask requires a non-detached Context to trigger, and the Context
is usually pulled from the primary handler.
On an example below, |on_rejected| is primary, as the attached promise
is rejected and |on_rejected| will be called as the reaction.
Promise.reject().then(on_fulfilled, on_rejected);
If the primary handler is undefined or invalid, we used to use the
promise's context as the fallback. E.g. the primary handler is undefined
on the examlpe below, and the context of |promise| was used.
let promise = Promise.reject();
promise.then(on_fulfilled);
However, that causes a non-intuitive behavior around a detached
context:
let DeadPromise = iframe.contentWindow.Promise;
iframe.src = "http://example.com"; // navigate away.
// DeadPromise's Context is detached state now.
let p = DeadPromise.reject();
// |on_rejected| is called, as the context is pulled from |on_rejected|.
p.then(on_fulfilled, on_rejected);
// |on_rejected| was NOT called, as a microtask to settle |q| does not
// run due to the detached context.
let q = p.then(on_fulfilled);
q.catch(on_rejected);
After this CL, we use non-primary handler as a source of fallback context.
On the last example above, the Context is pulled from |on_fullfilled|,
so that |q| is settled using that context.
Bug: chromium:941271
Change-Id: Iff71acf7c3617f3493d100abcd2c5c36bd1bbfd1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1535916
Reviewed-by: Benedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Taiju Tsuiki <tzik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60499}
2019-03-27 08:23:50 +00:00
|
|
|
TEST_F(MicrotaskQueueTest, DetachGlobal_Chain) {
|
|
|
|
Handle<JSPromise> stale_rejected_promise;
|
|
|
|
|
|
|
|
Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
|
|
|
|
{
|
|
|
|
v8::Context::Scope scope(sub_context);
|
|
|
|
stale_rejected_promise = RunJS<JSPromise>("Promise.reject()");
|
|
|
|
}
|
|
|
|
sub_context->DetachGlobal();
|
|
|
|
sub_context.Clear();
|
|
|
|
|
|
|
|
SetGlobalProperty(
|
|
|
|
"stale_rejected_promise",
|
|
|
|
Utils::ToLocal(Handle<JSReceiver>::cast(stale_rejected_promise)));
|
|
|
|
Handle<JSArray> result = RunJS<JSArray>(
|
|
|
|
"let result = [false];"
|
|
|
|
"stale_rejected_promise"
|
|
|
|
" .then(() => {})"
|
|
|
|
" .catch(() => {"
|
|
|
|
" result[0] = true;"
|
|
|
|
" });"
|
|
|
|
"result");
|
|
|
|
microtask_queue()->RunMicrotasks(isolate());
|
|
|
|
EXPECT_TRUE(
|
|
|
|
Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsTrue());
|
|
|
|
}
|
|
|
|
|
2019-04-04 06:16:19 +00:00
|
|
|
TEST_F(MicrotaskQueueTest, DetachGlobal_InactiveHandler) {
|
|
|
|
Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
|
|
|
|
Utils::OpenHandle(*sub_context)
|
|
|
|
->native_context()
|
|
|
|
.set_microtask_queue(microtask_queue());
|
|
|
|
|
|
|
|
Handle<JSArray> result;
|
|
|
|
Handle<JSFunction> stale_handler;
|
|
|
|
Handle<JSPromise> stale_promise;
|
|
|
|
{
|
|
|
|
v8::Context::Scope scope(sub_context);
|
|
|
|
result = RunJS<JSArray>("var result = [false, false]; result");
|
|
|
|
stale_handler = RunJS<JSFunction>("() => { result[0] = true; }");
|
|
|
|
stale_promise = RunJS<JSPromise>(
|
|
|
|
"var stale_promise = new Promise(()=>{});"
|
|
|
|
"stale_promise");
|
|
|
|
RunJS("stale_promise.then(() => { result [1] = true; });");
|
|
|
|
}
|
|
|
|
sub_context->DetachGlobal();
|
|
|
|
sub_context.Clear();
|
|
|
|
|
|
|
|
// The context of |stale_handler| and |stale_promise| is detached at this
|
|
|
|
// point.
|
|
|
|
// Ensure that resolution handling for |stale_handler| is cancelled without
|
|
|
|
// crash. Also, the resolution of |stale_promise| is also cancelled.
|
|
|
|
|
|
|
|
SetGlobalProperty("stale_handler", Utils::ToLocal(stale_handler));
|
|
|
|
RunJS("%EnqueueMicrotask(stale_handler)");
|
|
|
|
|
|
|
|
v8_isolate()->EnqueueMicrotask(Utils::ToLocal(stale_handler));
|
|
|
|
|
|
|
|
JSPromise::Fulfill(
|
|
|
|
stale_promise,
|
|
|
|
handle(ReadOnlyRoots(isolate()).undefined_value(), isolate()));
|
|
|
|
|
|
|
|
microtask_queue()->RunMicrotasks(isolate());
|
|
|
|
EXPECT_TRUE(
|
|
|
|
Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsFalse());
|
|
|
|
EXPECT_TRUE(
|
|
|
|
Object::GetElement(isolate(), result, 1).ToHandleChecked()->IsFalse());
|
|
|
|
}
|
|
|
|
|
2019-04-11 08:35:41 +00:00
|
|
|
TEST_F(MicrotaskQueueTest, MicrotasksScope) {
|
|
|
|
ASSERT_NE(isolate()->default_microtask_queue(), microtask_queue());
|
|
|
|
microtask_queue()->set_microtasks_policy(MicrotasksPolicy::kScoped);
|
|
|
|
|
|
|
|
bool ran = false;
|
|
|
|
{
|
|
|
|
MicrotasksScope scope(v8_isolate(), microtask_queue(),
|
|
|
|
MicrotasksScope::kRunMicrotasks);
|
|
|
|
microtask_queue()->EnqueueMicrotask(*NewMicrotask([&ran]() {
|
|
|
|
EXPECT_FALSE(ran);
|
|
|
|
ran = true;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
EXPECT_TRUE(ran);
|
|
|
|
}
|
|
|
|
|
2018-11-27 08:32:53 +00:00
|
|
|
} // namespace internal
|
|
|
|
} // namespace v8
|