Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,18 @@ passing a second `parentURL` argument for contextual resolution.

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

### `--experimental-inspector-network-resource`

<!-- YAML
added:
- REPLACEME
-->

> Stability: 1.1 - Active Development

Enables the Network.loadNetworkResource method in the Chrome DevTools Protocol (CDP) during debugging sessions.
This feature allows DevTools to fetch additional resources directly through the inspector backend.

### `--experimental-loader=module`

<!-- YAML
Expand Down
50 changes: 50 additions & 0 deletions src/inspector/io_agent.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "io_agent.h"
#include "crdtp/dispatch.h"

namespace node {
namespace inspector {
namespace protocol {

void IoAgent::Wire(UberDispatcher* dispatcher) {
frontend_ = std::make_shared<IO::Frontend>(dispatcher->channel());
IO::Dispatcher::wire(dispatcher, this);
}

DispatchResponse IoAgent::read(const String& in_handle,
Maybe<int> in_offset,
Maybe<int> in_size,
String* out_data,
bool* out_eof) {
std::string txt = data_map_[in_handle];
int offset = 0;
if (in_offset.isJust()) {
offset = in_offset.fromJust();
} else if (offset_map_.find(in_handle) != offset_map_.end()) {
offset = offset_map_[in_handle];
}
int size = 1 << 20;
if (in_size.isJust()) {
size = in_size.fromJust();
}

if (static_cast<std::size_t>(offset) < txt.length()) {
std::string out_txt = txt.substr(offset, size);
out_data->assign(out_txt);
} else {
*out_eof = true;
}

offset_map_[in_handle] = offset + size;

return DispatchResponse::Success();
}

DispatchResponse IoAgent::close(const String& in_handle) {
offset_map_.erase(in_handle);
data_map_.erase(in_handle);
return DispatchResponse::Success();
}

} // namespace protocol
} // namespace inspector
} // namespace node
36 changes: 36 additions & 0 deletions src/inspector/io_agent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef SRC_INSPECTOR_IO_AGENT_H_
#define SRC_INSPECTOR_IO_AGENT_H_

#include <unordered_map>
#include "node/inspector/protocol/IO.h"

namespace node {

namespace inspector {
namespace protocol {

class IoAgent : public IO::Backend {
public:
IoAgent() {}
void Wire(UberDispatcher* dispatcher);
DispatchResponse read(const String& in_handle,
Maybe<int> in_offset,
Maybe<int> in_size,
String* out_data,
bool* out_eof) override;
DispatchResponse close(const String& in_handle) override;

void setData(const std::string& key, const std::string value) {
data_map_[key] = value;
}

private:
std::shared_ptr<IO::Frontend> frontend_;
std::unordered_map<std::string, int> offset_map_;
std::unordered_map<std::string, std::string> data_map_;
};
} // namespace protocol
} // namespace inspector
} // namespace node

#endif // SRC_INSPECTOR_IO_AGENT_H_
157 changes: 155 additions & 2 deletions src/inspector/network_agent.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#include "network_agent.h"
#include <string>
#include "env-inl.h"
#include "inspector/protocol_helper.h"
#include "network_inspector.h"
#include "node_metadata.h"
#include "util-inl.h"
#include "uv.h"
#include "uv/unix.h"
#include "v8-context.h"
#include "v8.h"

