Skip to content

Commit 435c33f

Browse files
committed
inspector: initial support for Network.loadNetworkResource
Fixes: #57873
1 parent 52d95f5 commit 435c33f

File tree

16 files changed

+567
-8
lines changed

16 files changed

+567
-8
lines changed

doc/api/cli.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,18 @@ passing a second `parentURL` argument for contextual resolution.
10141014

10151015
Previously gated the entire `import.meta.resolve` feature.
10161016

1017+
### `--experimental-inspector-network-resource`
1018+
1019+
<!-- YAML
1020+
added:
1021+
- REPLACEME
1022+
-->
1023+
1024+
> Stability: 1.1 - Active Development
1025+
1026+
Enables the Network.loadNetworkResource method in the Chrome DevTools Protocol (CDP) during debugging sessions.
1027+
This feature allows DevTools to fetch additional resources directly through the inspector backend.
1028+
10171029
### `--experimental-loader=module`
10181030

10191031
<!-- YAML

src/inspector/io_agent.cc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include "io_agent.h"
2+
#include "crdtp/dispatch.h"
3+
4+
namespace node {
5+
namespace inspector {
6+
namespace protocol {
7+
8+
void IoAgent::Wire(UberDispatcher* dispatcher) {
9+
frontend_ = std::make_shared<IO::Frontend>(dispatcher->channel());
10+
IO::Dispatcher::wire(dispatcher, this);
11+
}
12+
13+
DispatchResponse IoAgent::read(const String& in_handle,
14+
Maybe<int> in_offset,
15+
Maybe<int> in_size,
16+
String* out_data,
17+
bool* out_eof) {
18+
std::string txt = data_map_[in_handle];
19+
int offset = 0;
20+
if (in_offset.isJust()) {
21+
offset = in_offset.fromJust();
22+
} else if (offset_map_.find(in_handle) != offset_map_.end()) {
23+
offset = offset_map_[in_handle];
24+
}
25+
int size = 1 << 20;
26+
if (in_size.isJust()) {
27+
size = in_size.fromJust();
28+
}
29+
30+
if (offset < txt.length()) {
31+
std::string out_txt = txt.substr(offset, size);
32+
out_data->assign(out_txt);
33+
} else {
34+
*out_eof = true;
35+
}
36+
37+
offset_map_[in_handle] = offset + size;
38+
39+
return DispatchResponse::Success();
40+
}
41+
42+
DispatchResponse IoAgent::close(const String& in_handle) {
43+
offset_map_.erase(in_handle);
44+
data_map_.erase(in_handle);
45+
return DispatchResponse::Success();
46+
}
47+
48+
} // namespace protocol
49+
} // namespace inspector
50+
} // namespace node

src/inspector/io_agent.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef SRC_INSPECTOR_IO_AGENT_H_
2+
#define SRC_INSPECTOR_IO_AGENT_H_
3+
4+
#include <unordered_map>
5+
#include "node/inspector/protocol/IO.h"
6+
7+
8+
namespace node {
9+
10+
namespace inspector {
11+
namespace protocol {
12+
13+
class IoAgent : public IO::Backend {
14+
public:
15+
IoAgent() {}
16+
void Wire(UberDispatcher* dispatcher);
17+
DispatchResponse read(const String& in_handle,
18+
Maybe<int> in_offset,
19+
Maybe<int> in_size,
20+
String* out_data,
21+
bool* out_eof) override;
22+
DispatchResponse close(const String& in_handle) override;
23+
24+
void setData(const std::string& key, const std::string value) {
25+
data_map_[key] = value;
26+
}
27+
28+
private:
29+
std::shared_ptr<IO::Frontend> frontend_;
30+
std::unordered_map<std::string, int> offset_map_;
31+
std::unordered_map<std::string, std::string> data_map_;
32+
};
33+
} // namespace protocol
34+
} // namespace inspector
35+
} // namespace node
36+
37+
#endif // SRC_INSPECTOR_IO_AGENT_H_

src/inspector/network_agent.cc

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
#include "network_agent.h"
2+
#include <string>
3+
#include "env-inl.h"
24
#include "inspector/protocol_helper.h"
35
#include "network_inspector.h"
6+
#include "node_metadata.h"
47
#include "util-inl.h"
8+
#include "uv.h"
9+
#include "uv/unix.h"
10+
#include "v8-context.h"
511
#include "v8.h"
612

