Skip to content

Commit 5912897

Browse files
committed
Guard the JSC unhandled-rejection SPI to Apple platforms
The macOS CI matrix built fine, but the Linux JSC job (WebKitGTK via gcc) failed: JSGlobalContextSetUnhandledRejectionCallback is an Apple SPI absent from WebKitGTK, and __builtin_available / its iOS/macOS platform names are Clang/Apple-only, so the unguarded registration didn't compile under gcc. - AppRuntime_JavaScriptCore.cpp: wrap the rejection-callback machinery (forward declaration, thread_local context, callback, and registration) in #if __APPLE__. On non-Apple JSC (WebKitGTK/Linux, Android JSC) tracking is now a documented no-op, consistent with the existing #if __APPLE__ guard around JSGlobalContextSetInspectable. - Shared.cpp: tighten the compile-time gate to V8 || (JavaScriptCore && __APPLE__), so the rejection tests skip on non-Apple JSC where the hook is unavailable (otherwise they would hang waiting for a report). - AppRuntime.h: note that JSC support is Apple-only (WebKitGTK JSC is a no-op). Re-validated on Win32 V8 Release: AppRuntime rejection tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 2eef639 commit 5912897

3 files changed

Lines changed: 24 additions & 10 deletions

File tree

Core/AppRuntime/Include/Babylon/AppRuntime.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ namespace Babylon
5252
// handled synchronously (e.g. `const p = Promise.reject(e); p.catch(...)`) is not reported.
5353
//
5454
// Coverage is determined by whether the engine exposes a host promise-rejection hook:
55-
// * V8 (Isolate::SetPromiseRejectCallback) and JavaScriptCore
56-
// (JSGlobalContextSetUnhandledRejectionCallback) -- supported.
55+
// * V8 (Isolate::SetPromiseRejectCallback) -- supported on all platforms.
56+
// * Apple JavaScriptCore (JSGlobalContextSetUnhandledRejectionCallback) -- supported. This
57+
// is an SPI present only in Apple's JSC; the WebKitGTK JSC used on Linux does not expose
58+
// it, so tracking is a no-op there.
5759
// * Chakra (in-box/EdgeMode) and JSI -- no-op: neither exposes such a hook
5860
// (JsSetHostPromiseRejectionTracker is ChakraCore-only, and neither jsi::Runtime nor
5961
// V8JSI surfaces the V8 callback).

Core/AppRuntime/Source/AppRuntime_JavaScriptCore.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
#include "AppRuntime.h"
2-
#include "AppRuntime_PromiseRejection.h"
32
#include <napi/env.h>
43

4+
#if __APPLE__
5+
#include "AppRuntime_PromiseRejection.h"
6+
57
// JSGlobalContextSetUnhandledRejectionCallback is declared in the private JavaScriptCore header
68
// <JavaScriptCore/JSContextRefPrivate.h>, which is not part of the public macOS/iOS SDK. The symbol
79
// is exported by the JavaScriptCore framework (SPI), so it is forward-declared here and the call is
810
// guarded with __builtin_available (it is JSC_API_AVAILABLE(macos(10.15.4), ios(13.4))). It registers
911
// a JS function invoked at the microtask checkpoint with (promise, reason) for each promise that is
1012
// still unhandled at that point, so -- unlike V8 -- no deferral or candidate bookkeeping is needed.
13+
// This is Apple-only: the WebKitGTK JavaScriptCore used on Linux exposes neither this SPI nor
14+
// __builtin_available, so unhandled-rejection tracking is a no-op there (see AppRuntime.h).
1115
extern "C" void JSGlobalContextSetUnhandledRejectionCallback(JSGlobalContextRef ctx, JSObjectRef function, JSValueRef* exception);
16+
#endif
1217

1318
namespace Babylon
1419
{
20+
#if __APPLE__
1521
namespace
1622
{
1723
// JSObjectMakeFunctionWithCallback takes no user-data argument; each AppRuntime owns its JSC
@@ -43,6 +49,7 @@ namespace Babylon
4349
return JSValueMakeUndefined(ctx);
4450
}
4551
}
52+
#endif
4653

