Skip to content

Add native fetch() polyfill#188

Open
bkaradzic-microsoft wants to merge 5 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:weekend/tpc-1581-fetch-native
Open

Add native fetch() polyfill#188
bkaradzic-microsoft wants to merge 5 commits into
BabylonJS:mainfrom
bkaradzic-microsoft:weekend/tpc-1581-fetch-native

Conversation

@bkaradzic-microsoft
Copy link
Copy Markdown
Contributor

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.

Implements the WHATWG fetch() API as a native polyfill under
Polyfills/Fetch/, mirroring the XMLHttpRequest polyfill layout. Like
XMLHttpRequest, it is built on top of the platform-specific transports
in UrlLib, so libcurl/WinHTTP/etc. behavior is identical between the two.

fetch(input, init) returns a Promise that resolves to a Response-like
object exposing ok/status/statusText/url/redirected/type/bodyUsed, a
case-insensitive headers accessor (get/has/forEach), the body accessors
text()/arrayBuffer()/json()/blob(), and clone(). Per the fetch spec, the
promise only rejects 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 (a deliberate lenient deviation
from the spec's single-use semantics). Methods are limited to GET/POST
and request bodies to strings, matching the underlying UrlLib transport.

Wired behind the JSRUNTIMEHOST_POLYFILL_FETCH option (UrlLib is now
fetched when either XMLHttpRequest or Fetch is enabled) and covered by
new unit tests.

Closes BabylonJS#98.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 6, 2026 00:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a native fetch() polyfill implemented in C++ on top of the existing UrlLib transports (matching the XMLHttpRequest approach), wires it behind a new CMake option, and extends the unit test suite to validate core fetch/Response behaviors.

Changes:

  • Introduce Polyfills/Fetch library implementing fetch(input, init) and a minimal Response/Headers surface.
  • Wire JSRUNTIMEHOST_POLYFILL_FETCH (default ON) and ensure UrlLib is fetched when either XHR or Fetch is enabled.
  • Add Mocha unit tests and a JSON asset to validate response properties and body readers (text/arrayBuffer/json/blob) plus cloning and header behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tests/UnitTests/Shared/Shared.cpp Initializes the new Fetch polyfill in the UnitTests runtime.
Tests/UnitTests/Scripts/tests.ts Adds a new describe("fetch") suite covering status/ok, statusText, body readers, headers, clone, init.method, and no-arg rejection.
Tests/UnitTests/CMakeLists.txt Links the new Fetch target into the UnitTests executable; Assets are globbed/copied so sample.json is picked up.
Tests/UnitTests/Assets/sample.json Adds deterministic JSON payload for the response.json() test.
Polyfills/Fetch/Source/Fetch.h Declares internal Fetch initializer.
Polyfills/Fetch/Source/Fetch.cpp Implements fetch(), Response-like object, and headers helpers over UrlLib.
Polyfills/Fetch/Readme.md Documents supported surface area and known limitations.
Polyfills/Fetch/Include/Babylon/Polyfills/Fetch.h Adds public API header for initializing the polyfill.
Polyfills/Fetch/CMakeLists.txt Defines the Fetch library target and links against JsRuntime, arcana, and UrlLib.
Polyfills/CMakeLists.txt Adds conditional inclusion of the Fetch subdirectory.
CMakeLists.txt Adds JSRUNTIMEHOST_POLYFILL_FETCH option and updates UrlLib dependency gating.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Polyfills/Fetch/Source/Fetch.cpp
Comment thread Polyfills/Fetch/Source/Fetch.cpp
- blob() now detects the Blob polyfill via IsUndefined()/IsNull() instead
  of IsFunction(). Some JavaScriptCore/JSI builds classify constructor
  functions as typeof 'object', so IsFunction() incorrectly rejected even
  when the Blob polyfill was installed, failing the blob() test across all
  JSC/JSI CI jobs (matches the existing File polyfill workaround).
- Add <cctype> for std::tolower/std::toupper used by ToLower/StatusText,
  which were relying on transitive includes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bkaradzic-microsoft
Copy link
Copy Markdown
Contributor Author

Addressed both review comments in ebe6a80:

  • blob() on JSC/JSI: switched the Blob-availability check from IsFunction() to IsUndefined()/IsNull(), matching the existing File polyfill workaround. Some JavaScriptCore/JSI builds report constructor functions as typeof 'object', which was causing the \�lob()\ test to fail across all JSC/JSI CI jobs.
  • : added the explicit include for \std::tolower/\std::toupper.

This should turn the failing JSC/JSI/Android/Ubuntu/macOS/iOS builds green (they were all failing solely on the blob() test).

bkaradzic and others added 3 commits June 5, 2026 17:27
arcana's task::then() captures the scheduler by reference and invokes it
on the UrlLib worker thread when the request completes, which happens after
the synchronous fetch() call has already returned. The previous stack-local
JsRuntimeScheduler was therefore destroyed before the continuation ran,
leaving arcana with a dangling reference. On Windows/Chakra this happened to
survive, but on clang/libc++, JSC, JSI and Android it dereferenced freed
memory and aborted with 'std::system_error: Invalid argument' inside
JsRuntime::Dispatch (caught by ASAN as a stack-use-after-return).

Heap-allocate the scheduler in a shared_ptr and co-own it from the
continuation callable so it stays alive until the request completes,
mirroring how XMLHttpRequest keeps its scheduler alive as a member.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Node-API-JSI declares Napi::Object::Set / Napi::Array::Set as non-const
member functions, so calling Set() on 'const auto' instances failed to
compile (C2663) on the *_JSI and Android targets while compiling fine
under Chakra/V8. Declare the blob() 'parts' array and 'options' object
as non-const, matching the other builder objects in this file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Android UnitTests app uses its own CMakeLists with an explicit polyfill
link list, separate from the desktop Tests/UnitTests target. Without Fetch
there, Shared.cpp failed to find <Babylon/Polyfills/Fetch.h> on Android_JSC
and Android_V8. Add 'PRIVATE Fetch' to mirror the desktop test target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bkaradzic-microsoft
Copy link
Copy Markdown
Contributor Author

CI is now fully green (23/23 ✅). Pushed four follow-up fixes after the initial submission, all surfaced by platform-specific CI:

  1. *\40f5ff7* — Fixed a stack-use-after-return crash (\std::system_error: Invalid argument\ on clang/libc++, JSC, JSI, Android). arcana's \ ask::then()\ captures the scheduler by reference and runs it on the UrlLib worker thread after \ etch()\ has returned, so the stack-local \JsRuntimeScheduler\ dangled. Now heap-allocated and co-owned by the continuation (mirrors how XMLHttpRequest keeps its scheduler alive as a member). Caught by ASAN.
  2. *\�be6a80* — \�lob()\ now detects the Blob polyfill via \IsUndefined()/IsNull()\ instead of \IsFunction()\ (JSC/JSI report constructors as typeof 'object'), plus added <cctype>. (addresses both Copilot review comments)
  3. *\44bc77a* — Made the \�lob()\ \parts/\options\ Napi objects non-const (Node-API-JSI declares \Set()\ non-const → C2663).
  4. *\1118799* — Linked \Fetch\ into the Android \UnitTestsJNI\ target, which has its own polyfill link list.

Ready for re-review.

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.

Implement fetch API

3 participants