Skip to content

Encode fetch responses for DevTools only when a debugger is attached - #59717

Open
hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:android-fetch-skip-devtools-base64
Open

hryhoriiK97 wants to merge 1 commit into
react:mainfrom
hryhoriiK97:android-fetch-skip-devtools-base64

Conversation

@hryhoriiK97

Copy link
Copy Markdown
Contributor

Summary:

On Android, a response handled by a ResponseHandler goes through NetworkEventUtil.onDataReceived(reactContext, requestId, devToolsRequestId, data: WritableMap, rawData). That's the blob path, which fetch uses by default. This method Base64-encodes the whole response body and passes it to InspectorNetworkReporter.maybeStoreResponseBody:

InspectorNetworkReporter.maybeStoreResponseBody(
    devToolsRequestId,
    Base64.encodeToString(rawData, Base64.NO_WRAP),
    true,
)

The reporter only uses the string when isDebuggingEnabled() is true, so without a debugger attached the encoded copy is thrown away. In a release build that's every response. The encoding now runs only when debugging is enabled. NetworkingModule already avoids the same kind of work for request bodies, by building their DevTools preview only in debug builds.

With a debugger attached, the same call runs as before.

Changelog:

[ANDROID] [CHANGED] - fetch responses are no longer Base64-encoded for DevTools when no debugger is attached

Test Plan:

Unit tests: NetworkEventUtilTest has two new tests, one checking that Base64 isn't touched when debugging is off, and one checking that the encoded body is stored when it's on. The first one fails without this change.

./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests 'com.facebook.react.modules.network.*'

87 tests, 0 failures. yarn format-check-kotlin passes.

DevTools check with this change: debug RNTester on an Android emulator, connected to the app's Metro DevTools target over CDP. After Network.enable and a fetch evaluated in the app, Network.getResponseBody returned {"body":"cGFja2FnZXItc3RhdHVzOnJ1bm5pbmc=","base64Encoded":true}, which decodes to the same text JS received.

Measurements on a Galaxy A16 (Android 16), release RNTester with default feature flags, compiled with cmd package compile -m speed. A Playground screen loops await (await fetch(url)).json() against a local server over adb reverse. A temporary switch in the same build selects the arm. The build was a local benchmark tree with other temporary probes switched off, not this exact branch, and the switch used the same isDebuggingEnabled() check. Each run is a fresh process with a 15 s window (20 s for 1 MB). The order is A/B/B/A twice, so 4 processes per arm, and the table shows medians:

Response size Encoding step Process CPU per request ART GCs in the window ART GC time per request
13 KB 1147 µs → 7 µs 34.3 ms → 32.2 ms 3 → 2 0.73 ms → 0.46 ms
103 KB 5482 µs → 6.5 µs 58.7 ms → 54.1 ms 12 → 5 3.46 ms → 1.38 ms
1 MB 15.3 ms → 4 µs 118.8 ms → 108.7 ms 48.5 → 24 18.7 ms → 9.3 ms
  • The encoding step is timed around the isDebuggingEnabled() check and the call, with System.nanoTime. CPU comes from /proc/<pid>/stat, and GC from Debug.getRuntimeStat.
  • CPU per request includes the whole process, including OkHttp's disk cache, and requests went through a remote device tunnel. Request counts were read in steps of 10, so the 1 MB CPU figure is the noisiest (individual runs overlap).
  • This work runs on the network thread, so I'm not claiming a frame-time change. I didn't measure frames.

I found this while profiling fetch on the same phone: on a 100 KB response loop, onDataReceived was 34% of the network thread, about half of that in creating the encoded String and about 40% in Base64.encode.

🤖 Generated with Claude Code

NetworkEventUtil.onDataReceived Base64-encodes every response handled by
a ResponseHandler, which is the blob path fetch uses by default, and
passes it to InspectorNetworkReporter.maybeStoreResponseBody. The
reporter drops the string unless debugging is enabled, so release builds
do this work for every response and throw the result away. Check
isDebuggingEnabled() before encoding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 31, 2026
@facebook-github-tools facebook-github-tools Bot added p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Oct 31, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Software Mansion Partner: Software Mansion Partner Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant