Skip to content

Commit 7a5c9d1

Browse files
committed
feat(jsc): unhandled promise rejection tracker
1 parent 55b886a commit 7a5c9d1

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

test-app/app/src/main/assets/internal/ts_helpers.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,15 @@
424424
handledPromise(promise);
425425
}
426426
};
427+
} else if (globalThis.__engine === "JSC") {
428+
// JSC's unhandled-rejection callback only fires for rejections that go
429+
// unhandled (there is no "handled later" retraction event), and it is
430+
// invoked with (promise, reason).
431+
globalThis.onUnhandledPromiseRejectionTracker = (promise, reason) => {
432+
hasBeenNotifiedProperty.set(promise, false);
433+
const error = makeRejectionError(reason);
434+
unhandledPromise(promise, error);
435+
};
427436
} else if (globalThis.__engine === "Hermes") {
428437
HermesInternal.enablePromiseRejectionTracker({
429438
allRejections: true,

test-app/runtime/src/main/cpp/napi/jsc/jsr.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
11
#include "jsr.h"
2+
// JSGlobalContextSetUnhandledRejectionCallback lives in this private JSC header.
3+
#include <JavaScriptCore/JSContextRefPrivate.h>
4+
5+
// Native trampoline for JSC's unhandled-promise-rejection callback. JSC invokes
6+
// it with (promise, reason); we forward to the JS-side
7+
// globalThis.onUnhandledPromiseRejectionTracker (installed by ts_helpers.js),
8+
// mirroring how the V8 path routes rejections.
9+
static napi_value JscUnhandledRejectionCallback(napi_env env, napi_callback_info info) {
10+
size_t argc = 2;
11+
napi_value args[2];
12+
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
13+
14+
napi_value global, tracker;
15+
napi_get_global(env, &global);
16+
napi_get_named_property(env, global, "onUnhandledPromiseRejectionTracker", &tracker);
17+
18+
napi_valuetype type;
19+
napi_typeof(env, tracker, &type);
20+
if (type == napi_function) {
21+
napi_call_function(env, global, tracker, argc, args, nullptr);
22+
}
23+
return nullptr;
24+
}
225

326
napi_status js_create_runtime(jsr_ns_runtime *runtime) {
427
if (!runtime) return napi_invalid_arg;
@@ -23,6 +46,19 @@ napi_status js_create_napi_env(napi_env* env, jsr_ns_runtime runtime) {
2346
napi_get_global(*env, &global);
2447
napi_set_named_property(*env, global, "gc", gc);
2548

49+
// Report unhandled promise rejections. JSC keeps the callback alive (it is
50+
// stored on and marked by the global object), so no extra protection is
51+
// needed. JSC only surfaces the "unhandled" event, not a later "handled"
52+
// retraction, so the JS tracker treats every call as unhandled.
53+
napi_value rejectionCallback;
54+
napi_create_function(*env, "onUnhandledRejection", NAPI_AUTO_LENGTH,
55+
JscUnhandledRejectionCallback, nullptr, &rejectionCallback);
56+
JSValueRef rejectionException = nullptr;
57+
JSGlobalContextSetUnhandledRejectionCallback(
58+
(*env)->context,
59+
reinterpret_cast<JSObjectRef>(rejectionCallback),
60+
&rejectionException);
61+
2662

2763
return napi_ok;
2864

0 commit comments

Comments
 (0)