Skip to content
Merged
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
11 changes: 11 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,17 @@ 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

Enable experimental support for inspector network resources.

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

<!-- YAML
Expand Down
37 changes: 37 additions & 0 deletions doc/api/inspector.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,43 @@ This feature is only available with the `--experimental-network-inspection` flag
Broadcasts the `Network.loadingFailed` event to connected frontends. This event indicates that
HTTP request has failed to load.

### `inspector.NetworkResources.put`

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

> Stability: 1.1 - Active Development

This feature is only available with the `--experimental-inspector-network-resource` flag enabled.

The inspector.NetworkResources.put method is used to provide a response for a loadNetworkResource
request issued via the Chrome DevTools Protocol (CDP).
This is typically triggered when a source map is specified by URL, and a DevTools frontend—such as
Chrome—requests the resource to retrieve the source map.

This method allows developers to predefine the resource content to be served in response to such CDP requests.

```js
const inspector = require('node:inspector');
// By preemptively calling put to register the resource, a source map can be resolved when
// a loadNetworkResource request is made from the frontend.
async function setNetworkResources() {
const mapUrl = 'http://localhost:3000/dist/app.js.map';
const tsUrl = 'http://localhost:3000/src/app.ts';
const distAppJsMap = await fetch(mapUrl).then((res) => res.text());
const srcAppTs = await fetch(tsUrl).then((res) => res.text());
inspector.NetworkResources.put(mapUrl, distAppJsMap);
inspector.NetworkResources.put(tsUrl, srcAppTs);
};
setNetworkResources().then(() => {
require('./dist/app');
});
```

For more details, see the official CDP documentation: [Network.loadNetworkResource](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-loadNetworkResource)

## Support of breakpoints

The Chrome DevTools Protocol [`Debugger` domain][] allows an
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ Enable experimental WebAssembly module support.
.It Fl -experimental-quic
Enable the experimental QUIC support.
.
.It Fl -experimental-inspector-network-resource
Enable experimental support for inspector network resources.
.
.It Fl -force-context-aware
Disable loading native addons that are not context-aware.
.
Expand Down
8 changes: 8 additions & 0 deletions lib/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const {
} = require('internal/validators');
const { isMainThread } = require('worker_threads');
const { _debugEnd } = internalBinding('process_methods');
const {
put,
} = require('internal/inspector/network_resources');

const {
Connection,
Expand Down Expand Up @@ -218,6 +221,10 @@ const Network = {
dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params),
};

const NetworkResources = {
put,
};

module.exports = {
open: inspectorOpen,
close: _debugEnd,
Expand All @@ -226,4 +233,5 @@ module.exports = {
console,
Session,
Network,
NetworkResources,
};
27 changes: 27 additions & 0 deletions lib/internal/inspector/network_resources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

const { getOptionValue } = require('internal/options');
const { validateString } = require('internal/validators');
const { putNetworkResource } = internalBinding('inspector');

/**
* Registers a resource for the inspector using the internal 'putNetworkResource' binding.
* @param {string} url - The URL of the resource.
* @param {string} data - The content of the resource to provide.
*/
function put(url, data) {
if (!getOptionValue('--experimental-inspector-network-resource')) {
process.emitWarning(
'The --experimental-inspector-network-resource option is not enabled. ' +
'Please enable it to use the putNetworkResource function');
return;
}
validateString(url, 'url');
validateString(data, 'data');

putNetworkResource(url, data);
}

module.exports = {
put,
};
57 changes: 57 additions & 0 deletions src/inspector/io_agent.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "io_agent.h"
#include <algorithm>
#include <iostream>
#include <string>
#include <string_view>
#include "crdtp/dispatch.h"
#include "inspector/network_resource_manager.h"

namespace node::inspector::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,
std::optional<int> in_offset,
std::optional<int> in_size,
String* out_data,
bool* out_eof) {
std::string url = in_handle;
std::string txt = network_resource_manager_->Get(url);
std::string_view txt_view(txt);

int offset = 0;
bool offset_was_specified = false;
if (in_offset.has_value()) {
offset = *in_offset;
offset_was_specified = true;
} else if (offset_map_.find(url) != offset_map_.end()) {
offset = offset_map_[url];
}
int size = 1 << 20;
if (in_size.has_value()) {
size = *in_size;
}
if (static_cast<std::size_t>(offset) < txt_view.length()) {
std::string_view out_view = txt_view.substr(offset, size);
out_data->assign(out_view.data(), out_view.size());
*out_eof = false;
if (!offset_was_specified) {
offset_map_[url] = offset + size;
}
} else {
*out_data = "";
*out_eof = true;
}

return DispatchResponse::Success();
}

DispatchResponse IoAgent::close(const String& in_handle) {
std::string url = in_handle;
network_resource_manager_->Erase(url);
return DispatchResponse::Success();
}
} // namespace node::inspector::protocol
30 changes: 30 additions & 0 deletions src/inspector/io_agent.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef SRC_INSPECTOR_IO_AGENT_H_
#define SRC_INSPECTOR_IO_AGENT_H_

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

