From 2c66dfedce7915b1380db43648c8834d3f221b2d Mon Sep 17 00:00:00 2001 From: Maksim Soltan Date: Wed, 1 Apr 2026 07:49:06 -0700 Subject: [PATCH] perf: use Content-Length header instead of res.body() for network size tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit res.body() buffers the entire HTTP response into a Node Buffer. For large responses (JS bundles, images, API payloads) this is O(response_size) memory pressure per request and blocks the event loop while Playwright reads the full response body just to get a byte count. Fix: read the Content-Length response header first (O(1), zero I/O). Fall back to res.body() only for chunked transfer encoding where Content-Length is legitimately absent. This is particularly noticeable on pages with heavy network traffic — downloading a 2MB JS bundle previously allocated a full 2MB Buffer just to call .length on it. Closes #711 --- browse/src/browser-manager.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index f4ade9e1e..484bc79e6 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -1016,14 +1016,28 @@ export class BrowserManager { } }); - // Capture response sizes via response finished + // Capture response sizes via response finished. + // Prefer Content-Length header over res.body() — body() buffers the entire + // response into memory, which is O(response_size) per request and blocks + // the event loop on large payloads (images, JS bundles, API responses). + // Content-Length is available immediately from headers with zero I/O cost. + // Fall back to body() only when Content-Length is absent (chunked transfer). page.on('requestfinished', async (req) => { try { const res = await req.response(); if (res) { const url = req.url(); - const body = await res.body().catch(() => null); - const size = body ? body.length : 0; + let size = 0; + + const contentLength = res.headers()['content-length']; + if (contentLength) { + size = parseInt(contentLength, 10) || 0; + } else { + // Chunked or unknown — body() is the only option + const body = await res.body().catch(() => null); + size = body ? body.length : 0; + } + for (let i = networkBuffer.length - 1; i >= 0; i--) { const entry = networkBuffer.get(i); if (entry && entry.url === url && !entry.size) {