mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
inspector: initial support for Network.loadNetworkResource
Fixes: https://github.com/nodejs/node/issues/57873 PR-URL: https://github.com/nodejs/node/pull/58077 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
9d073f32da
commit
d02831ef73
25 changed files with 613 additions and 26 deletions
|
@ -1046,6 +1046,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
|
||||
|
|
|
@ -594,6 +594,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
|
||||
|
|
|
@ -237,6 +237,9 @@ flag is no longer required as WASI is enabled by default.
|
|||
.It Fl -experimental-wasm-modules
|
||||
Enable experimental WebAssembly module 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.
|
||||
.
|
||||
|
|
|
@ -39,6 +39,9 @@ const {
|
|||
} = require('internal/validators');
|
||||
const { isMainThread } = require('worker_threads');
|
||||
const { _debugEnd } = internalBinding('process_methods');
|
||||
const {
|
||||
put,
|
||||
} = require('internal/inspector/network_resources');
|
||||
|
||||
const {
|
||||
Connection,
|
||||
|
@ -221,6 +224,10 @@ const Network = {
|
|||
dataReceived: (params) => broadcastToFrontend('Network.dataReceived', params),
|
||||
};
|
||||
|
||||
const NetworkResources = {
|
||||
put,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
open: inspectorOpen,
|
||||
close: _debugEnd,
|
||||
|
@ -229,4 +236,5 @@ module.exports = {
|
|||
console,
|
||||
Session,
|
||||
Network,
|
||||
NetworkResources,
|
||||
};
|
||||
|
|
27
lib/internal/inspector/network_resources.js
Normal file
27
lib/internal/inspector/network_resources.js
Normal file
|
@ -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
src/inspector/io_agent.cc
Normal file
57
src/inspector/io_agent.cc
Normal file
|
@ -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
src/inspector/io_agent.h
Normal file
30
src/inspector/io_agent.h
Normal file
|
@ -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_
|
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
|
29
src/inspector/network_resource_manager.cc
Normal file
29
src/inspector/network_resource_manager.cc
Normal file
|
@ -0,0 +1,29 @@
|
|||
#include "inspector/network_resource_manager.h"
|
||||
#include <atomic>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace node {
|
||||
namespace inspector {
|
||||
|
||||
void NetworkResourceManager::Put(const std::string& url,
|
||||
const std::string& data) {
|
||||
Mutex::ScopedLock lock(mutex_);
|
||||
resources_[url] = data;
|
||||
}
|
||||
|
||||
std::string NetworkResourceManager::Get(const std::string& url) {
|
||||
Mutex::ScopedLock lock(mutex_);
|
||||
auto it = resources_.find(url);
|
||||
if (it != resources_.end()) return it->second;
|
||||
return {};
|
||||
}
|
||||
|
||||
void NetworkResourceManager::Erase(const std::string& stream_id) {
|
||||
Mutex::ScopedLock lock(mutex_);
|
||||
resources_.erase(stream_id);
|
||||
}
|
||||
|
||||
} // namespace inspector
|
||||
} // namespace node
|
29
src/inspector/network_resource_manager.h
Normal file
29
src/inspector/network_resource_manager.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
// network_resource_manager.h
|
||||
#ifndef SRC_INSPECTOR_NETWORK_RESOURCE_MANAGER_H_
|
||||
#define SRC_INSPECTOR_NETWORK_RESOURCE_MANAGER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "node_mutex.h"
|
||||
|
||||
namespace node {
|
||||
namespace inspector {
|
||||
|
||||
class NetworkResourceManager {
|
||||
public:
|
||||
void Put(const std::string& url, const std::string& data);
|
||||
std::string Get(const std::string& url);
|
||||
|
||||
// Erase resource and mapping by stream id
|
||||
void Erase(const std::string& stream_id);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> resources_;
|
||||
Mutex mutex_; // Protects access to resources_
|
||||
};
|
||||
|
||||
} // namespace inspector
|
||||
} // namespace node
|
||||
|
||||
#endif // SRC_INSPECTOR_NETWORK_RESOURCE_MANAGER_H_
|
|
@ -36,6 +36,10 @@
|
|||
'src/inspector/target_agent.h',
|
||||
'src/inspector/worker_inspector.cc',
|
||||
'src/inspector/worker_inspector.h',
|
||||
'src/inspector/io_agent.cc',
|
||||
'src/inspector/io_agent.h',
|
||||
'src/inspector/network_resource_manager.cc',
|
||||
'src/inspector/network_resource_manager.h',
|
||||
],
|
||||
'node_inspector_generated_sources': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Forward.h',
|
||||
|
@ -51,6 +55,8 @@
|
|||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.h',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Target.cpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Target.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',
|
||||
|
|
|
@ -180,6 +180,11 @@ experimental domain Network
|
|||
# Request / response headers as keys / values of JSON object.
|
||||
type Headers extends object
|
||||
|
||||
type LoadNetworkResourcePageResult extends object
|
||||
properties
|
||||
boolean success
|
||||
optional IO.StreamHandle stream
|
||||
|
||||
# Disables network tracking, prevents network events from being sent to the client.
|
||||
command disable
|
||||
|
||||
|
@ -215,6 +220,13 @@ experimental domain Network
|
|||
returns
|
||||
# Data that has been buffered until streaming is enabled.
|
||||
binary bufferedData
|
||||
# Fetches the resource and returns the content.
|
||||
command loadNetworkResource
|
||||
parameters
|
||||
# URL of the resource to get content for.
|
||||
string url
|
||||
returns
|
||||
LoadNetworkResourcePageResult resource
|
||||
|
||||
# Fired when page is about to send HTTP request.
|
||||
event requestWillBeSent
|
||||
|
@ -321,3 +333,24 @@ experimental domain Target
|
|||
parameters
|
||||
boolean autoAttach
|
||||
boolean waitForDebuggerOnStart
|
||||
domain IO
|
||||
type StreamHandle extends string
|
||||
# Read a chunk of the stream
|
||||
command read
|
||||
parameters
|
||||
# Handle of the stream to read.
|
||||
StreamHandle handle
|
||||
# Seek to the specified offset before reading (if not specified, proceed with offset
|
||||
# following the last read). Some types of streams may only support sequential reads.
|
||||
optional integer offset
|
||||
# Maximum number of bytes to read (left upon the agent discretion if not specified).
|
||||
optional integer size
|
||||
returns
|
||||
# Data that were read.
|
||||
string data
|
||||
# Set if the end-of-file condition occurred while reading.
|
||||
boolean eof
|
||||
command close
|
||||
parameters
|
||||
# Handle of the stream to close.
|
||||
StreamHandle handle
|
||||
|
|
|
@ -60,12 +60,14 @@ ParentInspectorHandle::ParentInspectorHandle(
|
|||
const std::string& url,
|
||||
std::shared_ptr<MainThreadHandle> parent_thread,
|
||||
bool wait_for_connect,
|
||||
const std::string& name)
|
||||
const std::string& name,
|
||||
std::shared_ptr<NetworkResourceManager> network_resource_manager)
|
||||
: id_(id),
|
||||
url_(url),
|
||||
parent_thread_(parent_thread),
|
||||
wait_(wait_for_connect),
|
||||
name_(name) {}
|
||||
name_(name),
|
||||
network_resource_manager_(network_resource_manager) {}
|
||||
|
||||
ParentInspectorHandle::~ParentInspectorHandle() {
|
||||
parent_thread_->Post(
|
||||
|
@ -101,10 +103,13 @@ void WorkerManager::WorkerStarted(uint64_t session_id,
|
|||
}
|
||||
|
||||
std::unique_ptr<ParentInspectorHandle> WorkerManager::NewParentHandle(
|
||||
uint64_t thread_id, const std::string& url, const std::string& name) {
|
||||
uint64_t thread_id,
|
||||
const std::string& url,
|
||||
const std::string& name,
|
||||
std::shared_ptr<NetworkResourceManager> network_resource_manager) {
|
||||
bool wait = !delegates_waiting_on_start_.empty();
|
||||
return std::make_unique<ParentInspectorHandle>(
|
||||
thread_id, url, thread_, wait, name);
|
||||
thread_id, url, thread_, wait, name, network_resource_manager);
|
||||
}
|
||||
|
||||
void WorkerManager::RemoveAttachDelegate(int id) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef SRC_INSPECTOR_WORKER_INSPECTOR_H_
|
||||
#define SRC_INSPECTOR_WORKER_INSPECTOR_H_
|
||||
|
||||
#include "inspector/network_resource_manager.h"
|
||||
#if !HAVE_INSPECTOR
|
||||
#error("This header can only be used when inspector is enabled")
|
||||
#endif
|
||||
|
@ -54,16 +55,18 @@ struct WorkerInfo {
|
|||
|
||||
class ParentInspectorHandle {
|
||||
public:
|
||||
ParentInspectorHandle(uint64_t id,
|
||||
const std::string& url,
|
||||
std::shared_ptr<MainThreadHandle> parent_thread,
|
||||
bool wait_for_connect,
|
||||
const std::string& name);
|
||||
ParentInspectorHandle(
|
||||
uint64_t id,
|
||||
const std::string& url,
|
||||
std::shared_ptr<MainThreadHandle> parent_thread,
|
||||
bool wait_for_connect,
|
||||
const std::string& name,
|
||||
std::shared_ptr<NetworkResourceManager> network_resource_manager);
|
||||
~ParentInspectorHandle();
|
||||
std::unique_ptr<ParentInspectorHandle> NewParentInspectorHandle(
|
||||
uint64_t thread_id, const std::string& url, const std::string& name) {
|
||||
return std::make_unique<ParentInspectorHandle>(
|
||||
thread_id, url, parent_thread_, wait_, name);
|
||||
thread_id, url, parent_thread_, wait_, name, network_resource_manager_);
|
||||
}
|
||||
void WorkerStarted(std::shared_ptr<MainThreadHandle> worker_thread,
|
||||
bool waiting);
|
||||
|
@ -74,6 +77,9 @@ class ParentInspectorHandle {
|
|||
std::unique_ptr<inspector::InspectorSession> Connect(
|
||||
std::unique_ptr<inspector::InspectorSessionDelegate> delegate,
|
||||
bool prevent_shutdown);
|
||||
std::shared_ptr<NetworkResourceManager> GetNetworkResourceManager() {
|
||||
return network_resource_manager_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t id_;
|
||||
|
@ -81,6 +87,7 @@ class ParentInspectorHandle {
|
|||
std::shared_ptr<MainThreadHandle> parent_thread_;
|
||||
bool wait_;
|
||||
std::string name_;
|
||||
std::shared_ptr<NetworkResourceManager> network_resource_manager_;
|
||||
};
|
||||
|
||||
class WorkerManager : public std::enable_shared_from_this<WorkerManager> {
|
||||
|
@ -89,7 +96,10 @@ class WorkerManager : public std::enable_shared_from_this<WorkerManager> {
|
|||
: thread_(thread) {}
|
||||
|
||||
std::unique_ptr<ParentInspectorHandle> NewParentHandle(
|
||||
uint64_t thread_id, const std::string& url, const std::string& name);
|
||||
uint64_t thread_id,
|
||||
const std::string& url,
|
||||
const std::string& name,
|
||||
std::shared_ptr<NetworkResourceManager> network_resource_manager);
|
||||
void WorkerStarted(uint64_t session_id, const WorkerInfo& info, bool waiting);
|
||||
void WorkerFinished(uint64_t session_id);
|
||||
std::unique_ptr<WorkerManagerEventHandle> SetAutoAttach(
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "inspector/worker_agent.h"
|
||||
#include "inspector/worker_inspector.h"
|
||||
#include "inspector_io.h"
|
||||
#include "node.h"
|
||||
#include "node/inspector/protocol/Protocol.h"
|
||||
#include "node_errors.h"
|
||||
#include "node_internals.h"
|
||||
|
@ -238,8 +239,18 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
}
|
||||
runtime_agent_ = std::make_unique<protocol::RuntimeAgent>();
|
||||
runtime_agent_->Wire(node_dispatcher_.get());
|
||||
network_inspector_ =
|
||||
std::make_unique<NetworkInspector>(env, inspector.get());
|
||||
if (env->options()->experimental_inspector_network_resource) {
|
||||
io_agent_ = std::make_unique<protocol::IoAgent>(
|
||||
env->inspector_agent()->GetNetworkResourceManager());
|
||||
io_agent_->Wire(node_dispatcher_.get());
|
||||
network_inspector_ = std::make_unique<NetworkInspector>(
|
||||
env,
|
||||
inspector.get(),
|
||||
env->inspector_agent()->GetNetworkResourceManager());
|
||||
} else {
|
||||
network_inspector_ =
|
||||
std::make_unique<NetworkInspector>(env, inspector.get(), nullptr);
|
||||
}
|
||||
network_inspector_->Wire(node_dispatcher_.get());
|
||||
if (env->options()->experimental_worker_inspection) {
|
||||
target_agent_ = std::make_shared<protocol::TargetAgent>();
|
||||
|
@ -405,6 +416,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
std::unique_ptr<protocol::WorkerAgent> worker_agent_;
|
||||
std::shared_ptr<protocol::TargetAgent> target_agent_;
|
||||
std::unique_ptr<NetworkInspector> network_inspector_;
|
||||
std::shared_ptr<protocol::IoAgent> io_agent_;
|
||||
std::unique_ptr<InspectorSessionDelegate> delegate_;
|
||||
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
||||
std::unique_ptr<UberDispatcher> node_dispatcher_;
|
||||
|
@ -1153,7 +1165,8 @@ std::unique_ptr<ParentInspectorHandle> Agent::GetParentHandle(
|
|||
|
||||
CHECK_NOT_NULL(client_);
|
||||
if (!parent_handle_) {
|
||||
return client_->getWorkerManager()->NewParentHandle(thread_id, url, name);
|
||||
return client_->getWorkerManager()->NewParentHandle(
|
||||
thread_id, url, name, GetNetworkResourceManager());
|
||||
} else {
|
||||
return parent_handle_->NewParentInspectorHandle(thread_id, url, name);
|
||||
}
|
||||
|
@ -1219,6 +1232,17 @@ std::shared_ptr<WorkerManager> Agent::GetWorkerManager() {
|
|||
return client_->getWorkerManager();
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkResourceManager> Agent::GetNetworkResourceManager() {
|
||||
if (parent_handle_) {
|
||||
return parent_handle_->GetNetworkResourceManager();
|
||||
} else if (network_resource_manager_) {
|
||||
return network_resource_manager_;
|
||||
} else {
|
||||
network_resource_manager_ = std::make_shared<NetworkResourceManager>();
|
||||
return network_resource_manager_;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Agent::GetWsUrl() const {
|
||||
if (io_ == nullptr)
|
||||
return "";
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "inspector/network_resource_manager.h"
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#if !HAVE_INSPECTOR
|
||||
|
@ -127,6 +128,7 @@ class Agent {
|
|||
std::shared_ptr<WorkerManager> GetWorkerManager();
|
||||
|
||||
inline Environment* env() const { return parent_env_; }
|
||||
std::shared_ptr<NetworkResourceManager> GetNetworkResourceManager();
|
||||
|
||||
private:
|
||||
void ToggleAsyncHook(v8::Isolate* isolate, v8::Local<v8::Function> fn);
|
||||
|
@ -153,6 +155,7 @@ class Agent {
|
|||
bool network_tracking_enabled_ = false;
|
||||
bool pending_enable_network_tracking = false;
|
||||
bool pending_disable_network_tracking = false;
|
||||
std::shared_ptr<NetworkResourceManager> network_resource_manager_;
|
||||
};
|
||||
|
||||
} // namespace inspector
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "base_object-inl.h"
|
||||
#include "inspector/network_resource_manager.h"
|
||||
#include "inspector/protocol_helper.h"
|
||||
#include "inspector_agent.h"
|
||||
#include "inspector_io.h"
|
||||
|
@ -334,6 +335,18 @@ void Url(const FunctionCallbackInfo<Value>& args) {
|
|||
args.GetReturnValue().Set(OneByteString(env->isolate(), url));
|
||||
}
|
||||
|
||||
void PutNetworkResource(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_GE(args.Length(), 2);
|
||||
CHECK(args[0]->IsString());
|
||||
CHECK(args[1]->IsString());
|
||||
|
||||
Utf8Value url(env->isolate(), args[0].As<String>());
|
||||
Utf8Value data(env->isolate(), args[1].As<String>());
|
||||
|
||||
env->inspector_agent()->GetNetworkResourceManager()->Put(*url, *data);
|
||||
}
|
||||
|
||||
void Initialize(Local<Object> target, Local<Value> unused,
|
||||
Local<Context> context, void* priv) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
|
@ -378,6 +391,7 @@ void Initialize(Local<Object> target, Local<Value> unused,
|
|||
SetMethodNoSideEffect(context, target, "isEnabled", IsEnabled);
|
||||
SetMethod(context, target, "emitProtocolEvent", EmitProtocolEvent);
|
||||
SetMethod(context, target, "setupNetworkTracking", SetupNetworkTracking);
|
||||
SetMethod(context, target, "putNetworkResource", PutNetworkResource);
|
||||
|
||||
Local<String> console_string = FIXED_ONE_BYTE_STRING(isolate, "console");
|
||||
|
||||
|
@ -420,6 +434,7 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|||
registry->Register(JSBindingsConnection<MainThreadConnection>::New);
|
||||
registry->Register(JSBindingsConnection<MainThreadConnection>::Dispatch);
|
||||
registry->Register(JSBindingsConnection<MainThreadConnection>::Disconnect);
|
||||
registry->Register(PutNetworkResource);
|
||||
}
|
||||
|
||||
} // namespace inspector
|
||||
|
|
|
@ -650,6 +650,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||
AddOption("--experimental-worker-inspection",
|
||||
"experimental worker inspection support",
|
||||
&EnvironmentOptions::experimental_worker_inspection);
|
||||
AddOption("--experimental-inspector-network-resource",
|
||||
"experimental load network resources via the inspector",
|
||||
&EnvironmentOptions::experimental_inspector_network_resource);
|
||||
AddOption(
|
||||
"--heap-prof",
|
||||
"Start the V8 heap profiler on start up, and write the heap profile "
|
||||
|
|
|
@ -172,6 +172,7 @@ class EnvironmentOptions : public Options {
|
|||
bool cpu_prof = false;
|
||||
bool experimental_network_inspection = false;
|
||||
bool experimental_worker_inspection = false;
|
||||
bool experimental_inspector_network_resource = false;
|
||||
std::string heap_prof_dir;
|
||||
std::string heap_prof_name;
|
||||
static const uint64_t kDefaultHeapProfInterval = 512 * 1024;
|
||||
|
|
10
test/fixtures/inspector-network-resource/app.js.map
vendored
Normal file
10
test/fixtures/inspector-network-resource/app.js.map
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"version": 3,
|
||||
"file": "app.js",
|
||||
"sourceRoot": "",
|
||||
"sources": [
|
||||
"http://localhost:3000/app.ts"
|
||||
],
|
||||
"names": [],
|
||||
"mappings": ";AAAA,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS;IAC/B,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"
|
||||
}
|
181
test/parallel/test-inspector-network-resource.js
Normal file
181
test/parallel/test-inspector-network-resource.js
Normal file
|
@ -0,0 +1,181 @@
|
|||
// Flags: --inspect=0 --experimental-network-inspection
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
const { NodeInstance } = require('../common/inspector-helper');
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const resourceUrl = 'http://localhost:3000/app.js';
|
||||
const resourcePath = path.join(__dirname, '../fixtures/inspector-network-resource/app.js.map');
|
||||
|
||||
const resourceText = fs.readFileSync(resourcePath, 'utf8');
|
||||
const embedPath = resourcePath.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
||||
const script = `
|
||||
const { NetworkResources } = require('node:inspector');
|
||||
const fs = require('fs');
|
||||
NetworkResources.put('${resourceUrl}', fs.readFileSync('${embedPath}', 'utf8'));
|
||||
console.log('Network resource loaded:', '${resourceUrl}');
|
||||
debugger;
|
||||
`;
|
||||
|
||||
async function setupSessionAndPauseAtEvalLastLine(script) {
|
||||
const instance = new NodeInstance([
|
||||
'--inspect-wait=0',
|
||||
'--experimental-inspector-network-resource',
|
||||
], script);
|
||||
const session = await instance.connectInspectorSession();
|
||||
await session.send({ method: 'NodeRuntime.enable' });
|
||||
await session.waitForNotification('NodeRuntime.waitingForDebugger');
|
||||
await session.send({ method: 'Runtime.enable' });
|
||||
await session.send({ method: 'Debugger.enable' });
|
||||
await session.send({ method: 'Runtime.runIfWaitingForDebugger' });
|
||||
await session.waitForNotification('Debugger.paused');
|
||||
return { instance, session };
|
||||
}
|
||||
|
||||
test('should load and stream a static network resource using loadNetworkResource and IO.read', async () => {
|
||||
const { session } = await setupSessionAndPauseAtEvalLastLine(script);
|
||||
const { resource } = await session.send({
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: { url: resourceUrl },
|
||||
});
|
||||
assert(resource.success, 'Resource should be loaded successfully');
|
||||
assert(resource.stream, 'Resource should have a stream handle');
|
||||
let result = await session.send({ method: 'IO.read', params: { handle: resource.stream } });
|
||||
let data = result.data;
|
||||
let eof = result.eof;
|
||||
let content = '';
|
||||
while (!eof) {
|
||||
content += data;
|
||||
result = await session.send({ method: 'IO.read', params: { handle: resource.stream } });
|
||||
data = result.data;
|
||||
eof = result.eof;
|
||||
}
|
||||
content += data;
|
||||
assert.strictEqual(content, resourceText);
|
||||
await session.send({ method: 'IO.close', params: { handle: resource.stream } });
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
await session.waitForDisconnect();
|
||||
});
|
||||
|
||||
test('should return success: false for missing resource', async () => {
|
||||
const { session } = await setupSessionAndPauseAtEvalLastLine(script);
|
||||
const { resource } = await session.send({
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: { url: 'http://localhost:3000/does-not-exist.js' },
|
||||
});
|
||||
assert.strictEqual(resource.success, false);
|
||||
assert(!resource.stream, 'No stream should be returned for missing resource');
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
await session.waitForDisconnect();
|
||||
});
|
||||
|
||||
test('should error or return empty for wrong stream id', async () => {
|
||||
const { session } = await setupSessionAndPauseAtEvalLastLine(script);
|
||||
const { resource } = await session.send({
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: { url: resourceUrl },
|
||||
});
|
||||
assert(resource.success);
|
||||
const bogus = '999999';
|
||||
const result = await session.send({ method: 'IO.read', params: { handle: bogus } });
|
||||
assert(result.eof, 'Should be eof for bogus stream id');
|
||||
assert.strictEqual(result.data, '');
|
||||
await session.send({ method: 'IO.close', params: { handle: resource.stream } });
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
await session.waitForDisconnect();
|
||||
});
|
||||
|
||||
test('should support IO.read with size and offset', async () => {
|
||||
const { session } = await setupSessionAndPauseAtEvalLastLine(script);
|
||||
const { resource } = await session.send({
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: { url: resourceUrl },
|
||||
});
|
||||
assert(resource.success);
|
||||
assert(resource.stream);
|
||||
let result = await session.send({ method: 'IO.read', params: { handle: resource.stream, size: 5 } });
|
||||
assert.strictEqual(result.data, resourceText.slice(0, 5));
|
||||
result = await session.send({ method: 'IO.read', params: { handle: resource.stream, offset: 5, size: 5 } });
|
||||
assert.strictEqual(result.data, resourceText.slice(5, 10));
|
||||
result = await session.send({ method: 'IO.read', params: { handle: resource.stream, offset: 10 } });
|
||||
assert.strictEqual(result.data, resourceText.slice(10));
|
||||
await session.send({ method: 'IO.close', params: { handle: resource.stream } });
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
await session.waitForDisconnect();
|
||||
});
|
||||
|
||||
test('should load resource put from another thread', async () => {
|
||||
const workerScript = `
|
||||
console.log('this is worker thread');
|
||||
debugger;
|
||||
`;
|
||||
const script = `
|
||||
const { NetworkResources } = require('node:inspector');
|
||||
const fs = require('fs');
|
||||
NetworkResources.put('${resourceUrl}', fs.readFileSync('${embedPath}', 'utf8'));
|
||||
const { Worker } = require('worker_threads');
|
||||
const worker = new Worker(\`${workerScript}\`, {eval: true});
|
||||
`;
|
||||
const instance = new NodeInstance([
|
||||
'--experimental-inspector-network-resource',
|
||||
'--experimental-worker-inspection',
|
||||
'--inspect-brk=0',
|
||||
], script);
|
||||
const session = await instance.connectInspectorSession();
|
||||
await setupInspector(session);
|
||||
await session.waitForNotification('Debugger.paused');
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
await session.waitForNotification('Target.targetCreated');
|
||||
await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } });
|
||||
let sessionId;
|
||||
await session.waitForNotification((notification) => {
|
||||
if (notification.method === 'Target.attachedToTarget') {
|
||||
sessionId = notification.params.sessionId;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
});
|
||||
await setupInspector(session, sessionId);
|
||||
|
||||
await session.waitForNotification('Debugger.paused');
|
||||
|
||||
const { resource } = await session.send({
|
||||
method: 'Network.loadNetworkResource',
|
||||
params: { url: resourceUrl, sessionId },
|
||||
});
|
||||
|
||||
assert(resource.success, 'Resource should be loaded successfully');
|
||||
assert(resource.stream, 'Resource should have a stream handle');
|
||||
let result = await session.send({ method: 'IO.read', params: { handle: resource.stream, sessionId } });
|
||||
let data = result.data;
|
||||
let eof = result.eof;
|
||||
let content = '';
|
||||
while (!eof) {
|
||||
content += data;
|
||||
result = await session.send({ method: 'IO.read', params: { handle: resource.stream, sessionId } });
|
||||
data = result.data;
|
||||
eof = result.eof;
|
||||
}
|
||||
content += data;
|
||||
assert.strictEqual(content, resourceText);
|
||||
await session.send({ method: 'IO.close', params: { handle: resource.stream, sessionId } });
|
||||
|
||||
await session.send({ method: 'Debugger.resume', sessionId });
|
||||
|
||||
await session.waitForDisconnect();
|
||||
|
||||
async function setupInspector(session, sessionId) {
|
||||
await session.send({ method: 'NodeRuntime.enable', sessionId });
|
||||
await session.waitForNotification('NodeRuntime.waitingForDebugger');
|
||||
await session.send({ method: 'Runtime.enable', sessionId });
|
||||
await session.send({ method: 'Debugger.enable', sessionId });
|
||||
await session.send({ method: 'Runtime.runIfWaitingForDebugger', sessionId });
|
||||
}
|
||||
});
|
1
typings/internalBinding/inspector.d.ts
vendored
1
typings/internalBinding/inspector.d.ts
vendored
|
@ -33,4 +33,5 @@ export interface InspectorBinding {
|
|||
console: Console;
|
||||
Connection: InspectorConnectionConstructor;
|
||||
MainThreadConnection: InspectorConnectionConstructor;
|
||||
putNetworkResource: (url: string, resource: string) => void;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue