Skip to content

Commit 6cf8da4

Browse files
committed
Fix WebSocket request initiators for sockets opened from framework code
1 parent e6da2e5 commit 6cf8da4

3 files changed

Lines changed: 94 additions & 4 deletions

File tree

‎packages/react-native/Libraries/WebSocket/WebSocket.js‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,10 @@ class WebSocket extends EventTarget {
143143
);
144144
this._socketId = nextWebSocketId++;
145145
this._registerEvents();
146+
// Skip this constructor's own frame so that the request initiator stack
147+
// starts at the caller of `new WebSocket()`.
146148
const devToolsRequestId =
147-
global.__NETWORK_REPORTER__?.createDevToolsRequestId();
149+
global.__NETWORK_REPORTER__?.createDevToolsRequestId(1);
148150
NativeWebSocketModule.connect(
149151
url,
150152
protocolList,

‎packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetNetwork.cpp‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
#include <react/utils/Uuid.h>
1313

14+
#include <algorithm>
15+
1416
using namespace facebook::jsi;
1517
using namespace std::string_literals;
1618

@@ -44,8 +46,16 @@ void RuntimeTarget::installNetworkReporterAPI() {
4446
auto jsiCreateDevToolsRequestId = [selfWeak = weak_from_this()](
4547
Runtime& runtime,
4648
const Value& /*thisVal*/,
47-
const Value* /*args*/,
48-
size_t /*count*/) -> Value {
49+
const Value* args,
50+
size_t count) -> Value {
51+
// Optional `framesToSkip` argument: the number of leading JS call frames
52+
// that belong to the calling API's own implementation (e.g. the `WebSocket`
53+
// constructor), which are omitted from the recorded initiator stack so
54+
// that its top frame is the code that invoked the API.
55+
size_t framesToSkip = 0;
56+
if (count > 0 && args[0].isNumber()) {
57+
framesToSkip = static_cast<size_t>(std::max(0.0, args[0].asNumber()));
58+
}
4959
std::optional<std::string> devToolsRequestId;
5060
tryExecuteSync(selfWeak, [&](RuntimeTarget& self) {
5161
devToolsRequestId = self.createNetworkRequestId();
@@ -58,6 +68,15 @@ void RuntimeTarget::installNetworkReporterAPI() {
5868
auto stackTrace = self.delegate_.captureStackTrace(runtime);
5969
auto cdpStackTrace = self.delegate_.serializeStackTrace(*stackTrace);
6070
if (cdpStackTrace) {
71+
if (framesToSkip > 0) {
72+
if (auto* callFrames = cdpStackTrace->get_ptr("callFrames");
73+
callFrames != nullptr && callFrames->isArray()) {
74+
callFrames->erase(
75+
callFrames->begin(),
76+
callFrames->begin() +
77+
std::min(framesToSkip, callFrames->size()));
78+
}
79+
}
6180
NetworkHandler::getInstance().recordRequestInitiatorStack(
6281
*devToolsRequestId, std::move(*cdpStackTrace));
6382
}
@@ -81,7 +100,7 @@ void RuntimeTarget::installNetworkReporterAPI() {
81100
Function::createFromHostFunction(
82101
runtime,
83102
PropNameID::forAscii(runtime, "createDevToolsRequestId"),
84-
0,
103+
1,
85104
jsiCreateDevToolsRequestId));
86105
networkReporterApi = objectFreeze(runtime, std::move(networkReporterApi));
87106
globalObj.setProperty(runtime, "__NETWORK_REPORTER__", networkReporterApi);

‎packages/react-native/ReactCommon/jsinspector-modern/tests/NetworkReporterTest.cpp‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,75 @@ TEST_P(NetworkReporterTest, testWebSocketCreatedWithInitiator) {
625625
})");
626626
}
627627

628+
TEST_P(NetworkReporterTest, testWebSocketCreatedWithInitiatorFramesToSkip) {
629+
InSequence s;
630+
631+
this->expectMessageFromPage(JsonEq(R"({
632+
"id": 0,
633+
"result": {}
634+
})"));
635+
this->toPage_->sendMessage(R"({
636+
"id": 0,
637+
"method": "Debugger.enable"
638+
})");
639+
640+
this->expectMessageFromPage(JsonEq(R"({
641+
"id": 1,
642+
"result": {}
643+
})"));
644+
this->toPage_->sendMessage(R"({
645+
"id": 1,
646+
"method": "Network.enable"
647+
})");
648+
649+
auto& runtime = engineAdapter_->getRuntime();
650+
651+
// `apiShim` stands in for an API implementation (such as the `WebSocket`
652+
// constructor) that asks for its own frame to be omitted.
653+
auto requestId = this->eval(R"( // line 0
654+
function apiShim() { // line 1
655+
return globalThis.__NETWORK_REPORTER__.createDevToolsRequestId(1); // line 2
656+
} // line 3
657+
function openSocket() { // line 4
658+
return apiShim(); // line 5
659+
} // line 6
660+
openSocket(); // line 7
661+
662+
//# sourceURL=webSocketInitiatorFramesToSkipTest.js
663+
)")
664+
.asString(runtime)
665+
.utf8(runtime);
666+
667+
this->expectMessageFromPage(JsonParsed(AllOf(
668+
AtJsonPtr("/method", "Network.webSocketCreated"),
669+
AtJsonPtr("/params/requestId", requestId),
670+
AtJsonPtr("/params/url", "wss://example.com/frames-to-skip"),
671+
AtJsonPtr("/params/initiator/type", "script"),
672+
AtJsonPtr(
673+
"/params/initiator/stack/callFrames",
674+
AllOf(
675+
Each(AtJsonPtr("/url", "webSocketInitiatorFramesToSkipTest.js")),
676+
ElementsAre(
677+
AllOf(
678+
AtJsonPtr("/functionName", "openSocket"),
679+
AtJsonPtr("/lineNumber", 5)),
680+
AllOf(
681+
AtJsonPtr("/functionName", "global"),
682+
AtJsonPtr("/lineNumber", 7))))))));
683+
684+
NetworkReporter::getInstance().reportWebSocketCreated(
685+
requestId, "wss://example.com/frames-to-skip");
686+
687+
this->expectMessageFromPage(JsonEq(R"({
688+
"id": 2,
689+
"result": {}
690+
})"));
691+
this->toPage_->sendMessage(R"({
692+
"id": 2,
693+
"method": "Network.disable"
694+
})");
695+
}
696+
628697
TEST_P(NetworkReporterTest, testNetworkEventsWhenDisabled) {
629698
EXPECT_FALSE(NetworkReporter::getInstance().isDebuggingEnabled());
630699

0 commit comments

Comments
 (0)