Skip to content

feat: implement Response.clone()#312

Open
godronus wants to merge 1 commit into
bytecodealliance:mainfrom
godronus:feature/response-clone
Open

feat: implement Response.clone()#312
godronus wants to merge 1 commit into
bytecodealliance:mainfrom
godronus:feature/response-clone

Conversation

@godronus

@godronus godronus commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Implement Response.clone() (WHATWG Fetch)

Implements Response.clone() per the WHATWG Fetch spec. All 21 WPT tests in
fetch/api/response/response-clone.any.js pass.

Approach

Response bodies are modelled as default (non-byte) ReadableStreams, whose tee shares a single
chunk object between both branches. Fetch's "clone a body" requires the clone to receive independent
copies, so Response::clone implements the WHATWG ReadableStreamDefaultTee algorithm with
cloneForBranch2 = true:

  • A single reader drains the source body stream.
  • Each chunk is enqueued unchanged into branch1 (the original response's body), preserving
    object identity, and a JSAutoStructuredCloneBuffer copy is enqueued into branch2 (the clone's
    body).
  • The copy is made eagerly, in the source read, before either consumer observes the chunk — a
    lazy copy would be read-order dependent and would duplicate already-mutated bytes if the original
    branch is read and mutated in place first.

Cancellation is tracked per branch (canceled1/canceled2): a cancelled branch is dropped from the
read loop while the other continues.

sc_callbacks is not needed — body chunks are TypedArray/ArrayBuffer/DataView, handled by the
built-in structured clone.

Design notes

  • Cost: one structured-clone copy per chunk, clone branch only; branch1 is zero-copy. Same copy
    count as the spec byte-stream tee. highWaterMark is 0 and chunks are pulled on demand, so the
    body is never fully buffered.
  • Byte streams: modelling bodies as byte streams would let the native byte-stream tee provide
    this directly (CloneAsUint8Array, no hand-rolled tee), but that is a large change across all
    body I/O and is out of scope here.
  • Request::clone currently tees without copying branch2; aligning it is a reasonable
    follow-up.

Changes

builtins/web/fetch/request-response.h

  • Added Response::clone declaration.

builtins/web/fetch/request-response.cpp

  • Added #include "js/StructuredClone.h".
  • Request::bodyUsed_get / Response::bodyUsed_get: fall back to JS::ReadableStreamIsDisturbed
    when the BodyUsed slot is false, so bodyUsed reports true after body.cancel() and other
    external stream disturbances.
  • Added the tee implementation: CloneTeeSlot (shared-state layout), clone_tee_pump,
    clone_tee_read_then_handler, clone_tee_read_catch_handler, clone_tee_cancel_algorithm,
    clone_tee_pull_algorithm.
  • Implemented Response::clone: acquires a default reader on the body, creates two
    NativeStreamSource branches over shared tee state, and assigns branch1 to the original response
    and branch2 to the clone.
  • Registered JS_FN("clone", Response::clone, 0, JSPROP_ENUMERATE).

tests/wpt-harness/expectations/fetch/api/response/response-clone.any.js.json

  • New file — 21/21 passing.

WPT results

fetch/api/response/response-clone.any.js — 21/21 pass.

Side-effect fixes from the ReadableStreamIsDisturbed fallback in bodyUsed_get:

  • fetch/api/response/response-stream-disturbed-6.any.js — 5 FAIL → PASS (partially resolves Stream disturbed test failures #184)
  • fetch/api/response/response-stream-disturbed-by-pipe.any.js — 2 FAIL → PASS
  • fetch/api/request/request-disturbed.any.js — 1 FAIL → PASS
  • fetch/api/abort/general.any.js — 2 FAIL → PASS

fetch/api/body/mime-type.any.js — new expectations file (6 PASS, 14 FAIL). The file previously
aborted during initialisation on a top-level clone() that threw TypeError; it now loads, and the
14 failures expose a pre-existing gap where parse_body<Blob> does not populate blob.type from
the Content-Type header. Tracked in #311.

Downstream testing: PASSING

https://github.com/G-Core/FastEdge-sdk-js/blob/main/integration-tests/test-application/handlers/response-clone.ts

@godronus godronus force-pushed the feature/response-clone branch from f11e19c to 1c3a6cf Compare June 8, 2026 13:39
@godronus godronus marked this pull request as draft June 8, 2026 17:31
@godronus godronus force-pushed the feature/response-clone branch 2 times, most recently from 90647e3 to c28ec93 Compare June 9, 2026 12:29
… tee

Fix Headers cloning bug -- re: Immutable.
@godronus godronus force-pushed the feature/response-clone branch from c28ec93 to 43436f3 Compare June 9, 2026 16:19
@godronus godronus marked this pull request as ready for review June 9, 2026 16:42
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.

Stream disturbed test failures

1 participant