Skip to content

fix(chromium): allow explicit response.body() to re-fetch any evicted GET - #42863

Closed
Evan Rusackas (rusackas) wants to merge 1 commit into
microsoft:mainfrom
rusackas:fix/response-body-explicit-refetch
Closed

Evan Rusackas (rusackas) wants to merge 1 commit into
microsoft:mainfrom
rusackas:fix/response-body-explicit-refetch

Conversation

@rusackas

Copy link
Copy Markdown

Summary

response.body() for a page-level Response can return an empty buffer once the page's own JS has already consumed the same fetch() response (e.g. via .blob()), on any request whose CDP resource type isn't in kRefetchSafeResourceTypes (i.e. anything but static subresources / prefetches) - most notably plain fetch()/XHR GETs. Filed as #42862, with a minimal, version-bisected repro (works on 1.62.1, broken on 1.63.0).

Root cause

#42124 / #42201 restricted the evicted-body recovery path (Network.loadNetworkResource re-fetch) to static-subresource GETs and prefetch requests, to stop harTracer's automatic per-response HAR/trace capture from silently re-issuing requests with server-side side effects (#42002 - a tracking pixel getting hit twice).

That fix is correct for the case it targets, but the same restriction also gates the case where a test explicitly awaits response.body() once, for one response it already asked for by name (page.waitForResponse(...) then await response.body()). That's a fundamentally different risk: harTracer calls internalBody() on every response with no way to opt out, while an explicit response.body() call is a one-off the caller deliberately wrote. Gating both the same way means any app that reads a fetch()/XHR response in-page (e.g. const blob = await response.blob() before turning it into a download - a completely ordinary pattern) makes that response's body permanently unrecoverable for any later response.body() call, once Chromium evicts it.

Fix

Thread an isExplicit flag from Response.body() (the client-facing, dispatcher-driven entrypoint) down through internalBody() to createResponseBodyCallback(), defaulting to false:

GetResponseBodyCallback gained a parameter; Firefox/WebKit/BiDi's existing zero-argument callbacks are unchanged and still satisfy the (now one-parameter) type, since a function with fewer declared parameters is assignable to a type expecting more. Only Chromium's implementation needed a behavior change.

Testing

Fixes #42862

… GET

microsoft#42124/microsoft#42201 restricted the evicted-body re-fetch in
createResponseBodyCallback to static-subresource GETs and prefetches,
to stop harTracer's automatic per-response HAR capture from silently
re-issuing requests with server-side side effects (microsoft#42002).

That same restriction also applies when a test explicitly awaits
response.body() once for a response it already knows about, which is a
different situation: harTracer's capture is implicit and blanket, an
explicit body() call is a one-off the caller asked for by name. The
gate collaterally broke response.body() for any ordinary fetch()/XHR
GET whose body the page has already read (e.g. .blob() before
building a download), which Chromium evicts the same way it evicts
image bodies - "fetch" and "xhr" are not in kRefetchSafeResourceTypes
and carry no Sec-Purpose: prefetch header.

Thread an isExplicit flag from Response.body() (the client-facing,
dispatcher-driven path) down through internalBody() to
createResponseBodyCallback(), defaulting to false. harTracer.ts's
existing internalBody() call is untouched and keeps today's
restrictive behavior. The method check (never re-fetch non-GET) is
unconditional either way: an explicit call to body() is not an
invitation to resubmit a POST.

GetResponseBodyCallback gained a parameter; firefox/webkit/bidi's
zero-argument callbacks stay unchanged and still satisfy the type
(fewer declared params than the type expects is a valid assignment),
so only Chromium's implementation needed behavior changes.

Bisected against a real-world break: apache/superset's Playwright e2e
suite started failing exactly this way on the 1.62.1 -> 1.63.0 bump,
asserting on an exported file's contents via page.waitForResponse()
after the app's own code read it into a Blob for download.

Fixes microsoft#42862

Co-Authored-By: Evan Rusackas <evan@preset.io>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@rusackas

Copy link
Copy Markdown
Author

Thanks in advance for any CI approval and review comments... happy to help here wherever I'm able.

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.

[Regression 1.62.1 → 1.63.0]: page Response.body() returns empty after in-page fetch().blob() already consumed it

2 participants