namespace node::inspector::protocol {

class IoAgent : public IO::Backend {
public:
explicit IoAgent(
std::shared_ptr<NetworkResourceManager> network_resource_manager)
: network_resource_manager_(std::move(network_resource_manager)) {}
void Wire(UberDispatcher* dispatcher);
DispatchResponse read(const String& in_handle,
std::optional<int> in_offset,
std::optional<int> in_size,
String* out_data,
bool* out_eof) override;
DispatchResponse close(const String& in_handle) override;

private:
std::shared_ptr<IO::Frontend> frontend_;
std::unordered_map<std::string, int> offset_map_ =
{}; // Maps stream_id to offset
std::shared_ptr<NetworkResourceManager> network_resource_manager_;
};
} // namespace node::inspector::protocol
#endif // SRC_INSPECTOR_IO_AGENT_H_
48 changes: 44 additions & 4 deletions src/inspector/network_agent.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
#include "network_agent.h"
#include <string>
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "inspector/network_resource_manager.h"
#include "inspector/protocol_helper.h"
#include "network_inspector.h"
#include "node_metadata.h"
#include "util-inl.h"
#include "uv.h"
#include "v8-context.h"
#include "v8.h"

namespace node {
Expand Down Expand Up @@ -202,9 +208,15 @@ std::unique_ptr<protocol::Network::Response> createResponseFromObject(
.build();
}

NetworkAgent::NetworkAgent(NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector)
: inspector_(inspector), v8_inspector_(v8_inspector) {
NetworkAgent::NetworkAgent(
NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector,
Environment* env,
std::shared_ptr<NetworkResourceManager> network_resource_manager)
: inspector_(inspector),
v8_inspector_(v8_inspector),
env_(env),
network_resource_manager_(std::move(network_resource_manager)) {
event_notifier_map_["requestWillBeSent"] = &NetworkAgent::requestWillBeSent;
event_notifier_map_["responseReceived"] = &NetworkAgent::responseReceived;
event_notifier_map_["loadingFailed"] = &NetworkAgent::loadingFailed;
Expand Down Expand Up @@ -329,10 +341,38 @@ protocol::DispatchResponse NetworkAgent::streamResourceContent(
// If the request is finished, remove the entry.
requests_.erase(in_requestId);
}

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

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::ServerError(
"Network resource loading is not enabled. This feature is "
"experimental and requires --experimental-inspector-network-resource "
"flag to be set.");
}
CHECK_NOT_NULL(network_resource_manager_);
std::string data = network_resource_manager_->Get(in_url);
bool found = !data.empty();
if (found) {
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
.setSuccess(true)
.setStream(in_url)
.build();
*out_resource = std::move(result);
return protocol::DispatchResponse::Success();
} else {
auto result = protocol::Network::LoadNetworkResourcePageResult::create()
.setSuccess(false)
.build();
*out_resource = std::move(result);
return protocol::DispatchResponse::Success();
}
}

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

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

#include <map>
#include <memory>
#include <unordered_map>

namespace node {
Expand Down Expand Up @@ -38,8 +42,11 @@ struct RequestEntry {

class NetworkAgent : public protocol::Network::Backend {
public:
explicit NetworkAgent(NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector);
explicit NetworkAgent(
NetworkInspector* inspector,
v8_inspector::V8Inspector* v8_inspector,
Environment* env,
std::shared_ptr<NetworkResourceManager> network_resource_manager);

void Wire(protocol::UberDispatcher* dispatcher);

Expand All @@ -60,6 +67,11 @@ class NetworkAgent : public protocol::Network::Backend {
const protocol::String& in_requestId,
protocol::Binary* out_bufferedData) 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 Down Expand Up @@ -89,6 +101,8 @@ class NetworkAgent : public protocol::Network::Backend {
v8::Local<v8::Object>);
std::unordered_map<protocol::String, EventNotifier> event_notifier_map_;
std::map<protocol::String, RequestEntry> requests_;
Environment* env_;
std::shared_ptr<NetworkResourceManager> network_resource_manager_;
};

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

NetworkInspector::NetworkInspector(Environment* env,
v8_inspector::V8Inspector* v8_inspector)
: enabled_(false), env_(env) {
network_agent_ = std::make_unique<NetworkAgent>(this, v8_inspector);
NetworkInspector::NetworkInspector(
Environment* env,
v8_inspector::V8Inspector* v8_inspector,
std::shared_ptr<NetworkResourceManager> network_resource_manager)
: enabled_(false),
env_(env),
network_resource_manager_(std::move(network_resource_manager)) {
network_agent_ = std::make_unique<NetworkAgent>(
this, v8_inspector, env, network_resource_manager_);
}
NetworkInspector::~NetworkInspector() {
network_agent_.reset();
Expand Down
9 changes: 7 additions & 2 deletions src/inspector/network_inspector.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#ifndef SRC_INSPECTOR_NETWORK_INSPECTOR_H_
#define SRC_INSPECTOR_NETWORK_INSPECTOR_H_

#include <memory>
#include "env.h"
#include "network_agent.h"
#include "network_resource_manager.h"

namespace node {
class Environment;
Expand All @@ -11,8 +13,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<NetworkResourceManager> network_resource_manager);
~NetworkInspector();

void Wire(protocol::UberDispatcher* dispatcher);
Expand All @@ -32,6 +36,7 @@ class NetworkInspector {
bool enabled_;
Environment* env_;
std::unique_ptr<NetworkAgent> network_agent_;
std::shared_ptr<NetworkResourceManager> network_resource_manager_;
};

} // namespace inspector
Expand Down
Loading
Loading