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