Uh oh!
There was an error while loading. Please reload this page.
Add File / FileReader polyfill - #169
Conversation
There was a problem hiding this comment.
Pull request overview
This PR moves File / FileReader support from a BabylonNative Playground-only JS shim into JsRuntimeHost as a native C++ polyfill, so all JsRuntimeHost consumers get the WHATWG-style APIs alongside existing polyfills (Blob, URL, XHR, etc.).
Changes:
- Add a new
Polyfills/File/C++ target implementingFileandFileReader(including event handler slots + EventTarget-style listeners). - Wire the polyfill into the build via a new
JSRUNTIMEHOST_POLYFILL_FILECMake option (ON by default) and link it into unit tests. - Add new Mocha unit tests covering
FileandFileReaderbehaviors (construction, reads, events, abort, and a JSC regression aroundObject.prototypepollution).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| CMakeLists.txt | Adds JSRUNTIMEHOST_POLYFILL_FILE option (ON by default). |
| Polyfills/CMakeLists.txt | Conditionally includes the new File polyfill subdirectory. |
| Polyfills/File/CMakeLists.txt | Defines the File polyfill library target and its sources. |
| Polyfills/File/Include/Babylon/Polyfills/File.h | Public API entrypoint for initializing the polyfill. |
| Polyfills/File/Readme.md | Documents behavior and prerequisites (Blob must be initialized first). |
| Polyfills/File/Source/File.h | Declares the internal File ObjectWrap implementation. |
| Polyfills/File/Source/File.cpp | Implements File over the existing Blob polyfill and initializes FileReader. |
| Polyfills/File/Source/FileReader.h | Declares the internal FileReader ObjectWrap implementation. |
| Polyfills/File/Source/FileReader.cpp | Implements async reads, base64 DataURL generation, events, and abort logic. |
| Tests/UnitTests/CMakeLists.txt | Links the new File target into the UnitTests executable. |
| Tests/UnitTests/Shared/Shared.cpp | Initializes the File polyfill in the unit test JS environment. |
| Tests/UnitTests/Scripts/tests.ts | Adds new Mocha tests for File and FileReader, including a JSC regression canary. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
6f8a827 to
67f9f5cComparefa84e76 to
52f6338Comparebkaradzic-microsoft
commented
Jun 1, 2026
@bghgary friendly ping — this is the upstream half of BabylonJS/BabylonNative#1706 (File / FileReader polyfill, which re-enables 19 GLTF/OBJ serializer tests in BN). Same flow as #171 (TextEncoder) which you helped me close out earlier today: the polyfill was moved here from the BN PR per your review ("this should be a proper C++ polyfill under State just refreshed:
Once this lands I'll bump BN #1706's pin back to |
ryantrem
left a comment
There was a problem hiding this comment.
Similar to my comment on the other PR, we may want to do a minimal implementation of the File polyfill that is just enough to support BJS usage (not sure if that is already the case).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…_ptr trick Addresses @ryantrem review on BabylonJS#169 (review id 4412098338): * Drop FileReader.readAsBinaryString and its supporting code paths. BJS has zero call sites (searched core, dev/, tools/). Removing it also eliminates the deferred Latin-1/UTF-8 follow-up that previously crashed Chakra during a combined-edit attempt. * Wire File.prototype to inherit from Blob.prototype via Object.setPrototypeOf in File::Initialize. Babylon.js core branches on `instanceof Blob` in fileTools, Offline/database, abstractEngine, and thinNativeEngine; without inheritance those checks silently fail for File inputs and the wrong branch is taken. Internal m_blob composition stays as the implementation detail; only the JS-visible prototype chain is wired. New test asserts `new File(...) instanceof Blob === true`. * Replace shared_ptr<ObjectReference>-in-lambda trick with a member Napi::ObjectReference m_selfRef anchored across the in-flight read. Matches the member-slot pattern used by WebSocket/XHR in this repo. Lambdas now only capture POD + this, so they remain copyable for jsi::Function's std::function-style callable slot. Every terminal path (load, error, abort) resets m_selfRef to break the self-cycle. Each lambda also guards on m_readId before dereferencing m_selfRef to handle the abort-then-late-resolve race.
bkaradzic-microsoft
commented
Jun 2, 2026
@ryantrem on the minimization point — I surveyed Babylon.js usage and the only thing that wasn't already minimal is
Dropping Full set of changes in 4480ad8:
Local Win32 Chakra |
bkaradzic-microsoft
commented
Jun 2, 2026
CI on The prototype-chain wire-up for
Ball back in your court for re-review. |
bghgary
left a comment
There was a problem hiding this comment.
[Reviewed by Copilot on behalf of @bghgary]
Recommend landing #172 first. The JSC napi-shim bug it describes is the root cause of several workarounds in this PR — JS_PROTOTYPE_CHAIN_SHIM collapses to one direct setPrototypeOf call, the Napi::Eval / env.RunScript dispatch goes away, and the 14-line FileReader dual-exposure comment shrinks to one line. Quite a bit of code evaporates before merge if #172 lands first.
Other concerns inline.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
bkaradzic-microsoft
commented
Jun 2, 2026
Filed #174 with the JSC napi shim fix for #172. It replaces the old Once #174 lands, I'll rebase this branch onto main and:
Will respond on the Re: splitting |
bkaradzic-microsoft
commented
Jun 3, 2026
Update: closing #174 without merging — the So #169 keeps its JS-shim wire-up (
|
…abylonJS#169 File.cpp - Drop the misleading BABYLON_POLYFILL_USE_NAPI_JSI_EVAL macro and the __has_include(<napi/env.h>) guard. <napi/env.h> exists on every backend (V8, Chakra, JSC, JSI) and is pulled in transitively via <Babylon/Polyfills/File.h>, so the include guard always succeeded and the macro name implied a JSI-only path that doesn't exist — Napi::Eval is declared on all four backends (the Shared N-API impl in env.cc is a thin wrapper around env.RunScript). Call Napi::Eval directly. - Reorder Initialize: check "already provided" (cheap no-op, common path on platforms with a native File) before probing for Blob. - Throw Napi::Error on missing Blob instead of silently bailing. Consumers that wire up the File polyfill expect it to be installed; silent failures are hard to debug. FileReader.cpp - MakeEvent: replace the dangling "JS polyfill that this C++ implementation replaces" reference (no such JS polyfill exists in this PR) with a one-line description of the actual ProgressEvent contract. - DefineClass: collapse the 14-line dual-StaticValue/InstanceValue comment to one line pointing at JsRH#173. Per BabylonJS#173 this is the correct WHATWG IDL `const` member exposure pattern, not a workaround that needs in-line justification. tests.ts - Re-point the Chakra throw-from-constructor TODO at JsRH#175 (filed today) so the disabled "throws when fewer than 2 arguments are passed" test can be re-enabled atomically when that shim limitation is fixed. No behavior change; pure cleanup. Local Chakra build clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pushed Changes in this commit:
Still to come (separate commits for review-clarity, in priority order):
#172 / #174 update: as noted in the follow-up comment on #174, the proper JSC |
…_ptr trick Addresses @ryantrem review on BabylonJS#169 (review id 4412098338): * Drop FileReader.readAsBinaryString and its supporting code paths. BJS has zero call sites (searched core, dev/, tools/). Removing it also eliminates the deferred Latin-1/UTF-8 follow-up that previously crashed Chakra during a combined-edit attempt. * Wire File.prototype to inherit from Blob.prototype via Object.setPrototypeOf in File::Initialize. Babylon.js core branches on `instanceof Blob` in fileTools, Offline/database, abstractEngine, and thinNativeEngine; without inheritance those checks silently fail for File inputs and the wrong branch is taken. Internal m_blob composition stays as the implementation detail; only the JS-visible prototype chain is wired. New test asserts `new File(...) instanceof Blob === true`. * Replace shared_ptr<ObjectReference>-in-lambda trick with a member Napi::ObjectReference m_selfRef anchored across the in-flight read. Matches the member-slot pattern used by WebSocket/XHR in this repo. Lambdas now only capture POD + this, so they remain copyable for jsi::Function's std::function-style callable slot. Every terminal path (load, error, abort) resets m_selfRef to break the self-cycle. Each lambda also guards on m_readId before dereferencing m_selfRef to handle the abort-then-late-resolve race.
…_ptr trick Addresses @ryantrem review on BabylonJS#169 (review id 4412098338): * Drop FileReader.readAsBinaryString and its supporting code paths. BJS has zero call sites (searched core, dev/, tools/). Removing it also eliminates the deferred Latin-1/UTF-8 follow-up that previously crashed Chakra during a combined-edit attempt. * Wire File.prototype to inherit from Blob.prototype via Object.setPrototypeOf in File::Initialize. Babylon.js core branches on `instanceof Blob` in fileTools, Offline/database, abstractEngine, and thinNativeEngine; without inheritance those checks silently fail for File inputs and the wrong branch is taken. Internal m_blob composition stays as the implementation detail; only the JS-visible prototype chain is wired. New test asserts `new File(...) instanceof Blob === true`. * Replace shared_ptr<ObjectReference>-in-lambda trick with a member Napi::ObjectReference m_selfRef anchored across the in-flight read. Matches the member-slot pattern used by WebSocket/XHR in this repo. Lambdas now only capture POD + this, so they remain copyable for jsi::Function's std::function-style callable slot. Every terminal path (load, error, abort) resets m_selfRef to break the self-cycle. Each lambda also guards on m_readId before dereferencing m_selfRef to handle the abort-then-late-resolve race.
CI failed on JSC engines (Ubuntu_gcc, macOS, iOS, Android_JSC) with
'[Uncaught Error] setPrototypeOf@[native code]' during JS env init.
Root cause: the previous commit called
\Object.setPrototypeOf(func.Get('prototype'), Blob.prototype)\. On JSC,
the napi-defined class's \.prototype\ JS property points to
Object.prototype (per JSObjectMakeConstructor semantics; same quirk the
FileReader constants block documents). setPrototypeOf on Object.prototype
throws TypeError because Object.prototype's [[Prototype]] is immutable.
Fix: instantiate a throwaway File, fetch its real prototype via
Object.getPrototypeOf, and set THAT prototype's prototype to
Blob.prototype. On V8 / Chakra the real prototype is also
func.prototype, so this is correct everywhere. The temp instance is
GC-eligible immediately after.
Each call is guarded with IsExceptionPending + GetAndClearPendingException
so that if a non-Chromium JSC build still rejects setPrototypeOf on the
napi-internal prototype, Initialize stays best-effort: instances won't be
Blob subtypes on that engine but the rest of the polyfill still
installs and the JS env starts cleanly.Commit ac19d77 swapped the direct setPrototypeOf for a temp-instance trick to fix JSC, but that broke Chakra (Win32_x64_Chakra and Win32_x86_Chakra now report `file instanceof Blob === false`). Restore the direct call (which previously passed on V8 and Chakra), verify the chain via a probe instance, and only fall back to the temp-instance approach when the chain isn't wired up. That recovers JSC (where `func.prototype` aliases `Object.prototype`) without regressing the engines where the direct call was already correct.
Commit 9e7b333 (C++ dual-path direct -> temp-instance fallback) passed Chakra but regressed JSC: on JSC, setPrototypeOf on Object.prototype throws TypeError that escapes the napi shim as an [Uncaught Error] instead of being capturable via IsExceptionPending. Move both setPrototypeOf calls into a single JS shim wrapped in try/catch. JS-level try/catch reliably traps the TypeError on every engine, the direct path takes care of V8/Chakra, and the probe path (instanceof Blob check + getPrototypeOf + setPrototypeOf on the real napi-internal prototype) takes care of JSC.
…abylonJS#169 File.cpp - Drop the misleading BABYLON_POLYFILL_USE_NAPI_JSI_EVAL macro and the __has_include(<napi/env.h>) guard. <napi/env.h> exists on every backend (V8, Chakra, JSC, JSI) and is pulled in transitively via <Babylon/Polyfills/File.h>, so the include guard always succeeded and the macro name implied a JSI-only path that doesn't exist — Napi::Eval is declared on all four backends (the Shared N-API impl in env.cc is a thin wrapper around env.RunScript). Call Napi::Eval directly. - Reorder Initialize: check "already provided" (cheap no-op, common path on platforms with a native File) before probing for Blob. - Throw Napi::Error on missing Blob instead of silently bailing. Consumers that wire up the File polyfill expect it to be installed; silent failures are hard to debug. FileReader.cpp - MakeEvent: replace the dangling "JS polyfill that this C++ implementation replaces" reference (no such JS polyfill exists in this PR) with a one-line description of the actual ProgressEvent contract. - DefineClass: collapse the 14-line dual-StaticValue/InstanceValue comment to one line pointing at JsRH#173. Per BabylonJS#173 this is the correct WHATWG IDL `const` member exposure pattern, not a workaround that needs in-line justification. tests.ts - Re-point the Chakra throw-from-constructor TODO at JsRH#175 (filed today) so the disabled "throws when fewer than 2 arguments are passed" test can be re-enabled atomically when that shim limitation is fixed. No behavior change; pure cleanup. Local Chakra build clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…onJS#177 landed BabylonJS#177 fixed the JSC napi shim's Object.prototype pollution by passing the per-class JSClassRef to JSObjectMakeConstructor, so napi-defined classes now get a real per-class .prototype object instead of aliasing the global Object.prototype. That removes the entire reason for the JS_PROTOTYPE_CHAIN_SHIM and the Napi::Eval / try/catch dance: we can wire File.prototype's [[Prototype]] to Blob.prototype with a direct Object.setPrototypeOf call. Drops ~50 lines of explanatory comment + shim + Eval-with-IsExceptionPending guard in File::Initialize down to a 4-line napi call. `file instanceof Blob` regression coverage remains in tests.ts:1564 and BabylonJS#177's `describe("napi class prototype isolation (BabylonJS#172)")` block at tests.ts:1271 (uses Blob — and File extends Blob — to assert that napi-defined classes don't share Object.prototype). Local Chakra build clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ment Per bghgary's review nit: the "BabylonJS#177 fixed JSC .prototype pollution" context is now in main's git history, doesn't need to live in the comment block. Comment now focuses on WHY we wire File→Blob, not on which past bug enabled the wiring to be one-liner. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0bff942 to
fe457e5Comparebkaradzic-microsoft
commented
Jun 4, 2026
Rebased onto current Both asks from your latest review addressed:
Rebase was clean — 8 commits replayed cleanly on top of |
…eAccessor readyState/result/error are WHATWG readonly attributes and the on* slots are EventHandler IDL attributes; both should be prototype accessors, not writable enumerable own data properties stamped onto every instance. - readyState/result/error -> InstanceAccessor getters reading m_readyState / m_result / m_error. State-machine checks now read the C++ members directly, so JS can no longer tamper with them by overwriting the property. - on* handlers -> a single InstanceAccessor get/set pair whose accessor data carries the event type, backed by a Napi::FunctionReference map. Dispatch consults the map instead of the on-prefixed instance property. - Constructor no longer stamps any instance properties. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…impl Per review, avoid carrying a third hand-rolled base64 encoder. Promote the header-only azawadzki/base-n (already used by BabylonNative) into JsRuntimeHost as a FetchContent INTERFACE library, gated on JSRUNTIMEHOST_POLYFILL_FILE, and link it PRIVATE into the File polyfill. base-n's encode_b64 emits unpadded base64, so EncodeBase64 now just delegates to it and appends the RFC 4648 '=' padding for the final partial group. This keeps data: URL output byte-for-byte identical (e.g. "Hello" -> "SGVsbG8="). Pinned to the same commit BabylonNative uses (7573e77c). BN's direct base-n dependency can be dropped as a follow-up once it consumes JRH's. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bkaradzic-microsoft
commented
Jun 4, 2026
Addressed the remaining review asks (pushed
All 18 review threads are now resolved. |
napi_create_reference only accepts heap values (object/function/symbol) on the V8/JSC N-API backends, so storing a primitive string result (readAsText/readAsDataURL) in a Napi::Reference<Value> threw there and the load/loadend events never fired (Chakra's shim tolerated it, which is why this passed locally). Box result/error as properties on a persistent holder object instead; referencing the holder object is valid for every engine while keeping the state C++-owned and tamper-proof. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bkaradzic-microsoft
commented
Jun 4, 2026
CI on Verified green (all FileReader tests pass — 179 passing):
The only red job is |
Uh oh!
There was an error while loading. Please reload this page.
Replace the m_selfRef member self-reference with a shared_ptr<ObjectReference> captured by both promise reactions. The spec requires an in-flight read to keep the FileReader alive even if script drops its reference; this anchors the wrapper externally (owned by the lambdas) for exactly the read's duration. When the promise settles and the engine releases the reactions, the last shared_ptr copy drops and the anchor is released automatically. This drops the member self-cycle and the manual m_selfRef.Reset() on every terminal path, and removes the dead-this race the self-anchor left on the abort-then-drop path: the wrapper is now guaranteed alive whenever a reaction runs, so reading m_readId and calling anchor->Value() are always safe. m_readId is retained solely as the abort/restart guard. Matches the externally-anchored, lambda-owned-state convention used by the WebSocket polyfill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
bkaradzic-microsoft
commented
Jun 4, 2026
@bghgary thanks for the careful review — all of your inline concerns are addressed in 61d9924:
I also refreshed the PR description to match the current implementation (it had drifted): C++-backed readonly accessors for Verified Win32-x64 Chakra Debug green (180 JS + native gtests), and full CI is green across all engines/platforms. PTAL. |
bghgary
left a comment
There was a problem hiding this comment.
[Reviewed by Copilot on behalf of @bghgary]
LGTM. All concerns addressed; the FileReader in-flight lifecycle/UAF fix is verified in 61d9924 — shared_ptr<ObjectReference> anchor captured by both promise reactions, no self-ref member, no manual Reset().
Uh oh!
There was an error while loading. Please reload this page.
### Problem When JsRuntimeHost is embedded via FetchContent in a parent project that already declares its own `base-n` INTERFACE target — e.g. **BabylonNative**, which uses base-n for its Window/Canvas polyfills — configuring fails: ``` CMake Error at .../jsruntimehost-src/CMakeLists.txt:150 (add_library): add_library cannot create target "base-n" because another target with the same name already exists. The existing target is an interface library created in source directory ".../Dependencies". ``` This surfaced now because the merged File / FileReader polyfill (#169) switched its base64 encoder over to the `base-n` dependency, and BabylonNative declares `base-n` before it fetches JsRuntimeHost. ### Fix Guard the `base-n` block with `NOT TARGET base-n` so that when a parent project already provides the target, JsRuntimeHost adopts it instead of redeclaring. This is the standard CMake subproject pattern and mirrors BabylonNative's own `if(NOT TARGET glslang)` guards. Standalone JsRuntimeHost builds are unaffected (no pre-existing target → block runs as before). ### Validation - Standalone `NAPI_JAVASCRIPT_ENGINE=Chakra` configure + `UnitTests` build: green, File polyfill + base-n link as before. - Unblocks BabylonJS/BabylonNative#1706 (the consuming PR that re-pins to merged main). Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings in: * Guard base-n target against parent-project collision (BabylonJS#184) * Add File / FileReader polyfill (BabylonJS#169) * Stop mutating instance [[Prototype]] chain in napi_wrap on Chakra (BabylonJS#179) * Fix .prototype pollution on JSC (BabylonJS#177) * Add TextEncoder polyfill (BabylonJS#171) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Implements the WHATWG etch() API as a **native** polyfill under `Polyfills/Fetch/`, mirroring the `XMLHttpRequest` polyfill layout. Like `XMLHttpRequest`, it is built on the same platform-specific `UrlLib` transports, so libcurl/WinHTTP/etc. behavior is identical between the two. Closes#98. ### API `fetch(input, init)` returns a `Promise` that resolves to a `Response`-like object exposing: - `ok`, `status`, `statusText`, `url`, `redirected`, `type`, `bodyUsed` - `headers` with `get(name)` / `has(name)` / `forEach(cb)` (case-insensitive) - `text()`, `arrayBuffer()`, `json()`, `blob()` (each returns a `Promise`) - `clone()` ### Behavior / deliberate limitations - Per the fetch spec, the promise **rejects only on transport-level failures**; a completed request with a non-2xx status (e.g. 404) still resolves with `ok === false`. - The body is fully buffered before the promise resolves, so the body accessors may be called more than once (`bodyUsed` stays `false`) — a lenient deviation from the spec's single-use semantics. - `GET`/`POST` only and string request bodies only, matching the underlying `UrlLib` transport (same constraints as `XMLHttpRequest`). - `blob()` requires the `Blob` polyfill to be initialized. ### Wiring - New `JSRUNTIMEHOST_POLYFILL_FETCH` option (default `ON`). `UrlLib` is now fetched when **either** `XMLHttpRequest` or `Fetch` is enabled. - Initialized in the test harness and covered by a new `describe("fetch")` block (12 cases: status/ok, 404-resolves, statusText, text/arrayBuffer/json/blob, case-insensitive headers, clone, init method, no-arg rejection). A small `Assets/sample.json` backs the deterministic `json()` test. ### Verification Built the `Fetch` target under `warnings_as_errors` and ran the full UnitTests suite on Win32/Chakra: **192 passing**, including all 12 new fetch cases. ### Context This is step 2 of the fetch native-polyfill pivot discussed in BabylonNative#1707 (reviewers asked fetch to follow the same native pattern as File/FileReader (#169) and AbortController). Once this lands, BabylonNative#1707 becomes a small bump that initializes `Polyfills::Fetch`. --------- Co-authored-by: Branimir Karadzic <branimirkaradzic@gmail.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Branimir Karadžić <branimirk@microsoft.com>
The File polyfill in BabylonNative currently lives as a JS shim (
Apps/Playground/Scripts/file_polyfill.js, ~308 lines) that isLoadScript-ed from the Playground'sAppContext(see BabylonJS/BabylonNative#1706). Per @bghgary's review on that PR, it should live in JsRuntimeHost alongside Blob, URL, WebSocket, XMLHttpRequest, TextDecoder, AbortController, etc., so every JsRuntimeHost consumer -- not only the BabylonNative Playground -- getsFileandFileReader.This PR adds a native C++
Polyfills/File/target that implements the WHATWGFileandFileReaderweb APIs, plus 29 new Mocha unit tests (15 File + 14 FileReader) underTests/UnitTests/Scripts/tests.ts.Surface area
File(parts, name, options)constructor.Fileinstance accessors:size,type,name,lastModified.Fileinstance methods:arrayBuffer(),text(),bytes().File extends Blob: the prototype chain is wired sonew File(...) instanceof Blob === true(Babylon.js core branches oninstanceof Blobin fileTools, Offline/database, abstractEngine, thinNativeEngine).FileReaderwith state constantsEMPTY/LOADING/DONE(exposed both on the constructor and on instances, per WHATWG).FileReaderreadonly attributesreadyState,result,errorand theonloadstart/onprogress/onload/onabort/onerror/onloadendEventHandler attributes.FileReaderevents delivered via both theonXhandler slots andaddEventListener/removeEventListener/dispatchEvent.FileReader.readAsText/readAsArrayBuffer/readAsDataURL/abort.Implementation notes
Filedelegates its byte storage to the existingBlobpolyfill viaenv.Global().Get("Blob"), reusing Blob'sBlobParthandling (ArrayBuffer, typed array, string, Blob).Blob::Initializemust run beforeFile::Initialize.FileReader's readonly attributes (readyState/result/error) and theon*handler slots are backed by C++ state surfaced through prototype accessors, not by writable JS properties, so script can neither overwrite them nor fool the state-machine checks.result/errorare boxed on a persistent holder object rather thanNapi::Reference<Value>slots, becausenapi_create_referencerejects primitive values on the real N-API backends (V8/JSC) andreadAsText/readAsDataURLproduce primitive string results.readAsDataURLbase64-encodes via thebase-ndependency (added as a top-levelFetchContent), matching the encoder already used by other polyfills.FileReaderalive (per spec) by anchoring its JS wrapper in ashared_ptr<Napi::ObjectReference>captured by the promise reactions -- externally owned, released automatically when the promise settles. A monotonic read id letsabort()/ restart invalidate a stale continuation so a late-resolvingarrayBuffer()promise cannot dispatch a phantomloadafter a user-initiated abort.JSRUNTIMEHOST_POLYFILL_FILECMake option is on by default and gates building the target.Notable JSC-specific care
Two pieces of the implementation are shaped specifically to work correctly on JavaScriptCore:
FileReader.EMPTY/LOADING/DONEare registered viaStaticValueandInstanceValuedescriptors inside theDefineClassproperty list, not viafunc.Get("prototype").Set(...)after class creation. On JSC the napi shim'sConstructorInfo::Createdefaults the constructor's.prototypetoObject.prototype, so the latter pattern would polluteObject.prototypewithEMPTY/LOADING/DONEkeys and breakfor..inover plain objects throughout the runtime. The regression test"does not pollute Object.prototype with EMPTY/LOADING/DONE"pins this behaviour.The Blob-dependency guard in
File::InitializeusesIsUndefined()rather thanIsFunction(). Some JSC builds (notablylibjavascriptcoregtk-4.1on Linux) classify constructors created viaJSObjectMakeConstructorastypeof 'object', not'function', sonapi_typeofreturnsnapi_objectfor them and anIsFunction()guard would silently early-return on those engines.IsUndefined()matches the guard used by Blob and works on V8, JSI, Chakra, iOS-JSC, Android-JSC, and Linux JSC.Testing
Locally
UnitTests.exe(Win32 x64 Chakra Debug) passes all 180 JS tests (151 pre-existing + 29 new) and the native gtests, including theObject.prototype-pollution regression canary and the abort / late-resolve continuation guards. CI is green across all engines/platforms (V8, Chakra, JSI/JSC, iOS, Android, UWP, macOS, Ubuntu, sanitizers).After this merges
BabylonJS/BabylonNative#1706re-pinsCMakeLists.txt'sGIT_REPOSITORY/GIT_TAGto the merged SHA and removes the JS shim.