713
namespace node {
@@ -177,8 +183,13 @@ std::unique_ptr<protocol::Network::Response> createResponseFromObject(
177183
}
178184

179185
NetworkAgent::NetworkAgent(NetworkInspector* inspector,
180-
v8_inspector::V8Inspector* v8_inspector)
181-
: inspector_(inspector), v8_inspector_(v8_inspector) {
186+
v8_inspector::V8Inspector* v8_inspector,
187+
Environment* env,
188+
std::shared_ptr<protocol::IoAgent> io_agent)
189+
: inspector_(inspector),
190+
v8_inspector_(v8_inspector),
191+
env_(env),
192+
io_agent_(io_agent) {
182193
event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent;
183194
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
184195
event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed;
@@ -211,6 +222,167 @@ protocol::DispatchResponse NetworkAgent::disable() {
211222
return protocol::DispatchResponse::Success();
212223
}
213224

225+
std::tuple<int, std::string, std::string> NetworkAgent::spawnFetchProcess(
226+
std::string_view code, Environment* env, std::string_view url) {
227+
std::string stdout_result;
228+
std::string stderr_result;
229+
uv_loop_t* loop = new uv_loop_t;
230+
uv_loop_init(loop);
231+
uv_process_t child;
232+
uv_pipe_t stdout_pipe;
233+
uv_pipe_init(loop, &stdout_pipe, 0);
234+
uv_pipe_t stderr_pipe;
235+
uv_pipe_init(loop, &stderr_pipe, 0);
236+
237+
uv_process_options_t uv_process_options;
238+
std::string command =
239+
env->exec_path() + " --eval \"" + code.data() + "\" -- " + url.data();
240+
241+
#ifdef _WIN32
242+
const char* file = "cmd.exe";
243+
char* args[] = {
244+
const_cast<char*>(file),
245+
const_cast<char*>("/d"),
246+
const_cast<char*>("/s"),
247+
const_cast<char*>("/c"),
248+
reinterpret_cast<char*>(const_cast<char*>(command.c_str())),
249+
nullptr
250+
};
251+
#elif defined(__ANDROID__)
252+
const char* file = "/system/bin/sh";
253+
char* args[] = {
254+
const_cast<char*>(file),
255+
const_cast<char*>("-c"),
256+
reinterpret_cast<char*>(const_cast<char*>(command.c_str())),
257+
nullptr
258+
};
259+
#else
260+
const char* file = "/bin/sh";
261+
char* args[] = {
262+
const_cast<char*>(file),
263+
const_cast<char*>("-c"),
264+
reinterpret_cast<char*>(const_cast<char*>(command.c_str())),
265+
nullptr
266+
};
267+
#endif
268+
269+
uv_stdio_container_t stdio[3];
270+
uv_process_options.file = file;
271+
uv_process_options.args = args;
272+
uv_process_options.flags = 0;
273+
uv_process_options.stdio_count = 3;
274+
uv_process_options.stdio = stdio;
275+
uv_process_options.cwd = nullptr;
276+
uv_process_options.env = nullptr;
277+
278+
uv_process_options.exit_cb =
279+
[](uv_process_t* req, int64_t exit_status, int term_signal) {
280+
uv_close(reinterpret_cast<uv_handle_t*>(req), nullptr);
281+
};
282+
283+
stdio[0].flags = UV_INHERIT_FD;
284+
stdio[0].data.fd = 0;
285+
stdio[1].flags =
286+
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
287+
stdio[1].data.stream = reinterpret_cast<uv_stream_t*>(&stdout_pipe);
288+
stdio[2].flags =
289+
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
290+
stdio[2].data.stream = reinterpret_cast<uv_stream_t*>(&stderr_pipe);
291+
292+
int r = uv_spawn(loop, &child, &uv_process_options);
293+
294+
if (r != 0) {
295+
uv_loop_close(loop);
296+
delete loop;
297+
return {r, stdout_result, stderr_result};
298+
}
299+
300+
auto alloc_cb =
301+
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
302+
buf->base = static_cast<char*>(malloc(suggested_size));
303+
buf->len = suggested_size;
304+
};
305+
306+
auto read_cb = [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
307+
auto* response = static_cast<std::string*>(stream->data);
308+
if (nread > 0) {
309+
response->append(buf->base, nread);
310+
} else if (nread < 0) {
311+
if (!response->empty() && response->back() == '\n') {
312+
response->pop_back();
313+
}
314+
uv_close(reinterpret_cast<uv_handle_t*>(stream), nullptr);
315+
}
316+
if (buf->base) free(buf->base);
317+
};
318+
319+
stdout_pipe.data = &stdout_result;
320+
uv_read_start(
321+
reinterpret_cast<uv_stream_t*>(&stdout_pipe), alloc_cb, read_cb);
322+
323+
stderr_pipe.data = &stderr_result;
324+
uv_read_start(
325+
reinterpret_cast<uv_stream_t*>(&stderr_pipe), alloc_cb, read_cb);
326+
327+
uv_run(loop, UV_RUN_DEFAULT);
328+
329+
uv_walk(
330+
loop,
331+
[](uv_handle_t* handle, void*) {
332+
if (!uv_is_closing(handle)) {
333+
uv_close(handle, nullptr);
334+
}
335+
},
336+
nullptr);
337+
338+
uv_run(loop, UV_RUN_DEFAULT);
339+
340+
uv_loop_close(loop);
341+
delete loop;
342+
return {r, stdout_result, stderr_result};
343+
}
344+
345+
protocol::DispatchResponse NetworkAgent::loadNetworkResource(
346+
const protocol::String& in_url,
347+
std::unique_ptr<protocol::Network::LoadNetworkResourcePageResult>*
348+
out_resource) {
349+
if (!env_->options()->experimental_inspector_network_resource) {
350+
return protocol::DispatchResponse::MethodNotFound(
351+
"Network.loadNetworkResource is not supported in this environment. "
352+
"Please enable the experimental-inspector-network-resource option.");
353+
}
354+
DCHECK(io_agent_);
355+
356+
std::string code = R"(
357+
fetch(process.argv[1], {signal: AbortSignal.timeout(2000) }).then(res => {
358+
if (res.ok) {
359+
res.text().then(console.log)
360+
} else {
361+
throw new Error('Network error: ' + res.status);
362+
}
363+
})
364+
)";
365+
366+
auto [r, response, err] = spawnFetchProcess(code, env_, in_url);
367+
if (r == 0 && err.empty()) {
368+
std::string uuid = std::to_string(load_id_counter_);
369+
load_id_counter_++;
370+
io_agent_->setData(uuid, response);
371+
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
372+
.setSuccess(true)
373+
.setStream(uuid)
374+
.build();
375+
out_resource->reset(result.release());
376+
} else {
377+
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
378+
.setSuccess(false)
379+
.build();
380+
out_resource->reset(result.release());
381+
}
382+
383+
return protocol::DispatchResponse::Success();
384+
}
385+
214386
void NetworkAgent::requestWillBeSent(v8::Local<v8::Context> context,
215387
v8::Local<v8::Object> params) {
216388
protocol::String request_id;

src/inspector/network_agent.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef SRC_INSPECTOR_NETWORK_AGENT_H_
22
#define SRC_INSPECTOR_NETWORK_AGENT_H_
33

4+
#include "env.h"
5+
#include "io_agent.h"
46
#include "node/inspector/protocol/Network.h"
57

68
#include <unordered_map>
@@ -13,14 +15,21 @@ class NetworkInspector;
1315
class NetworkAgent : public protocol::Network::Backend {
1416
public:
1517
explicit NetworkAgent(NetworkInspector* inspector,
16-
v8_inspector::V8Inspector* v8_inspector);
18+
v8_inspector::V8Inspector* v8_inspector,
19+
Environment* env,
20+
std::shared_ptr<protocol::IoAgent> io_agent);
1721

1822
void Wire(protocol::UberDispatcher* dispatcher);
1923

2024
protocol::DispatchResponse enable() override;
2125

2226
protocol::DispatchResponse disable() override;
2327

28+
protocol::DispatchResponse loadNetworkResource(
29+
const protocol::String& in_url,
30+
std::unique_ptr<protocol::Network::LoadNetworkResourcePageResult>*
31+
out_resource) override;
32+
2433
void emitNotification(v8::Local<v8::Context> context,
2534
const protocol::String& event,
2635
v8::Local<v8::Object> params);
@@ -38,12 +47,17 @@ class NetworkAgent : public protocol::Network::Backend {
3847
v8::Local<v8::Object> params);
3948

4049
private:
50+
std::tuple<int, std::string, std::string> spawnFetchProcess(
51+
std::string_view code, Environment* env, std::string_view url);
4152
NetworkInspector* inspector_;
4253
v8_inspector::V8Inspector* v8_inspector_;
4354
std::shared_ptr<protocol::Network::Frontend> frontend_;
55+
Environment* env_;
4456
using EventNotifier = void (NetworkAgent::*)(v8::Local<v8::Context> context,
4557
v8::Local<v8::Object>);
4658
std::unordered_map<protocol::String, EventNotifier> event_notifier_map_;
59+
std::shared_ptr<protocol::IoAgent> io_agent_;
60+
int load_id_counter_ = 1;
4761
};
4862

