Skip to content

[integration] wgpu-browser-polyfill-stack 2026-09 (main @ f47991d + all open PRs + Worker/IndexedDB) - #22

Open
matthargett wants to merge 218 commits into
mainfrom
wgpu-browser-polyfill-stack-2026-09
Open

matthargett wants to merge 218 commits into
mainfrom
wgpu-browser-polyfill-stack-2026-09

Conversation

@matthargett

Copy link
Copy Markdown
Member

Rebuilt integration branch for BabylonNative wgpu: upstream main @ f47991d (BabylonJS#232) merged with the current heads of every open PR — napi-v7 (BabylonJS#189, incl. BabylonJS#116/BabylonJS#183), BabylonJS#217, BabylonJS#182, the polyfill family (BabylonJS#208–BabylonJS#212), engine-fixes (BabylonJS#237) — plus the Worker + IndexedDB polyfills carried from feature/web-worker, the Apple run-loop pump and the JSC environment-registry sync from the previous integration branch.

Deliberately NOT carried (superseded): the July "CTS refresh" batch and the "transitional JSC detach" series (replaced by the rebased napi-v7 work), the TextDecoder UTF-16 commit (upstream BabylonJS#230/BabylonJS#235), the early data-URL commit (BabylonJS#212), and the QuickJS throw-status fix (upstream BabylonJS#225).

Duplicate test/CMake entries produced by merging sibling branches that carry copies of the same commits were removed. CI on this PR is the validation — no local build (disk).

bkaradzic-microsoft and others added 30 commits September 10, 2026 16:29
…JS#232)

V8 finishes asynchronous WebAssembly compilation on a background thread
and then posts a **foreground** task to settle the promise on the
isolate thread. `AppRuntime_JSI` already ran those via
`TaskRunnerAdapter`; the direct-V8 path dropped them, so asynchronous
WebAssembly promises could hang. Synchronous `new WebAssembly.Module`
was unaffected.

Foreground posts now go through `V8ForegroundTaskRunner`, which
dispatches `v8::Task::Run` through AppRuntime. Delayed posts use
`DelayedTaskScheduler`, extracted into Foundation from the timer queue
behind `setTimeout` / `setInterval`. AppRuntime registers its scheduler
with the environment for the polyfill to borrow; standalone `JsRuntime`
instances retain their original API and do not acquire timer ownership.
`TimeoutDispatcher` owns a fallback scheduler when no host scheduler is
registered. Interval rearming remains on the JavaScript thread after the
callback.

Delayed V8 posts round their absolute requested time upward to
microsecond precision, and completed pending records are removed even
when completion precedes `Schedule` returning. Shared callback state and
explicit host unregistration guard teardown independently of retained
task-runner references. `V8Platform` and the foreground runner are in
dedicated files; the wake-and-pump bridge and `PumpMessageLoop` are
gone.

Added asynchronous WebAssembly promise-settlement tests plus native
timing, pending-record, cancellation, scheduler-ownership, and shutdown
regressions.

Validation for 7192ac0 (Windows Release): V8 11.9 passed 17 selected
native tests, including the JavaScript suite with 228 passing; Chakra
passed 9 selected native tests, including 225 JavaScript tests with the
3 V8-only tests skipped. The 16 native timer/lifetime tests also passed
25 repeated iterations. The V8 implementation files syntax-compile
against the actual Android V8 11.0 headers; Android device execution
remains for CI.

---------

Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 88569c10-a7ff-4373-9a58-afa9c68b8c09
Copilot-Session: da74bc94-a7dc-4817-bd81-59b5c6b123fc
Copilot-Session: 54aaa1e1-b4b3-46e7-9de4-5a56add4ac42
V8 weak callbacks enqueue N-API finalizers for a safe second pass, but AppRuntime did not drain that queue. Run the queue after each host dispatch and cover the behavior with a deterministic V8-only unit test.
Run deferred V8 finalizers once after each dispatcher batch and cap each batch at eight milliseconds. This prevents large GC queues from monopolizing the runtime thread while preserving eventual finalization on later turns. Add a deterministic slow-finalizer regression test that proves the queue yields and then drains completely.
…ction when the receiver was undefined, so the VM forcibly substituted the global object even in strict mode. The new implementation always routes through Function.prototype.call, preserving the exact thisArg. This only affected JSC: Chakra already pushes recv onto the argv array before invoking JsCallFunction, and V8 hands the raw recv value to Function::Call. Neither engine coerces in strict mode, so no additional fixes were required.
…bug fix in strict mode was actually found by the suite! The failing behavior was exercised by Tests/NodeApi/test/js-native-api/3_callbacks/test.js. New cmake targets emit a node-lite binary and a NodeApiTests binary, all currently enabled tests for currently supported NAPI v5 pass on Mac. Next step is to enable them for running in Android simulator.
…x handles during instrumentation, causing crashes, and now route console output through the new NodeLiteRuntime::Callbacks. On Android we forward stdout/stderr to logcat via callbacks to work around this for now. Added Android-specific shims (node_lite_android.cpp, child_process_android.cpp) so native module loading uses dlopen and JS child_process.spawnSync safely reports “unsupported”. Extended the Node‑API harness to allow in-process execution: RunNodeLiteScript captures output, SetNodeApiTestEnvironment lets the JNI layer provide a base directory and asset manager, and the GTest registration path uses that configuration instead of shelling out to the node_lite executable
…-- a use-after-free. Will check sanitizers under Android next
Two build-restoration fixes (no behavior/impl or NAPI-version changes), needed
after rebasing onto upstream HEAD and building with the current Xcode/libc++:

- node_lite: NodeApi::CallFunction took std::span<napi_value> but is only ever
  called with braced-init-lists ({a,b,c}). Newer libc++ correctly rejects
  constructing a non-const std::span from an initializer_list (that ctor is
  C++26). Switch the parameter to std::initializer_list<napi_value> (begin()
  yields the const napi_value* napi_call_function wants).

- Tests/NodeApi: the POST_BUILD copy_directory of each .node runs as an Xcode
  script phase BEFORE Xcode's implicit CodeSign phase signs the original, so the
  copied addons that node_lite/NodeApiTests dlopen are unsigned on a clean build
  and macOS refuses to load them. Ad-hoc sign the copies directly (APPLE only).
…mulator)

Build-restoration fixes for the Android in-process NodeApi harness after rebasing
onto upstream HEAD (no impl/NAPI-version changes). Each was a latent break in the
napi-tests Android integration, surfaced by a clean build on a current toolchain:

- CMakeLists.txt: drop the AndroidExtensions Globals.cpp 'patch' step. It file(COPY)'d
  patches/AndroidExtensions/Globals.cpp, which was never committed in any ref (author's
  local-only file). Upstream uses a newer AndroidExtensions pin and needs no patch.
- build.gradle: bump default ndkVersion 23.1.7779620 -> 28.2.13676358 (matches CI's
  NDK_VERSION). NDK 23's libc++ can't compile googletest 1.17.0's <=> usage. Also map the
  Android sanitizer flag JSR_ENABLE_ASAN -> ENABLE_SANITIZERS (the upstream option kept
  during the rebase).
- Tests/NodeApi/CMakeLists.txt: use ${JsRuntimeHost_SOURCE_DIR} instead of
  ${CMAKE_SOURCE_DIR} for Core/Node-API include paths. On Android JsRuntimeHost is added
  as a subdirectory of the app, so CMAKE_SOURCE_DIR was the app dir (headers not found);
  the project-scoped var is correct in both standalone (macOS) and nested (Android) builds.
- Tests/NodeApi/CMakeLists.txt: allow the .node modules to link with unresolved napi_*
  symbols on Android (-Wl,--unresolved-symbols=ignore-all), the ELF equivalent of Apple's
  -undefined dynamic_lookup; they bind at dlopen time from the host (UnitTestsJNI).
- Shared.cpp: gate the Android NodeApi-harness block on NODE_API_AVAILABLE_NATIVE_TESTS
  (defined only by UnitTestsJNI) so the standalone UnitTests target -- built but unused on
  Android -- doesn't try to compile AndroidExtensions/NodeApi code it doesn't link.
The instrumented run aborted with 'use of deleted global reference': the harness fell
back to android::global::GetAppContext() (GetFilesDir -> GetObjectClass) whose JNI global
ref is not valid during the instrumented run. JNI.cpp now computes a writable base dir from
the still-valid instrumentation Context and passes it plus the native AAssetManager to
SetNodeApiTestEnvironment() before RunTests() -- the wiring the harness was designed for
(see e1fce6b) but which was never actually connected.

This removes the crash and lets ConfigureNodeApiTests run. NOTE: on-device execution of the
NodeApi conformance tests is still not achieved -- CopyAssetsRecursive relies on
AAssetManager subdirectory enumeration (AAssetDir_getNextFileName lists files only, not
dirs) so the nested test tree isn't copied, and the native .node modules are neither
packaged nor loadable from an app-writable dir on API 29+. Tracked as follow-up.
Before this, the instrumented run passed vacuously -- no NodeApi tests ran. Several
layered fixes get them executing on the emulator (macOS path unchanged: still 12/12):

#1 Asset enumeration: AAssetManager can't list subdirectories, so CopyAssetsRecursive
   copied nothing. copyNodeApiTests now emits a file manifest (manifest.txt -- not a
   dotfile, which aapt would drop) and Shared.cpp copies each listed file.

#2 Native module packaging/loading: build each addon as lib<name>.so on Android so AGP
   packages it into lib/<abi>/ (nativeLibraryDir, the only dlopen-able location on API
   29+); node_lite_android loads it by soname; ResolveModulePath resolves the (on-disk
   absent) .node so LoadNativeModule runs.

V8 lifecycle (in-process node_lite shares the host's V8): reuse the host's already-
   initialized V8 platform (fixes 'Wrong initialization order'); hold a Locker +
   Isolate::Scope so multi-isolate access is locked (fixes 'Entering the V8 API without
   proper locking').

KNOWN REMAINING (tracked): node_lite calls Node-API outside any napi callback during
   NodeLiteRuntime::Initialize/script execution, which on V8 needs a live HandleScope +
   current Context. v8::HandleScope/Context::Scope are stack-only (operator new is
   private) so they can't be held across the holder; this needs a scope-wrapping rework
   of node_lite's V8 entry points (or napi_open_handle_scope + context enter). Until then
   the on-device native tests segfault in napi_create_object.
…eate_object segfault)

NodeApiEnvScope -> jsr_open_napi_env_scope was a no-op stub: it allocated a scope
struct but never entered the env's V8 isolate/context. On JSC that's fine (the env
carries its context explicitly), but on V8 node_lite then calls Node-API outside any
napi callback with no *current context*, so napi_create_object -> v8::Object::New(isolate)
segfaulted during NodeLiteRuntime::Initialize. Enter the env's context on open and exit
it on close (Android only). The in-process V8 runtime now initializes and runs tests.
… error

Step toward in-process error handling: ExitOnException was noexcept, but the in-process
runner installs a fatal handler that throws NodeLiteFatalError (rather than std::exit) so
the harness can turn a JS error into a ProcessResult. Throwing from the noexcept function
std::terminate'd the test process. Dropped noexcept so it propagates to RunNodeLiteScript.

(Partial: other noexcept teardown paths -- NodeApiHandleScope/NodeApiEnvScope dtors calling
NODE_LITE_CALL, and the env-holder dtor's onUnhandledError -> ExitWithJSError -- can still
throw during unwinding when a test errors. Full in-process error-path exception-safety is
the remaining Android item.)
NodeApiHandleScope/NodeApiEnvScope destructors used the throwing NODE_LITE_CALL, and the
JsRuntimeHostEnvHolder destructor's onUnhandledError can invoke the throwing in-process fatal
handler -- both std::terminate if they fire while a NodeLiteFatalError is unwinding. Make the
scope dtors ignore the close status and wrap onUnhandledError in try/catch.

Correct robustness fixes, but they do NOT yet resolve the remaining in-process failure: when a
test errors, a *second* NodeLiteFatalError is thrown during unwinding (double-exception ->
std::terminate). The escaping throw site isn't visible in the tombstone (stack already unwound)
and needs on-device lldb to pinpoint. macOS unaffected (12/12).
…winding

Don't re-throw NodeLiteFatalError from the in-process fatal handler when std::uncaught_exceptions()
> 0, to avoid a double-exception std::terminate. (Correct hardening, but the remaining in-process
abort is a *single* uncaught NodeLiteFatalError escaping RunNodeLiteScript's catch -- a scope-exit
destructor throw on a test that leaves a pending exception; needs on-device lldb to pinpoint.)
…(fixes terminate)

THE fix for the in-process abort. ExitWithJSError / ExitWithJSAssertError / ExitWithMessage
were declared noexcept. With the default fatal handler they call std::exit (never throw), but
the in-process runner installs a handler that *throws* NodeLiteFatalError (caught by
RunNodeLiteScript and turned into a ProcessResult). A throw crossing a noexcept boundary is an
immediate std::terminate -- so when any test errored (e.g. the expected-error basics tests
throw_string/mustcall_failure), the whole instrumented run aborted instead of reporting a
result. Removing noexcept lets the throw unwind to the catch. Confirmed on the emulator via a
temporary _Unwind_Backtrace probe (now removed): the throw stack was
HandleFatalError <- ExitWithMessage(noexcept!) <- ExitWithJSError <- RunTestScript <- RunNodeLiteScript.

Net effect: the in-process Android run no longer aborts; the js-native-api v5 tests (2-5) pass;
the remaining failures are the basics harness self-tests, run through the generic fixture rather
than the specialized test_basics.cpp path macOS uses. macOS unaffected (still 12/12).
… napi

The js-native-api conformance addons are dlopen'd in-process by the Android
harness and import napi_* from the host (libUnitTestsJNI.so). The host is
loaded RTLD_LOCAL by System.loadLibrary, and bionic's linker-namespace model
does not surface its statically-linked (but exported) napi_* symbols to a
dlopen'd module -- so the addon cannot bind them at load time. Post-hoc
RTLD_GLOBAL promotion of the host is a no-op on bionic (confirmed on device:
the module dlopen still returns NULL with the host re-opened RTLD_GLOBAL).

Making these tests runnable on Android requires building napi as a shared
library (libnapi.so) depended on by both the host and the addons -- a
packaging change affecting every Android consumer, deferred to a separate
change per the v5-suite-in-place scope. Until then, skip the in-process addon
tests on Android with a clear reason; macOS runs the full v5 addon suite
(12/12) as the reference.

This unblocks the Android suite: it now builds, the in-process harness runs
without aborting, and the suite passes (addon tests reported SKIPPED).
… in-process)

Dynamic .node loading is never shipped to the Play / Quest stores, and bionic won't resolve a
dlopen'd addon's napi_* imports against the System.loadLibrary-loaded host anyway (the addon carries
no DT_NEEDED for napi; RTLD_GLOBAL host promotion is a no-op on bionic). Rather than make napi a
shared library for every Android consumer (tracked separately, task #9), compile the conformance
addons directly into the host (UnitTestsJNI) and resolve them in-process.

To link several addons into one binary without symbol clashes:
- node_api.h: make NODE_API_MODULE_REGISTER_FUNCTION / _GET_API_VERSION_FUNCTION overridable.
- entry_point.h (JSR_NODE_API_STATIC_LINK): give Init internal linkage and emit a per-addon load-time
  constructor that self-registers its uniquely-suffixed registrar/version functions with the host.
- The Android CMakeLists compiles each addon as an OBJECT library with per-module unique entry-point
  names and links them into UnitTestsJNI.
- node_lite_android LoadFunction resolves entry points from the in-process static registry by module
  name instead of dlopen+dlsym.

Removes the Android GTEST_SKIP. The 4 v5 js-native-api conformance tests now execute in-process and
PASS on Android (2_function_arguments, 3_callbacks, 4_object_factory, 5_function_factory). macOS is
unchanged (the desktop dynamic .node path uses the #else branches).
The conformance suite runs gtest in-process; its results (RUN/OK/FAILED and failure
file:line:message) went to stdout, which Android discards -- leaving only the JUnit "expected 0,
was 1" with no detail. Pump stdout/stderr to logcat (tag NodeApiTests) so test output and any
pre-crash native context are visible via `adb logcat -s NodeApiTests`.
…by static linking)

The conformance addons are statically linked into the in-process Android test host (f32130e), so the
standalone SHARED .so + -Wl,--unresolved-symbols=ignore-all + lib<name>.so naming that the old
dlopen-on-Android path needed are dead. add_node_api_module now early-returns on Android and is a
clean desktop-only MODULE .node helper. Also fixes a stale node_lite comment describing the
abandoned soname-dlopen path.

No functional change on desktop (MODULE .node, -undefined dynamic_lookup, POST_BUILD staging,
codesign all preserved); macOS still 12/12.
V8Platform::EnsureInitialized() became a no-op once we found the host AppRuntime already initializes
V8's process-global platform; the class and its unused init_flag_/platform_ members were left over
from the abandoned platform-init attempt. Fold the (still-important) "don't re-init the platform"
rationale into a comment at the isolate-creation site, and drop the dead class plus the now-unused
<mutex> / <libplatform> includes. No behavior change; Android still 4/4 js-native-api.
…d libnapi.so

Replaces the interim static-link-into-host approach with the dynamic .node model used by
nodejs/node-api-cts (add_node_api_cts_addon), so the Android suite and a future node-api-cts
migration share one addon model.

- Core/Node-API: build napi as a SHARED library (libnapi.so) on Android. It exports all 106 napi_*
  (default visibility -- no global -fvisibility=hidden), and the host plus every addon depend on the
  one libnapi.so via a real DT_NEEDED, so there is a single napi instance. Static elsewhere.
- The conformance addons are again standalone SHARED lib<name>.so (packaged into nativeLibraryDir),
  now linking napi (DT_NEEDED libnapi.so) instead of -Wl,--unresolved-symbols=ignore-all.
- node_lite_android resolves entry points via dlopen(soname)+dlsym again; the addon's napi_* bind
  from libnapi.so at load. Reverts the static-link infra (entry_point.h JSR_NODE_API_STATIC_LINK
  branch, node_api.h overridable registrar macros, the host's per-addon OBJECT libraries).

Verified on device: lib2_function_arguments.so has DT_NEEDED [libnapi.so], its napi_* are imports,
libnapi.so exports the 106 napi_*, and all 4 v5 js-native-api tests pass in-process. macOS unchanged
(12/12; desktop keeps static napi + dlopen'd MODULE .node).
The V8JSI shim let facebook::jsi::JSError escape from evaluateJavaScript
unconverted. Every other engine throws Napi::Error for a script
exception, and AppRuntime's dispatch treats anything else as fatal, so a
`throw` reaching a dispatched Eval on JSI aborted the process (exit 3).
Convert JSError to Napi::Error carrying the thrown value, and other JSI
exceptions to a Napi::Error with their message.

(cherry picked from commit d8ffd6a)
The conflict resolution had spliced the new test (and a stray
RunTests() context line) into the middle of
PrimitiveExceptionSurvivesNativeCatch.
FileReader::Dispatch copied the listeners into a std::vector of bare
Napi::Function values before calling them. A listener that removes a
later listener drops the only strong reference to it, and on
JavaScriptCore nothing else roots a napi_value that lives on the C++
heap (its handle scopes are stubs; only the C stack is scanned), so the
later function could be collected before the loop reached it. Snapshot
FunctionReferences instead, released when dispatch returns.

(cherry picked from commit c712eda)
The XMLHttpRequest and fetch suites fetched https://github.com/ and a
mesh from raw.githubusercontent.com to check status codes, statusText,
percent-encoding of non-ASCII paths and abort-in-flight. Any hiccup on
those hosts (or a runner without outbound network) failed the run, as
the 504s on the recent upstream reruns showed.

The test host now starts a small HTTP/1.1 server on 127.0.0.1 with an
ephemeral port and publishes its origin to the scripts as the
`hostTestServer` global. It answers "/" with 200, unknown paths with
404 "Not Found", "/assets/<name>" with 200 only when the request-target
decodes to the expected UTF-8 file name (so the client's own encoding
is what gets checked), and "/delay/<ms>" after a delay so the abort
tests are guaranteed to abort a request that is still in flight. The
connection-refused tests keep using 127.0.0.1:1.

Plain sockets on every platform (Winsock on Windows, poll-driven accept
loop, MSG_NOSIGNAL/SO_NOSIGPIPE for clients that go away mid-request);
the Android test library links it too.

(cherry picked from commit 6945119)
…ready provides the target; include <cstdlib> for std::abort (BabylonJS#250)
WorkerOptions is a WebIDL dictionary, so a member set to undefined is
the same as leaving it out. The constructor stringified the value
instead and rejected "undefined" with the TypeError meant for a bogus
type, which is what a caller spreading optional options into
`new Worker(url, { type })` hit. Only a present, non-undefined value is
validated now. Regression test: Worker.UndefinedTypeMeansClassic.

(cherry picked from commit c9df287)
…eave a dump

The WER LocalDumps registration produced no .dmp on the runner image for
a plain access violation, so the "crash dumps" artifact only ever held
the exe and pdb. ProcDump installed as the AeDebug handler writes a full
dump for any crashing process regardless of WER's policy; the existing
upload step picks it up from the same folder.

(cherry picked from commit a9ae185)
…thods writable

Blob::TryGetData compared an object's prototype chain against whatever
the global `Blob` binding held at call time. A script that replaces the
global Blob with its own class (the BabylonNative validation harness
does, for image loading) made every instance of that class look like
one of ours, and Unwrap on an object that wraps nothing threw a bare
"Invalid argument" out of URL.createObjectURL. The constructor is now
kept on the runtime's native object at Initialize and checked against
that, and a null Unwrap yields "not a Blob" like any other foreign
object.

The harness also assigns its own URL.createObjectURL; napi_default
makes static methods read-only so that assignment failed silently and
the native method kept receiving the foreign Blob. WebIDL static
operations are writable, enumerable and configurable, so declare the
URL statics that way.

Found by the SOG Gaussian-splat scenes of the Dawn validation catalog
("Failed to parse SOG zip data" with cause "Invalid argument"), which
pass again with this change.
…ime native object (workers have no JsRuntime)
…alue

Native code throws Napi::Error, a C++ exception carrying the JavaScript
error object. JSI reports any std::exception that escapes a host
function as a fresh Error("Exception in HostFunction: " + what()), so
on the JSI backend a polyfill's TypeError reached scripts as a plain
Error with a prefixed message: `instanceof TypeError` was false and
messages no longer compared equal, unlike every other backend.

Route every host-function trampoline (Function::New callbacks, class
constructors, static and instance methods and accessors) through a
helper that catches Napi::Error and rethrows jsi::JSError with the
original value, the same conversion Napi::Eval already does in the
other direction (BabylonJS#246).

Regression test: "native exceptions reach scripts as the thrown error
object, with its class and message, on every engine".

(cherry picked from commit 94e20fc)
JS-to-native callbacks can report an error either by throwing Napi::Error or by calling ThrowAsJavaScriptException, which records an exception in the environment. The JSI trampoline converted only the first form, allowing pending exceptions to escape their JavaScript call site and surface later as uncaught dispatcher failures.\n\nCheck and clear the pending slot before every JSI host callback returns, then throw its original JavaScript value through JSI. Add a reentrant callback regression that exercises the pending-exception form without depending on a particular polyfill.
Move dispatch state behind shared synchronized storage so a JsRuntimeScheduler can outlive its JavaScript environment without dereferencing the deleted JsRuntime. Teardown closes the state before the host runtime exits, and late native completions become no-ops.\n\nCover both post-teardown dispatch and concurrent dispatch during AppRuntime destruction.
… QoS class

Apple silicon offers no public core-affinity API; the scheduler places
threads by QoS class, with background QoS confined to the efficiency
cluster. Clamping the whole process (taskpolicy -c background) also
throttles the host's frame timer, so a Playground run under that clamp
drops to ~10 fps with the process 80% idle and the frame rate stops
being a CPU measurement.

With this diagnostic environment variable only the JavaScript thread
takes the QoS class: JSRUNTIMEHOST_APPRUNTIME_QOS=background put 96% of
a splat scene's samples on the four E cores of an M4 Max while the app
kept 54 fps, which is the "run the script side on efficiency cores"
experiment the mobile-bottleneck triage wants. utility is accepted too
(it still spills onto performance cores). Apple platforms only.
Carry the reviewed PR BabylonJS#248 shared listener records and regression coverage into the polyfill integration stack. Removed listeners are skipped, new registrations are deferred, and snapshots retain their callbacks during nested dispatch.

Source: BabylonJS#248, bead2d6.
Keep primitive-throw coverage specific to JSI until the independent JavaScriptCore fix lands. Cover all supported primitive kinds and preserve Error object identity.

Capture test promises by shared ownership, bound completion waits, and copy unexpected exception messages into plain C++ errors on the runtime thread. Apply the repository formatter to the JSI Eval implementation.
The trace callback receives const char*, but NSLog was given %@, which interprets that pointer as an Objective-C object. Use %s for the C string on both macOS and iOS so diagnostic tracing does not send messages to an invalid object.

Both entry points compile with -Werror=format and emit the expected sentinel through the real DebugTrace callback in a macOS harness.
Share synchronized dispatch state with scheduler copies, close it before AppRuntime discards pending work, and release dispatch captures outside the lock. Preserve existing callable overload behavior while accepting mutable and environment-taking callbacks. Add bounded native regression tests with owned timeout state.

Adapted from integration commits by Matt Hargett:

Source-commit: 7093c83
Source-commit: 0a5d570
V8JSI 0.64.33 reconstructs JSError from message text before Napi::Eval receives it, so original JavaScript object identity is not provided by that adapter. Keep the real Eval message, global visibility, and recovery checks independent, with original-object identity checked on the other engines.

Add JSI runtime-decorator tests for forwarding Error/plain-object identity, wrapping actual primitive JSError values, and converting native JSI exceptions. Retain end-to-end primitive coverage and document the narrower object-preservation guarantee.
The pinned Windows NuGet package does not ship jsi/decorator.h. Move the existing conversion into a private helper used by Eval and test it with original JSError values on a real V8JSI runtime, without a RuntimeDecorator dependency. Keep end-to-end Eval catch and recovery coverage separately because this adapter reconstructs thrown values before delivering them to Node-API.

Validated the JSI source and tests with the headers actually present in ReactNative.V8Jsi.Windows 0.64.33; the macOS JSC suite passes with continuous collection. Windows JSI execution is validated by the fork CI job.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants