perf(cef shim): send a DevTools call's parameters without a parse and rewrite on the main thread
Summary
The same hang records show the CEF shim's half of a DevTools call on the main thread. Not measured: no before/after job exists for this PR (see Observed / Not run), so it claims no ms gain; the records below are why the change was made:
| Record | Stall | Main-thread CPU | Top frames |
|---|---|---|---|
| v2.2.9 seq 565 | 61 ms | 61 ms | CefBrowserHostCToCpp::ExecuteDevToolsMethod <- cmux_shim_devtools_call <- CEFTab.devTools |
| v2.2.10 seq 410 | 104 ms | 104 ms | cef_parse_json <- CefParseJSON <- cmux_shim_devtools_call <- CEFTab.devTools |
cmux_shim_devtools_call parsed the host's parameters text (CefParseJSON, through UTF-16) into a CefDictionaryValue for CefBrowserHost::ExecuteDevToolsMethod, which writes it back to JSON. Both run on CEF's UI thread, the app's main thread, and for an app page's script batch the text is megabytes.
Now the shim checks the method name (IsDevToolsMethodName: letters, digits, _, dots; no quote can reach the message) and that the text is one JSON object (IsOneJSONObject: a byte scan with no values built), then sends the message ExecuteDevToolsMethod would make, {"id":N,"method":M,"params":P}, with P as the host wrote it, through SendDevToolsMessage. Chromium parses the message once, as for any DevTools client. The id rule does not change (shim ids below 2^30), so the result still reaches OnDevToolsMethodResult (ForwardDevToolsMessage passes those ids on) and arrives as DEVTOOLS_RESULT; a result Chromium sends inside the call is still caught by the host's synchronous-answer table. The shim header and the ABI identity do not change; so the header comment (returns 0 when params_json is not a JSON object) is now true only for text that fails the scan. I left the header as it is because an edit changes the ABI identity hash. The helpers live in devtools_message_id.h, next to the id scanner, so the existing Linux C++ test covers them.
Behavior change: parameters text that passes the one-object scan but is not valid JSON (for example a mismatched closer, {"a":1]) is now sent, and Chromium answers with a parse error that has no id, so the waiting call times out after 5 s instead of failing at once. The host never makes such text (its parameters come from JSONSerialization).
Independent of the Swift-side PR (#28916, cp/cef-devtools-params-off-main). The commit messages on this branch quote the 61-104 ms records as the cause; they are not a measured gain of this change (the pushed commits are not rewritten).
Testing
Focused command: scripts/cmux-next/test-shim-devtools-id-cpp.sh (also the "cmux-next CEF shim C++ tests" workflow, which runs on this PR). New cases: method name checks, the one-object scan, and the message byte for byte (a lone surrogate escape, deep nesting and key order kept, a 3 MB script).
Red at 64c1fadd0d4 (test commit): run 41169037116 fails at compile (no DevToolsCallMessage, IsDevToolsMethodName, IsOneJSONObject). This is a missing-symbol red, not a behavioral one.
Green at 50c1beded06 (head): run 41172438907, "DevTools message id scanner" 35 cases, 0 failures (33 at 3ee956586b0, run 41169038950).
Compile: cmux-ci compile-check --package CmuxNext --app --recipe-version 2 job 3bb9229df74c417a7fbd8eff passed at 3ee956586b0; it does not compile the shim's .mm files. The shim itself compiled in the tagged build cmux-ci build cmux --tag cef-devtools-shim-v1 job cf3b7b8183b95a3b26577314 at 50c1beded06 (done; the bundle has libcmux_cef_shim.dylib; build-cef-shim.sh outputs are keyed by the shim sources, so this is a fresh compile of the changed file).
Observed / Not run
Observed: the runs and jobs above.
Not run: a live before/after of the shim call's main-thread time. The reviewer is right that the 61 ms record (v2.2.9 seq 565) shows only the ExecuteDevToolsMethod frame, which also holds Chromium's own CBOR conversion and dispatch; that work stays on the main thread after this PR. The 104 ms record (v2.2.10 seq 410) is inside cef_parse_json, which this PR removes. The capture route that would measure it failed (capture minis refused this VM's SSH probe; the Blacksmith fallback needs glaeda-gh). No live wiring test covers shim_browser.mm (cmux_shim_devtools_call).
Squash commit message
The squash title is the PR title. Use this text as the squash body (commit_message), not the joined commit messages, which quote the hang-record ms as a cause:
cmux_shim_devtools_call parsed the host's parameters JSON (CefParseJSON, through UTF-16) into a
CefDictionaryValue for CefBrowserHost::ExecuteDevToolsMethod, which wrote it back to JSON, both on
CEF's UI thread (the app's main thread). Hang records v2.2.9 seq 565 and v2.2.10 seq 410 name this
call; the gain of this change is not measured.
Now the shim checks the method name (IsDevToolsMethodName) and that the text is one JSON object
(IsOneJSONObject, a byte scan with no values built), then sends {"id":N,"method":M,"params":P} with
P as the host wrote it, through SendDevToolsMessage. Ids stay below 2^30, so the result still
reaches OnDevToolsMethodResult and arrives as DEVTOOLS_RESULT. The shim header and ABI identity do
not change. Text that passes the scan but is not valid JSON now gets no result and times out after
5 s (the host never makes it).
Test: scripts/cmux-next/test-shim-devtools-id-cpp.sh (35 cases).
Performance
No perf numbers: not measured. No before/after job exists for the shim call; the live A/B (#29020) measures the whole DevTools call path, and this PR claims no gain.
Changelog
Changed: The Chromium shim no longer parses and rewrites a DevTools call's parameters on the main thread (not measured).
Proof
No capture: no UI change.