|
13 | 13 | #include <atomic>
|
14 | 14 | #include <condition_variable>
|
15 | 15 | #include <cstdlib>
|
| 16 | +#include <deque> |
16 | 17 | #include <mutex>
|
17 |
| -#include <queue> |
18 | 18 | #include <sstream>
|
19 | 19 | #include <thread>
|
20 | 20 |
|
@@ -51,6 +51,12 @@ class JSCRuntime : public jsi::Runtime {
|
51 | 51 | const std::shared_ptr<const jsi::Buffer>& buffer,
|
52 | 52 | const std::string& sourceURL) override;
|
53 | 53 |
|
| 54 | + // If we use this interface to implement microtasks in the host we need to |
| 55 | + // polyfill `Promise` to use these methods, because JSC doesn't currently |
| 56 | + // support providing a custom queue for its built-in implementation. |
| 57 | + // Not doing this would result in a non-compliant behavior, as microtasks |
| 58 | + // wouldn't execute in the order in which they were queued. |
| 59 | + void queueMicrotask(const jsi::Function& callback) override; |
54 | 60 | bool drainMicrotasks(int maxMicrotasksHint = -1) override;
|
55 | 61 |
|
56 | 62 | jsi::Object global() override;
|
@@ -265,6 +271,7 @@ class JSCRuntime : public jsi::Runtime {
|
265 | 271 | std::atomic<bool> ctxInvalid_;
|
266 | 272 | std::string desc_;
|
267 | 273 | JSValueRef nativeStateSymbol_ = nullptr;
|
| 274 | + std::deque<jsi::Function> microtaskQueue_; |
268 | 275 | #ifndef NDEBUG
|
269 | 276 | mutable std::atomic<intptr_t> objectCounter_;
|
270 | 277 | mutable std::atomic<intptr_t> symbolCounter_;
|
@@ -378,6 +385,10 @@ JSCRuntime::JSCRuntime(JSGlobalContextRef ctx)
|
378 | 385 | }
|
379 | 386 |
|
380 | 387 | JSCRuntime::~JSCRuntime() {
|
| 388 | + // We need to clear the microtask queue to remove all references to the |
| 389 | + // callbacks, so objectCounter_ would be 0 below. |
| 390 | + microtaskQueue_.clear(); |
| 391 | + |
381 | 392 | // On shutting down and cleaning up: when JSC is actually torn down,
|
382 | 393 | // it calls JSC::Heap::lastChanceToFinalize internally which
|
383 | 394 | // finalizes anything left over. But at this point,
|
@@ -434,7 +445,24 @@ jsi::Value JSCRuntime::evaluateJavaScript(
|
434 | 445 | return createValue(res);
|
435 | 446 | }
|
436 | 447 |
|
437 |
| -bool JSCRuntime::drainMicrotasks(int maxMicrotasksHint) { |
| 448 | +void JSCRuntime::queueMicrotask(const jsi::Function& callback) { |
| 449 | + microtaskQueue_.emplace_back( |
| 450 | + jsi::Value(*this, callback).asObject(*this).asFunction(*this)); |
| 451 | +} |
| 452 | + |
| 453 | +bool JSCRuntime::drainMicrotasks(int /*maxMicrotasksHint*/) { |
| 454 | + // Note that new jobs can be enqueued during the draining. |
| 455 | + while (!microtaskQueue_.empty()) { |
| 456 | + jsi::Function callback = std::move(microtaskQueue_.front()); |
| 457 | + |
| 458 | + // We need to pop before calling the callback because that might throw. |
| 459 | + // When that happens, the host will call `drainMicrotasks` again to execute |
| 460 | + // the remaining microtasks, and this one shouldn't run again. |
| 461 | + microtaskQueue_.pop_front(); |
| 462 | + |
| 463 | + callback.call(*this); |
| 464 | + } |
| 465 | + |
438 | 466 | return true;
|
439 | 467 | }
|
440 | 468 |
|
|
0 commit comments