Line data Source code
1 : // Copyright 2018 the V8 project 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 "src/microtask-queue.h"
6 :
7 : #include <algorithm>
8 : #include <functional>
9 : #include <memory>
10 : #include <vector>
11 :
12 : #include "src/heap/factory.h"
13 : #include "src/objects-inl.h"
14 : #include "src/objects/foreign.h"
15 : #include "src/objects/js-array-inl.h"
16 : #include "src/objects/js-objects-inl.h"
17 : #include "src/objects/promise-inl.h"
18 : #include "src/visitors.h"
19 : #include "test/unittests/test-utils.h"
20 : #include "testing/gtest/include/gtest/gtest.h"
21 :
22 : namespace v8 {
23 : namespace internal {
24 :
25 : using Closure = std::function<void()>;
26 :
27 22 : void RunStdFunction(void* data) {
28 : std::unique_ptr<Closure> f(static_cast<Closure*>(data));
29 : (*f)();
30 22 : }
31 :
32 : template <typename TMixin>
33 : class WithFinalizationGroupMixin : public TMixin {
34 : public:
35 13 : WithFinalizationGroupMixin() = default;
36 13 : ~WithFinalizationGroupMixin() override = default;
37 :
38 13 : static void SetUpTestCase() {
39 13 : CHECK_NULL(save_flags_);
40 13 : save_flags_ = new SaveFlags();
41 13 : FLAG_harmony_weak_refs = true;
42 13 : FLAG_expose_gc = true;
43 13 : FLAG_allow_natives_syntax = true;
44 : TMixin::SetUpTestCase();
45 13 : }
46 :
47 13 : static void TearDownTestCase() {
48 : TMixin::TearDownTestCase();
49 13 : CHECK_NOT_NULL(save_flags_);
50 13 : delete save_flags_;
51 13 : save_flags_ = nullptr;
52 13 : }
53 :
54 : private:
55 : static SaveFlags* save_flags_;
56 :
57 : DISALLOW_COPY_AND_ASSIGN(WithFinalizationGroupMixin);
58 : };
59 :
60 : template <typename TMixin>
61 : SaveFlags* WithFinalizationGroupMixin<TMixin>::save_flags_ = nullptr;
62 :
63 : using TestWithNativeContextAndFinalizationGroup = //
64 : WithInternalIsolateMixin< //
65 : WithContextMixin< //
66 : WithFinalizationGroupMixin< //
67 : WithIsolateScopeMixin< //
68 : WithSharedIsolateMixin< //
69 : ::testing::Test>>>>>;
70 :
71 39 : class MicrotaskQueueTest : public TestWithNativeContextAndFinalizationGroup {
72 : public:
73 : template <typename F>
74 22 : Handle<Microtask> NewMicrotask(F&& f) {
75 : Handle<Foreign> runner =
76 44 : factory()->NewForeign(reinterpret_cast<Address>(&RunStdFunction));
77 44 : Handle<Foreign> data = factory()->NewForeign(
78 22 : reinterpret_cast<Address>(new Closure(std::forward<F>(f))));
79 22 : return factory()->NewCallbackTask(runner, data);
80 : }
81 :
82 13 : void SetUp() override {
83 26 : microtask_queue_ = MicrotaskQueue::New(isolate());
84 : native_context()->set_microtask_queue(microtask_queue());
85 13 : }
86 :
87 13 : void TearDown() override {
88 13 : if (microtask_queue()) {
89 12 : microtask_queue()->RunMicrotasks(isolate());
90 12 : context()->DetachGlobal();
91 : }
92 13 : }
93 :
94 : MicrotaskQueue* microtask_queue() const { return microtask_queue_.get(); }
95 :
96 1 : void ClearTestMicrotaskQueue() {
97 1 : context()->DetachGlobal();
98 : microtask_queue_ = nullptr;
99 1 : }
100 :
101 : template <size_t N>
102 : Handle<Name> NameFromChars(const char (&chars)[N]) {
103 4 : return isolate()->factory()->NewStringFromStaticChars(chars);
104 : }
105 :
106 : private:
107 : std::unique_ptr<MicrotaskQueue> microtask_queue_;
108 : };
109 :
110 : class RecordingVisitor : public RootVisitor {
111 : public:
112 1 : RecordingVisitor() = default;
113 2 : ~RecordingVisitor() override = default;
114 :
115 2 : void VisitRootPointers(Root root, const char* description,
116 : FullObjectSlot start, FullObjectSlot end) override {
117 9 : for (FullObjectSlot current = start; current != end; ++current) {
118 5 : visited_.push_back(*current);
119 : }
120 2 : }
121 :
122 : const std::vector<Object>& visited() const { return visited_; }
123 :
124 : private:
125 : std::vector<Object> visited_;
126 : };
127 :
128 : // Sanity check. Ensure a microtask is stored in a queue and run.
129 15443 : TEST_F(MicrotaskQueueTest, EnqueueAndRun) {
130 1 : bool ran = false;
131 2 : EXPECT_EQ(0, microtask_queue()->capacity());
132 2 : EXPECT_EQ(0, microtask_queue()->size());
133 4 : microtask_queue()->EnqueueMicrotask(*NewMicrotask([&ran] {
134 2 : EXPECT_FALSE(ran);
135 1 : ran = true;
136 4 : }));
137 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue()->capacity());
138 2 : EXPECT_EQ(1, microtask_queue()->size());
139 2 : EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
140 1 : EXPECT_TRUE(ran);
141 2 : EXPECT_EQ(0, microtask_queue()->size());
142 1 : }
143 :
144 : // Check for a buffer growth.
145 15443 : TEST_F(MicrotaskQueueTest, BufferGrowth) {
146 1 : int count = 0;
147 :
148 : // Enqueue and flush the queue first to have non-zero |start_|.
149 : microtask_queue()->EnqueueMicrotask(
150 5 : *NewMicrotask([&count] { EXPECT_EQ(0, count++); }));
151 2 : EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
152 :
153 1 : EXPECT_LT(0, microtask_queue()->capacity());
154 2 : EXPECT_EQ(0, microtask_queue()->size());
155 2 : EXPECT_EQ(1, microtask_queue()->start());
156 :
157 : // Fill the queue with Microtasks.
158 17 : for (int i = 1; i <= MicrotaskQueue::kMinimumCapacity; ++i) {
159 : microtask_queue()->EnqueueMicrotask(
160 40 : *NewMicrotask([&count, i] { EXPECT_EQ(i, count++); }));
161 : }
162 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue()->capacity());
163 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity, microtask_queue()->size());
164 :
165 : // Add another to grow the ring buffer.
166 2 : microtask_queue()->EnqueueMicrotask(*NewMicrotask(
167 6 : [&] { EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1, count++); }));
168 :
169 1 : EXPECT_LT(MicrotaskQueue::kMinimumCapacity, microtask_queue()->capacity());
170 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1, microtask_queue()->size());
171 :
172 : // Run all pending Microtasks to ensure they run in the proper order.
173 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 1,
174 0 : microtask_queue()->RunMicrotasks(isolate()));
175 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity + 2, count);
176 1 : }
177 :
178 : // MicrotaskQueue instances form a doubly linked list.
179 15443 : TEST_F(MicrotaskQueueTest, InstanceChain) {
180 1 : ClearTestMicrotaskQueue();
181 :
182 1 : MicrotaskQueue* default_mtq = isolate()->default_microtask_queue();
183 1 : ASSERT_TRUE(default_mtq);
184 2 : EXPECT_EQ(default_mtq, default_mtq->next());
185 2 : EXPECT_EQ(default_mtq, default_mtq->prev());
186 :
187 : // Create two instances, and check their connection.
188 : // The list contains all instances in the creation order, and the next of the
189 : // last instance is the first instance:
190 : // default_mtq -> mtq1 -> mtq2 -> default_mtq.
191 1 : std::unique_ptr<MicrotaskQueue> mtq1 = MicrotaskQueue::New(isolate());
192 1 : std::unique_ptr<MicrotaskQueue> mtq2 = MicrotaskQueue::New(isolate());
193 2 : EXPECT_EQ(default_mtq->next(), mtq1.get());
194 3 : EXPECT_EQ(mtq1->next(), mtq2.get());
195 2 : EXPECT_EQ(mtq2->next(), default_mtq);
196 2 : EXPECT_EQ(default_mtq, mtq1->prev());
197 3 : EXPECT_EQ(mtq1.get(), mtq2->prev());
198 3 : EXPECT_EQ(mtq2.get(), default_mtq->prev());
199 :
200 : // Deleted item should be also removed from the list.
201 : mtq1 = nullptr;
202 2 : EXPECT_EQ(default_mtq->next(), mtq2.get());
203 2 : EXPECT_EQ(mtq2->next(), default_mtq);
204 2 : EXPECT_EQ(default_mtq, mtq2->prev());
205 3 : EXPECT_EQ(mtq2.get(), default_mtq->prev());
206 : }
207 :
208 : // Pending Microtasks in MicrotaskQueues are strong roots. Ensure they are
209 : // visited exactly once.
210 15443 : TEST_F(MicrotaskQueueTest, VisitRoot) {
211 : // Ensure that the ring buffer has separate in-use region.
212 11 : for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
213 10 : microtask_queue()->EnqueueMicrotask(*NewMicrotask([] {}));
214 : }
215 2 : EXPECT_EQ(MicrotaskQueue::kMinimumCapacity / 2 + 1,
216 0 : microtask_queue()->RunMicrotasks(isolate()));
217 :
218 : std::vector<Object> expected;
219 11 : for (int i = 0; i < MicrotaskQueue::kMinimumCapacity / 2 + 1; ++i) {
220 5 : Handle<Microtask> microtask = NewMicrotask([] {});
221 10 : expected.push_back(*microtask);
222 5 : microtask_queue()->EnqueueMicrotask(*microtask);
223 : }
224 1 : EXPECT_GT(microtask_queue()->start() + microtask_queue()->size(),
225 0 : microtask_queue()->capacity());
226 :
227 : RecordingVisitor visitor;
228 1 : microtask_queue()->IterateMicrotasks(&visitor);
229 :
230 1 : std::vector<Object> actual = visitor.visited();
231 : std::sort(expected.begin(), expected.end());
232 : std::sort(actual.begin(), actual.end());
233 1 : EXPECT_EQ(expected, actual);
234 1 : }
235 :
236 15443 : TEST_F(MicrotaskQueueTest, PromiseHandlerContext) {
237 1 : Local<v8::Context> v8_context2 = v8::Context::New(v8_isolate());
238 1 : Local<v8::Context> v8_context3 = v8::Context::New(v8_isolate());
239 1 : Local<v8::Context> v8_context4 = v8::Context::New(v8_isolate());
240 : Handle<Context> context2 = Utils::OpenHandle(*v8_context2, isolate());
241 : Handle<Context> context3 = Utils::OpenHandle(*v8_context3, isolate());
242 : Handle<Context> context4 = Utils::OpenHandle(*v8_context3, isolate());
243 : context2->native_context()->set_microtask_queue(microtask_queue());
244 : context3->native_context()->set_microtask_queue(microtask_queue());
245 : context4->native_context()->set_microtask_queue(microtask_queue());
246 :
247 : Handle<JSFunction> handler;
248 : Handle<JSProxy> proxy;
249 : Handle<JSProxy> revoked_proxy;
250 : Handle<JSBoundFunction> bound;
251 :
252 : // Create a JSFunction on |context2|
253 : {
254 : v8::Context::Scope scope(v8_context2);
255 : handler = RunJS<JSFunction>("()=>{}");
256 4 : EXPECT_EQ(*context2,
257 0 : *JSReceiver::GetContextForMicrotask(handler).ToHandleChecked());
258 : }
259 :
260 : // Create a JSProxy on |context3|.
261 : {
262 : v8::Context::Scope scope(v8_context3);
263 4 : ASSERT_TRUE(
264 : v8_context3->Global()
265 : ->Set(v8_context3, NewString("handler"), Utils::ToLocal(handler))
266 : .FromJust());
267 : proxy = RunJS<JSProxy>("new Proxy(handler, {})");
268 : revoked_proxy = RunJS<JSProxy>(
269 : "let {proxy, revoke} = Proxy.revocable(handler, {});"
270 : "revoke();"
271 : "proxy");
272 4 : EXPECT_EQ(*context2,
273 0 : *JSReceiver::GetContextForMicrotask(proxy).ToHandleChecked());
274 2 : EXPECT_TRUE(JSReceiver::GetContextForMicrotask(revoked_proxy).is_null());
275 : }
276 :
277 : // Create a JSBoundFunction on |context4|.
278 : // Note that its CreationContext and ContextForTaskCancellation is |context2|.
279 : {
280 : v8::Context::Scope scope(v8_context4);
281 4 : ASSERT_TRUE(
282 : v8_context4->Global()
283 : ->Set(v8_context4, NewString("handler"), Utils::ToLocal(handler))
284 : .FromJust());
285 : bound = RunJS<JSBoundFunction>("handler.bind()");
286 4 : EXPECT_EQ(*context2,
287 0 : *JSReceiver::GetContextForMicrotask(bound).ToHandleChecked());
288 : }
289 :
290 : // Give the objects to the main context.
291 1 : SetGlobalProperty("handler", Utils::ToLocal(handler));
292 1 : SetGlobalProperty("proxy", Utils::ToLocal(proxy));
293 1 : SetGlobalProperty("revoked_proxy", Utils::ToLocal(revoked_proxy));
294 1 : SetGlobalProperty("bound", Utils::ToLocal(Handle<JSReceiver>::cast(bound)));
295 : RunJS(
296 : "Promise.resolve().then(handler);"
297 : "Promise.reject().catch(proxy);"
298 : "Promise.resolve().then(revoked_proxy);"
299 : "Promise.resolve().then(bound);");
300 :
301 2 : ASSERT_EQ(4, microtask_queue()->size());
302 1 : Handle<Microtask> microtask1(microtask_queue()->get(0), isolate());
303 1 : ASSERT_TRUE(microtask1->IsPromiseFulfillReactionJobTask());
304 3 : EXPECT_EQ(*context2,
305 0 : Handle<PromiseFulfillReactionJobTask>::cast(microtask1)->context());
306 :
307 1 : Handle<Microtask> microtask2(microtask_queue()->get(1), isolate());
308 1 : ASSERT_TRUE(microtask2->IsPromiseRejectReactionJobTask());
309 3 : EXPECT_EQ(*context2,
310 0 : Handle<PromiseRejectReactionJobTask>::cast(microtask2)->context());
311 :
312 1 : Handle<Microtask> microtask3(microtask_queue()->get(2), isolate());
313 1 : ASSERT_TRUE(microtask3->IsPromiseFulfillReactionJobTask());
314 : // |microtask3| corresponds to a PromiseReaction for |revoked_proxy|.
315 : // As |revoked_proxy| doesn't have a context, the current context should be
316 : // used as the fallback context.
317 3 : EXPECT_EQ(*native_context(),
318 0 : Handle<PromiseFulfillReactionJobTask>::cast(microtask3)->context());
319 :
320 1 : Handle<Microtask> microtask4(microtask_queue()->get(3), isolate());
321 1 : ASSERT_TRUE(microtask4->IsPromiseFulfillReactionJobTask());
322 3 : EXPECT_EQ(*context2,
323 0 : Handle<PromiseFulfillReactionJobTask>::cast(microtask4)->context());
324 :
325 1 : v8_context4->DetachGlobal();
326 1 : v8_context3->DetachGlobal();
327 1 : v8_context2->DetachGlobal();
328 : }
329 :
330 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_Enqueue) {
331 2 : EXPECT_EQ(0, microtask_queue()->size());
332 :
333 : // Detach MicrotaskQueue from the current context.
334 1 : context()->DetachGlobal();
335 :
336 : // No microtask should be enqueued after DetachGlobal call.
337 2 : EXPECT_EQ(0, microtask_queue()->size());
338 : RunJS("Promise.resolve().then(()=>{})");
339 2 : EXPECT_EQ(0, microtask_queue()->size());
340 1 : }
341 :
342 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_Run) {
343 2 : EXPECT_EQ(0, microtask_queue()->size());
344 :
345 : // Enqueue microtasks to the current context.
346 : Handle<JSArray> ran = RunJS<JSArray>(
347 : "var ran = [false, false, false, false];"
348 : "Promise.resolve().then(() => { ran[0] = true; });"
349 : "Promise.reject().catch(() => { ran[1] = true; });"
350 : "ran");
351 :
352 : Handle<JSFunction> function =
353 : RunJS<JSFunction>("(function() { ran[2] = true; })");
354 : Handle<CallableTask> callable =
355 1 : factory()->NewCallableTask(function, Utils::OpenHandle(*context()));
356 2 : microtask_queue()->EnqueueMicrotask(*callable);
357 :
358 : // The handler should not run at this point.
359 1 : const int kNumExpectedTasks = 3;
360 7 : for (int i = 0; i < kNumExpectedTasks; ++i) {
361 9 : EXPECT_TRUE(
362 0 : Object::GetElement(isolate(), ran, i).ToHandleChecked()->IsFalse());
363 : }
364 2 : EXPECT_EQ(kNumExpectedTasks, microtask_queue()->size());
365 :
366 : // Detach MicrotaskQueue from the current context.
367 1 : context()->DetachGlobal();
368 :
369 : // RunMicrotasks processes pending Microtasks, but Microtasks that are
370 : // associated to a detached context should be cancelled and should not take
371 : // effect.
372 1 : microtask_queue()->RunMicrotasks(isolate());
373 2 : EXPECT_EQ(0, microtask_queue()->size());
374 7 : for (int i = 0; i < kNumExpectedTasks; ++i) {
375 9 : EXPECT_TRUE(
376 0 : Object::GetElement(isolate(), ran, i).ToHandleChecked()->IsFalse());
377 : }
378 1 : }
379 :
380 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_FinalizationGroup) {
381 : // Enqueue an FinalizationGroupCleanupTask.
382 : Handle<JSArray> ran = RunJS<JSArray>(
383 : "var ran = [false];"
384 : "var wf = new FinalizationGroup(() => { ran[0] = true; });"
385 : "(function() { wf.register({}, {}); })();"
386 : "gc();"
387 : "ran");
388 :
389 2 : EXPECT_TRUE(
390 0 : Object::GetElement(isolate(), ran, 0).ToHandleChecked()->IsFalse());
391 2 : EXPECT_EQ(1, microtask_queue()->size());
392 :
393 : // Detach MicrotaskQueue from the current context.
394 1 : context()->DetachGlobal();
395 :
396 1 : microtask_queue()->RunMicrotasks(isolate());
397 :
398 : // RunMicrotasks processes the pending Microtask, but Microtasks that are
399 : // associated to a detached context should be cancelled and should not take
400 : // effect.
401 2 : EXPECT_EQ(0, microtask_queue()->size());
402 2 : EXPECT_TRUE(
403 0 : Object::GetElement(isolate(), ran, 0).ToHandleChecked()->IsFalse());
404 1 : }
405 :
406 : namespace {
407 :
408 3 : void DummyPromiseHook(PromiseHookType type, Local<Promise> promise,
409 3 : Local<Value> parent) {}
410 :
411 : } // namespace
412 :
413 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_PromiseResolveThenableJobTask) {
414 : // Use a PromiseHook to switch the implementation to ResolvePromise runtime,
415 : // instead of ResolvePromise builtin.
416 1 : v8_isolate()->SetPromiseHook(&DummyPromiseHook);
417 :
418 : RunJS(
419 : "var resolve;"
420 : "var promise = new Promise(r => { resolve = r; });"
421 : "promise.then(() => {});"
422 : "resolve({});");
423 :
424 : // A PromiseResolveThenableJobTask is pending in the MicrotaskQueue.
425 2 : EXPECT_EQ(1, microtask_queue()->size());
426 :
427 : // Detach MicrotaskQueue from the current context.
428 1 : context()->DetachGlobal();
429 :
430 : // RunMicrotasks processes the pending Microtask, but Microtasks that are
431 : // associated to a detached context should be cancelled and should not take
432 : // effect.
433 : // As PromiseResolveThenableJobTask queues another task for resolution,
434 : // the return value is 2 if it ran.
435 2 : EXPECT_EQ(1, microtask_queue()->RunMicrotasks(isolate()));
436 2 : EXPECT_EQ(0, microtask_queue()->size());
437 1 : }
438 :
439 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_HandlerContext) {
440 : // EnqueueMicrotask should use the context associated to the handler instead
441 : // of the current context. E.g.
442 : // // At Context A.
443 : // let resolved = Promise.resolve();
444 : // // Call DetachGlobal on A, so that microtasks associated to A is
445 : // // cancelled.
446 : //
447 : // // At Context B.
448 : // let handler = () => {
449 : // console.log("here");
450 : // };
451 : // // The microtask to run |handler| should be associated to B instead of A,
452 : // // so that handler runs even |resolved| is on the detached context A.
453 : // resolved.then(handler);
454 :
455 1 : Handle<JSReceiver> results = isolate()->factory()->NewJSObjectWithNullProto();
456 :
457 : // These belong to a stale Context.
458 : Handle<JSPromise> stale_resolved_promise;
459 : Handle<JSPromise> stale_rejected_promise;
460 : Handle<JSReceiver> stale_handler;
461 :
462 1 : Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
463 : {
464 : v8::Context::Scope scope(sub_context);
465 : stale_resolved_promise = RunJS<JSPromise>("Promise.resolve()");
466 : stale_rejected_promise = RunJS<JSPromise>("Promise.reject()");
467 : stale_handler = RunJS<JSReceiver>(
468 : "(results, label) => {"
469 : " results[label] = true;"
470 : "}");
471 : }
472 : // DetachGlobal() cancells all microtasks associated to the context.
473 1 : sub_context->DetachGlobal();
474 : sub_context.Clear();
475 :
476 1 : SetGlobalProperty("results", Utils::ToLocal(results));
477 1 : SetGlobalProperty(
478 : "stale_resolved_promise",
479 1 : Utils::ToLocal(Handle<JSReceiver>::cast(stale_resolved_promise)));
480 1 : SetGlobalProperty(
481 : "stale_rejected_promise",
482 1 : Utils::ToLocal(Handle<JSReceiver>::cast(stale_rejected_promise)));
483 1 : SetGlobalProperty("stale_handler", Utils::ToLocal(stale_handler));
484 :
485 : // Set valid handlers to stale promises.
486 : RunJS(
487 : "stale_resolved_promise.then(() => {"
488 : " results['stale_resolved_promise'] = true;"
489 : "})");
490 : RunJS(
491 : "stale_rejected_promise.catch(() => {"
492 : " results['stale_rejected_promise'] = true;"
493 : "})");
494 1 : microtask_queue()->RunMicrotasks(isolate());
495 2 : EXPECT_TRUE(
496 : JSReceiver::HasProperty(results, NameFromChars("stale_resolved_promise"))
497 0 : .FromJust());
498 2 : EXPECT_TRUE(
499 : JSReceiver::HasProperty(results, NameFromChars("stale_rejected_promise"))
500 0 : .FromJust());
501 :
502 : // Set stale handlers to valid promises.
503 : RunJS(
504 : "Promise.resolve("
505 : " stale_handler.bind(null, results, 'stale_handler_resolve'))");
506 : RunJS(
507 : "Promise.reject("
508 : " stale_handler.bind(null, results, 'stale_handler_reject'))");
509 1 : microtask_queue()->RunMicrotasks(isolate());
510 3 : EXPECT_FALSE(
511 : JSReceiver::HasProperty(results, NameFromChars("stale_handler_resolve"))
512 0 : .FromJust());
513 3 : EXPECT_FALSE(
514 : JSReceiver::HasProperty(results, NameFromChars("stale_handler_reject"))
515 0 : .FromJust());
516 1 : }
517 :
518 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_Chain) {
519 : Handle<JSPromise> stale_rejected_promise;
520 :
521 1 : Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
522 : {
523 : v8::Context::Scope scope(sub_context);
524 : stale_rejected_promise = RunJS<JSPromise>("Promise.reject()");
525 : }
526 1 : sub_context->DetachGlobal();
527 : sub_context.Clear();
528 :
529 2 : SetGlobalProperty(
530 : "stale_rejected_promise",
531 1 : Utils::ToLocal(Handle<JSReceiver>::cast(stale_rejected_promise)));
532 : Handle<JSArray> result = RunJS<JSArray>(
533 : "let result = [false];"
534 : "stale_rejected_promise"
535 : " .then(() => {})"
536 : " .catch(() => {"
537 : " result[0] = true;"
538 : " });"
539 : "result");
540 1 : microtask_queue()->RunMicrotasks(isolate());
541 2 : EXPECT_TRUE(
542 0 : Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsTrue());
543 1 : }
544 :
545 15443 : TEST_F(MicrotaskQueueTest, DetachGlobal_InactiveHandler) {
546 1 : Local<v8::Context> sub_context = v8::Context::New(v8_isolate());
547 : Utils::OpenHandle(*sub_context)
548 : ->native_context()
549 : ->set_microtask_queue(microtask_queue());
550 :
551 : Handle<JSArray> result;
552 : Handle<JSFunction> stale_handler;
553 : Handle<JSPromise> stale_promise;
554 : {
555 : v8::Context::Scope scope(sub_context);
556 : result = RunJS<JSArray>("var result = [false, false]; result");
557 : stale_handler = RunJS<JSFunction>("() => { result[0] = true; }");
558 : stale_promise = RunJS<JSPromise>(
559 : "var stale_promise = new Promise(()=>{});"
560 1 : "stale_promise");
561 : RunJS("stale_promise.then(() => { result [1] = true; });");
562 : }
563 1 : sub_context->DetachGlobal();
564 : sub_context.Clear();
565 :
566 : // The context of |stale_handler| and |stale_promise| is detached at this
567 : // point.
568 : // Ensure that resolution handling for |stale_handler| is cancelled without
569 : // crash. Also, the resolution of |stale_promise| is also cancelled.
570 :
571 1 : SetGlobalProperty("stale_handler", Utils::ToLocal(stale_handler));
572 : RunJS("%EnqueueMicrotask(stale_handler)");
573 :
574 1 : v8_isolate()->EnqueueMicrotask(Utils::ToLocal(stale_handler));
575 :
576 : JSPromise::Fulfill(
577 : stale_promise,
578 1 : handle(ReadOnlyRoots(isolate()).undefined_value(), isolate()));
579 :
580 1 : microtask_queue()->RunMicrotasks(isolate());
581 2 : EXPECT_TRUE(
582 0 : Object::GetElement(isolate(), result, 0).ToHandleChecked()->IsFalse());
583 2 : EXPECT_TRUE(
584 0 : Object::GetElement(isolate(), result, 1).ToHandleChecked()->IsFalse());
585 1 : }
586 :
587 15443 : TEST_F(MicrotaskQueueTest, MicrotasksScope) {
588 2 : ASSERT_NE(isolate()->default_microtask_queue(), microtask_queue());
589 : microtask_queue()->set_microtasks_policy(MicrotasksPolicy::kScoped);
590 :
591 1 : bool ran = false;
592 : {
593 : MicrotasksScope scope(v8_isolate(), microtask_queue(),
594 2 : MicrotasksScope::kRunMicrotasks);
595 4 : microtask_queue()->EnqueueMicrotask(*NewMicrotask([&ran]() {
596 2 : EXPECT_FALSE(ran);
597 1 : ran = true;
598 4 : }));
599 : }
600 1 : EXPECT_TRUE(ran);
601 : }
602 :
603 : } // namespace internal
604 9264 : } // namespace v8
|