4963
} // namespace inspector

src/inspector/network_inspector.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ namespace node {
44
namespace inspector {
55

66
NetworkInspector::NetworkInspector(Environment* env,
7-
v8_inspector::V8Inspector* v8_inspector)
7+
v8_inspector::V8Inspector* v8_inspector,
8+
std::shared_ptr<protocol::IoAgent> io_agent)
89
: enabled_(false), env_(env) {
9-
network_agent_ = std::make_unique<NetworkAgent>(this, v8_inspector);
10+
network_agent_ =
11+
std::make_unique<NetworkAgent>(this, v8_inspector, env, io_agent);
1012
}
1113
NetworkInspector::~NetworkInspector() {
1214
network_agent_.reset();

src/inspector/network_inspector.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ namespace inspector {
1111

1212
class NetworkInspector {
1313
public:
14-
explicit NetworkInspector(Environment* env,
15-
v8_inspector::V8Inspector* v8_inspector);
14+
explicit NetworkInspector(
15+
Environment* env,
16+
v8_inspector::V8Inspector* v8_inspector,
17+
std::shared_ptr<protocol::IoAgent> io_agent = nullptr);
1618
~NetworkInspector();
1719

1820
void Wire(protocol::UberDispatcher* dispatcher);

0 commit comments

Comments
 (0)