4754
void AppRuntime::RunEnvironmentTier(const char*)
4855
{
@@ -57,6 +64,7 @@ namespace Babylon
5764

5865
Napi::Env env = Napi::Attach(globalContext);
5966

67+
#if __APPLE__
6068
// Always track unhandled promise rejections (routed to the host UnhandledExceptionHandler).
6169
JSCRejectionContext rejectionContext{this, env};
6270
t_rejectionContext = &rejectionContext;
@@ -67,10 +75,13 @@ namespace Babylon
6775
JSStringRelease(callbackName);
6876
JSGlobalContextSetUnhandledRejectionCallback(globalContext, callback, nullptr);
6977
}
78+
#endif
7079

7180
Run(env);
7281

82+
#if __APPLE__
7383
t_rejectionContext = nullptr;
84+
#endif
7485

7586
JSGlobalContextRelease(globalContext);
7687

Tests/UnitTests/Shared/Shared.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -283,11 +283,12 @@ TEST(AppRuntime, DestroyDoesNotDeadlock)
283283
TEST(AppRuntime, UnhandledPromiseRejectionReachesHandler)
284284
{
285285
// Unhandled promise rejection tracking is implemented on the engines that expose a host
286-
// promise-rejection hook: V8 (Isolate::SetPromiseRejectCallback) and JavaScriptCore
287-
// (JSGlobalContextSetUnhandledRejectionCallback). The OS EdgeMode Chakra runtime and the V8JSI
288-
// (JSI) shim expose no such hook, so the body is compiled out there and the test is skipped.
289-
#if !defined(JSRUNTIMEHOST_NAPI_ENGINE_V8) && !defined(JSRUNTIMEHOST_NAPI_ENGINE_JavaScriptCore)
290-
GTEST_SKIP() << "unhandled promise rejection tracking is only implemented for the V8 and JavaScriptCore backends";
286+
// promise-rejection hook: V8 (Isolate::SetPromiseRejectCallback) and Apple JavaScriptCore
287+
// (JSGlobalContextSetUnhandledRejectionCallback, an SPI absent from WebKitGTK/Linux JSC). The OS
288+
// EdgeMode Chakra runtime and the V8JSI (JSI) shim expose no such hook, so the body is compiled
289+
// out there (and on non-Apple JSC) and the test is skipped.
290+
#if !(defined(JSRUNTIMEHOST_NAPI_ENGINE_V8) || (defined(JSRUNTIMEHOST_NAPI_ENGINE_JavaScriptCore) && defined(__APPLE__)))
291+
GTEST_SKIP() << "unhandled promise rejection tracking requires the V8 or Apple JavaScriptCore backend";
291292
#else
292293
// A fire-and-forget rejected promise (no handler ever attached) must reach the embedder's
293294
// UnhandledExceptionHandler.
@@ -313,8 +314,8 @@ TEST(AppRuntime, UnhandledPromiseRejectionReachesHandler)
313314
TEST(AppRuntime, SynchronouslyHandledRejectionDoesNotReachHandler)
314315
{
315316
// Only engines with a host promise-rejection hook implement this tracking (see the note above).
316-
#if !defined(JSRUNTIMEHOST_NAPI_ENGINE_V8) && !defined(JSRUNTIMEHOST_NAPI_ENGINE_JavaScriptCore)
317-
GTEST_SKIP() << "unhandled promise rejection tracking is only implemented for the V8 and JavaScriptCore backends";
317+
#if !(defined(JSRUNTIMEHOST_NAPI_ENGINE_V8) || (defined(JSRUNTIMEHOST_NAPI_ENGINE_JavaScriptCore) && defined(__APPLE__)))
318+
GTEST_SKIP() << "unhandled promise rejection tracking requires the V8 or Apple JavaScriptCore backend";
318319
#else
319320
// A rejection that is handled synchronously in the same turn must NOT reach the handler --
320321
// reporting is deferred to the end of the turn, by which point the .catch has been attached.

0 commit comments

Comments
 (0)