namespace node {
Expand Down Expand Up @@ -177,8 +183,13 @@ std::unique_ptr<protocol::Network::Response> createResponseFromObject(
}

NetworkAgent::NetworkAgent(NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector)
: inspector_(inspector), v8_inspector_(v8_inspector) {
v8_inspector::V8Inspector* v8_inspector,
Environment* env,
std::shared_ptr<protocol::IoAgent> io_agent)
: inspector_(inspector),
v8_inspector_(v8_inspector),
env_(env),
io_agent_(io_agent) {
event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent;
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed;
Expand Down Expand Up @@ -211,6 +222,148 @@ protocol::DispatchResponse NetworkAgent::disable() {
return protocol::DispatchResponse::Success();
}

std::tuple<int, std::string, std::string> NetworkAgent::spawnFetchProcess(
std::string_view code, Environment* env, std::string_view url) {
std::string stdout_result;
std::string stderr_result;
uv_loop_t* loop = new uv_loop_t;
uv_loop_init(loop);
uv_process_t child;
uv_pipe_t stdout_pipe;
uv_pipe_init(loop, &stdout_pipe, 0);
uv_pipe_t stderr_pipe;
uv_pipe_init(loop, &stderr_pipe, 0);

uv_process_options_t uv_process_options;
std::string command =
env->exec_path() + " --eval \"" + code.data() + "\" -- " + url.data();

const char* file = env->exec_path().c_str();
char* args[] = {
// shell_escape(env->exec_path()).c_str(),
const_cast<char*>(file),
const_cast<char*>("--eval"),
reinterpret_cast<char*>(const_cast<char*>(code.data())),
reinterpret_cast<char*>(const_cast<char*>(url.data())),
nullptr};

uv_stdio_container_t stdio[3];
uv_process_options.file = file;
uv_process_options.args = args;
uv_process_options.flags = 0;
uv_process_options.stdio_count = 3;
uv_process_options.stdio = stdio;
uv_process_options.cwd = nullptr;
uv_process_options.env = nullptr;

uv_process_options.exit_cb =
[](uv_process_t* req, int64_t exit_status, int term_signal) {
uv_close(reinterpret_cast<uv_handle_t*>(req), nullptr);
};

stdio[0].flags = UV_INHERIT_FD;
stdio[0].data.fd = 0;
stdio[1].flags =
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
stdio[1].data.stream = reinterpret_cast<uv_stream_t*>(&stdout_pipe);
stdio[2].flags =
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
stdio[2].data.stream = reinterpret_cast<uv_stream_t*>(&stderr_pipe);

int r = uv_spawn(loop, &child, &uv_process_options);

if (r != 0) {
uv_loop_close(loop);
delete loop;
return {r, stdout_result, stderr_result};
}

auto alloc_cb =
[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = static_cast<char*>(malloc(suggested_size));
buf->len = suggested_size;
};

auto read_cb = [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
auto* response = static_cast<std::string*>(stream->data);
if (nread > 0) {
response->append(buf->base, nread);
} else if (nread < 0) {
if (!response->empty() && response->back() == '\n') {
response->pop_back();
}
uv_close(reinterpret_cast<uv_handle_t*>(stream), nullptr);
}
if (buf->base) free(buf->base);
};

stdout_pipe.data = &stdout_result;
uv_read_start(
reinterpret_cast<uv_stream_t*>(&stdout_pipe), alloc_cb, read_cb);

stderr_pipe.data = &stderr_result;
uv_read_start(
reinterpret_cast<uv_stream_t*>(&stderr_pipe), alloc_cb, read_cb);

uv_run(loop, UV_RUN_DEFAULT);

uv_walk(
loop,
[](uv_handle_t* handle, void*) {
if (!uv_is_closing(handle)) {
uv_close(handle, nullptr);
}
},
nullptr);

uv_run(loop, UV_RUN_DEFAULT);

uv_loop_close(loop);
delete loop;
return {r, stdout_result, stderr_result};
}

protocol::DispatchResponse NetworkAgent::loadNetworkResource(
const protocol::String& in_url,
std::unique_ptr<protocol::Network::LoadNetworkResourcePageResult>*
out_resource) {
if (!env_->options()->experimental_inspector_network_resource) {
return protocol::DispatchResponse::MethodNotFound(
"Network.loadNetworkResource is not supported in this environment. "
"Please enable the experimental-inspector-network-resource option.");
}
DCHECK(io_agent_);

std::string code = R"(
fetch(process.argv[1], {signal: AbortSignal.timeout(2000) }).then(res => {
if (res.ok) {
res.text().then(console.log)
} else {
throw new Error('Network error: ' + res.status);
}
})
)";

auto [r, response, err] = spawnFetchProcess(code, env_, in_url);
if (r == 0 && err.empty()) {
std::string uuid = std::to_string(load_id_counter_);
load_id_counter_++;
io_agent_->setData(uuid, response);
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
.setSuccess(true)
.setStream(uuid)
.build();
out_resource->reset(result.release());
} else {
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
.setSuccess(false)
.build();
out_resource->reset(result.release());
}

return protocol::DispatchResponse::Success();
}

void NetworkAgent::requestWillBeSent(v8::Local<v8::Context> context,
v8::Local<v8::Object> params) {
protocol::String request_id;
Expand Down
16 changes: 15 additions & 1 deletion src/inspector/network_agent.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SRC_INSPECTOR_NETWORK_AGENT_H_
#define SRC_INSPECTOR_NETWORK_AGENT_H_

#include "env.h"
#include "io_agent.h"
#include "node/inspector/protocol/Network.h"

#include <unordered_map>
Expand All @@ -13,14 +15,21 @@ class NetworkInspector;
class NetworkAgent : public protocol::Network::Backend {
public:
explicit NetworkAgent(NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector);
v8_inspector::V8Inspector* v8_inspector,
Environment* env,
std::shared_ptr<protocol::IoAgent> io_agent);

void Wire(protocol::UberDispatcher* dispatcher);

protocol::DispatchResponse enable() override;

protocol::DispatchResponse disable() override;

protocol::DispatchResponse loadNetworkResource(
const protocol::String& in_url,
std::unique_ptr<protocol::Network::LoadNetworkResourcePageResult>*
out_resource) override;

void emitNotification(v8::Local<v8::Context> context,
const protocol::String& event,
v8::Local<v8::Object> params);
Expand All @@ -38,12 +47,17 @@ class NetworkAgent : public protocol::Network::Backend {
v8::Local<v8::Object> params);

private:
std::tuple<int, std::string, std::string> spawnFetchProcess(
std::string_view code, Environment* env, std::string_view url);
NetworkInspector* inspector_;
v8_inspector::V8Inspector* v8_inspector_;
std::shared_ptr<protocol::Network::Frontend> frontend_;
Environment* env_;
using EventNotifier = void (NetworkAgent::*)(v8::Local<v8::Context> context,
v8::Local<v8::Object>);
std::unordered_map<protocol::String, EventNotifier> event_notifier_map_;
std::shared_ptr<protocol::IoAgent> io_agent_;
int load_id_counter_ = 1;
};

} // namespace inspector
Expand Down
6 changes: 4 additions & 2 deletions src/inspector/network_inspector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ namespace node {
namespace inspector {

NetworkInspector::NetworkInspector(Environment* env,
v8_inspector::V8Inspector* v8_inspector)
v8_inspector::V8Inspector* v8_inspector,
std::shared_ptr<protocol::IoAgent> io_agent)
: enabled_(false), env_(env) {
network_agent_ = std::make_unique<NetworkAgent>(this, v8_inspector);
network_agent_ =
std::make_unique<NetworkAgent>(this, v8_inspector, env, io_agent);
}
NetworkInspector::~NetworkInspector() {
network_agent_.reset();
Expand Down
6 changes: 4 additions & 2 deletions src/inspector/network_inspector.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ namespace inspector {

class NetworkInspector {
public:
explicit NetworkInspector(Environment* env,
v8_inspector::V8Inspector* v8_inspector);
explicit NetworkInspector(
Environment* env,
v8_inspector::V8Inspector* v8_inspector,
std::shared_ptr<protocol::IoAgent> io_agent = nullptr);
~NetworkInspector();

void Wire(protocol::UberDispatcher* dispatcher);
Expand Down
4 changes: 4 additions & 0 deletions src/inspector/node_inspector.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
'src/inspector/network_agent.h',
'src/inspector/worker_inspector.cc',
'src/inspector/worker_inspector.h',
'src/inspector/io_agent.cc',
'src/inspector/io_agent.h',
],
'node_inspector_generated_sources': [
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Forward.h',
Expand All @@ -47,6 +49,8 @@
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.cpp',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.h',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/IO.h',
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/IO.cpp',
],
'node_protocol_files': [
'<(protocol_tool_path)/lib/Forward_h.template',
Expand Down
Loading
Loading