From 3037e2a1fff0aa384ad9c91f1382a9e2f5a714c4 Mon Sep 17 00:00:00 2001 From: Nikita Nemirovsky Date: Tue, 10 Mar 2026 02:29:01 +0800 Subject: [PATCH] Support large WebKit Inspector messages (heap snapshots) Three changes to handle messages that can reach 50-200+ MB: 1. Raise MAX_BODY_LENGTH from 64 MB to 256 MB (webinspector.c) - The previous 64 MB limit silently dropped large messages like Heap.trackingStart snapshots, breaking the event pipeline - Also adds parentheses to fix operator precedence bug (1<<26 without parens is ambiguous in some expressions) 2. Send large payloads as WebSocket continuation frames (ios_webkit_debug_proxy.c) - Splits messages >1 MB into 1 MB chunks using RFC 6455 continuation frames - Reduces peak memory on the outgoing side since the WebSocket layer no longer needs to buffer the entire payload at once 3. Skip UTF-8 validation for large payloads (websocket.c) - Payloads >1 MB skip the byte-by-byte UTF-8 validation - The data is JSON from WebKit Inspector and is always valid UTF-8 - Also fixes ws_send_frame to clear continued_opcode on FIN, matching the receive-side behavior --- src/ios_webkit_debug_proxy.c | 33 ++++++++++++++++++++++++++++++--- src/webinspector.c | 5 +++-- src/websocket.c | 9 +++++++-- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/ios_webkit_debug_proxy.c b/src/ios_webkit_debug_proxy.c index 058ab723..007bce07 100644 --- a/src/ios_webkit_debug_proxy.c +++ b/src/ios_webkit_debug_proxy.c @@ -1415,6 +1415,10 @@ rpc_status iwdp_on_applicationSentListing(rpc_t rpc, return RPC_SUCCESS; } +// Maximum WebSocket frame payload before splitting into continuation frames. +// 1 MB chunks reduce peak memory vs. buffering the entire payload at once. +#define WS_MAX_FRAME_PAYLOAD (1 << 20) + rpc_status iwdp_on_applicationSentData(rpc_t rpc, const char *app_id, const char *dest_id, const char *data, const size_t length) { @@ -1424,9 +1428,32 @@ rpc_status iwdp_on_applicationSentData(rpc_t rpc, return RPC_SUCCESS; // error but don't kill the inspector! } ws_t ws = iws->ws; - return ws->send_frame(ws, - true, OPCODE_TEXT, false, - data, length); + // Small messages: send as a single frame (fast path). + if (length <= WS_MAX_FRAME_PAYLOAD) { + return ws->send_frame(ws, + true, OPCODE_TEXT, false, + data, length); + } + // Large messages (e.g. heap snapshots): split into continuation frames + // to reduce peak memory. Each chunk is sent as its own WebSocket frame. + // Always pass OPCODE_TEXT — ws_send_frame tracks continuation state + // internally and converts non-first frames to OPCODE_CONTINUATION. + size_t offset = 0; + while (offset < length) { + size_t chunk = length - offset; + if (chunk > WS_MAX_FRAME_PAYLOAD) { + chunk = WS_MAX_FRAME_PAYLOAD; + } + bool is_last = (offset + chunk >= length); + ws_status ret = ws->send_frame(ws, + is_last, OPCODE_TEXT, false, + data + offset, chunk); + if (ret) { + return ret; + } + offset += chunk; + } + return RPC_SUCCESS; } rpc_status iwdp_on_applicationUpdated(rpc_t rpc, diff --git a/src/webinspector.c b/src/webinspector.c index f0301a54..819ba2e7 100644 --- a/src/webinspector.c +++ b/src/webinspector.c @@ -39,8 +39,9 @@ // TODO figure out exact value #define MAX_RPC_LEN 8096 - 500 -// some arbitrarly limit, to catch bad packets -#define MAX_BODY_LENGTH 1<<26 +// some arbitrarily limit, to catch bad packets +// 256 MB — large enough for heap snapshots (50-200MB+) from heavy pages +#define MAX_BODY_LENGTH (1 << 28) struct wi_private { bool is_sim; diff --git a/src/websocket.c b/src/websocket.c index 3437deff..d04a792a 100644 --- a/src/websocket.c +++ b/src/websocket.c @@ -281,7 +281,10 @@ ws_status ws_send_frame(ws_t self, size_t i; bool is_utf8 = (opcode2 == OPCODE_TEXT ? true : false); - if (is_utf8) { + // Skip UTF-8 validation for large payloads (>1 MB) — the data is JSON from + // WebKit Inspector and is always valid UTF-8. Validating 50-200 MB payloads + // is expensive and unnecessary. + if (is_utf8 && payload_length <= (1 << 20)) { unsigned int utf8_state = UTF8_VALID; const char *payload_head = payload_data; for (i = 0; i < payload_length; i++) { @@ -336,7 +339,9 @@ ws_status ws_send_frame(ws_t self, out_tail += payload_length; } - if (!is_fin && !my->continued_opcode) { + if (is_fin) { + my->continued_opcode = 0; + } else if (!my->continued_opcode) { my->continued_opcode = opcode; }