mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 12:20:27 +01:00
inspector: support for worker inspection in chrome devtools
Fixes: https://github.com/nodejs/node/issues/56343 PR-URL: https://github.com/nodejs/node/pull/56759 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
cfd2021c35
commit
2281a04e5e
|
|
@ -1205,6 +1205,17 @@ added: v22.4.0
|
||||||
|
|
||||||
Enable experimental [`Web Storage`][] support.
|
Enable experimental [`Web Storage`][] support.
|
||||||
|
|
||||||
|
### `--experimental-worker-inspection`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added:
|
||||||
|
- REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1.1 - Active Development
|
||||||
|
|
||||||
|
Enable experimental support for the worker inspection with Chrome DevTools.
|
||||||
|
|
||||||
### `--expose-gc`
|
### `--expose-gc`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,10 @@ class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> {
|
||||||
std::unique_ptr<InspectorSessionDelegate> MakeDelegateThreadSafe(
|
std::unique_ptr<InspectorSessionDelegate> MakeDelegateThreadSafe(
|
||||||
std::unique_ptr<InspectorSessionDelegate> delegate);
|
std::unique_ptr<InspectorSessionDelegate> delegate);
|
||||||
bool Expired();
|
bool Expired();
|
||||||
|
void SetTargetSessionId(int target_session_id) {
|
||||||
|
target_session_id_ = target_session_id;
|
||||||
|
}
|
||||||
|
std::optional<int> GetTargetSessionId() { return target_session_id_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
@ -65,6 +69,7 @@ class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> {
|
||||||
Mutex block_lock_;
|
Mutex block_lock_;
|
||||||
int next_session_id_ = 0;
|
int next_session_id_ = 0;
|
||||||
std::atomic_int next_object_id_ = {1};
|
std::atomic_int next_object_id_ = {1};
|
||||||
|
std::optional<int> target_session_id_ = std::nullopt;
|
||||||
|
|
||||||
friend class MainThreadInterface;
|
friend class MainThreadInterface;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@
|
||||||
'src/inspector/network_inspector.h',
|
'src/inspector/network_inspector.h',
|
||||||
'src/inspector/network_agent.cc',
|
'src/inspector/network_agent.cc',
|
||||||
'src/inspector/network_agent.h',
|
'src/inspector/network_agent.h',
|
||||||
|
'src/inspector/target_agent.cc',
|
||||||
|
'src/inspector/target_agent.h',
|
||||||
'src/inspector/worker_inspector.cc',
|
'src/inspector/worker_inspector.cc',
|
||||||
'src/inspector/worker_inspector.h',
|
'src/inspector/worker_inspector.h',
|
||||||
],
|
],
|
||||||
|
|
@ -47,6 +49,8 @@
|
||||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h',
|
'<(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.cpp',
|
||||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.h',
|
'<(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',
|
||||||
],
|
],
|
||||||
'node_protocol_files': [
|
'node_protocol_files': [
|
||||||
'<(protocol_tool_path)/lib/Forward_h.template',
|
'<(protocol_tool_path)/lib/Forward_h.template',
|
||||||
|
|
|
||||||
|
|
@ -249,3 +249,28 @@ experimental domain NodeRuntime
|
||||||
# This event is fired when the runtime is waiting for the debugger. For
|
# This event is fired when the runtime is waiting for the debugger. For
|
||||||
# example, when inspector.waitingForDebugger is called
|
# example, when inspector.waitingForDebugger is called
|
||||||
event waitingForDebugger
|
event waitingForDebugger
|
||||||
|
|
||||||
|
# https://chromedevtools.github.io/devtools-protocol/1-3/Target/
|
||||||
|
experimental domain Target
|
||||||
|
type SessionID extends string
|
||||||
|
type TargetID extends string
|
||||||
|
type TargetInfo extends object
|
||||||
|
properties
|
||||||
|
TargetID targetId
|
||||||
|
string type
|
||||||
|
string title
|
||||||
|
string url
|
||||||
|
boolean attached
|
||||||
|
boolean canAccessOpener
|
||||||
|
event targetCreated
|
||||||
|
parameters
|
||||||
|
TargetInfo targetInfo
|
||||||
|
event attachedToTarget
|
||||||
|
parameters
|
||||||
|
SessionID sessionId
|
||||||
|
TargetInfo targetInfo
|
||||||
|
boolean waitingForDebugger
|
||||||
|
command setAutoAttach
|
||||||
|
parameters
|
||||||
|
boolean autoAttach
|
||||||
|
boolean waitForDebuggerOnStart
|
||||||
|
|
|
||||||
136
src/inspector/target_agent.cc
Normal file
136
src/inspector/target_agent.cc
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
#include "target_agent.h"
|
||||||
|
#include <string_view>
|
||||||
|
#include "crdtp/dispatch.h"
|
||||||
|
#include "inspector/worker_inspector.h"
|
||||||
|
#include "main_thread_interface.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
namespace inspector {
|
||||||
|
namespace protocol {
|
||||||
|
|
||||||
|
std::unordered_map<int, std::shared_ptr<MainThreadHandle>>
|
||||||
|
TargetAgent::target_session_id_worker_map_ =
|
||||||
|
std::unordered_map<int, std::shared_ptr<MainThreadHandle>>();
|
||||||
|
int TargetAgent::next_session_id_ = 1;
|
||||||
|
class WorkerTargetDelegate : public WorkerDelegate {
|
||||||
|
public:
|
||||||
|
explicit WorkerTargetDelegate(std::shared_ptr<TargetAgent> target_agent)
|
||||||
|
: target_agent_(target_agent) {}
|
||||||
|
|
||||||
|
void WorkerCreated(const std::string& title,
|
||||||
|
const std::string& url,
|
||||||
|
bool waiting,
|
||||||
|
std::shared_ptr<MainThreadHandle> worker) override {
|
||||||
|
target_agent_->createAndAttachIfNecessary(worker, title, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::shared_ptr<TargetAgent> target_agent_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Target::TargetInfo> createTargetInfo(
|
||||||
|
const std::string_view target_id,
|
||||||
|
const std::string_view type,
|
||||||
|
const std::string_view title,
|
||||||
|
const std::string_view url) {
|
||||||
|
return Target::TargetInfo::create()
|
||||||
|
.setTargetId(std::string(target_id))
|
||||||
|
.setType(std::string(type))
|
||||||
|
.setTitle(std::string(title))
|
||||||
|
.setUrl(std::string(url))
|
||||||
|
.setAttached(false)
|
||||||
|
.setCanAccessOpener(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetAgent::Wire(UberDispatcher* dispatcher) {
|
||||||
|
frontend_ = std::make_unique<Target::Frontend>(dispatcher->channel());
|
||||||
|
Target::Dispatcher::wire(dispatcher, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetAgent::createAndAttachIfNecessary(
|
||||||
|
std::shared_ptr<MainThreadHandle> worker,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& url) {
|
||||||
|
std::string target_id = std::to_string(getNextTargetId());
|
||||||
|
std::string type = "node_worker";
|
||||||
|
|
||||||
|
targetCreated(target_id, type, title, url);
|
||||||
|
bool attached = false;
|
||||||
|
if (auto_attach_) {
|
||||||
|
attached = true;
|
||||||
|
attachedToTarget(worker, target_id, type, title, url);
|
||||||
|
}
|
||||||
|
targets_.push_back({target_id, type, title, url, worker, attached});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetAgent::listenWorker(std::weak_ptr<WorkerManager> worker_manager) {
|
||||||
|
auto manager = worker_manager.lock();
|
||||||
|
if (!manager) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::unique_ptr<WorkerDelegate> delegate(
|
||||||
|
new WorkerTargetDelegate(shared_from_this()));
|
||||||
|
worker_event_handle_ = manager->SetAutoAttach(std::move(delegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetAgent::reset() {
|
||||||
|
if (worker_event_handle_) {
|
||||||
|
worker_event_handle_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetAgent::targetCreated(const std::string_view target_id,
|
||||||
|
const std::string_view type,
|
||||||
|
const std::string_view title,
|
||||||
|
const std::string_view url) {
|
||||||
|
frontend_->targetCreated(createTargetInfo(target_id, type, title, url));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TargetAgent::getNextSessionId() {
|
||||||
|
return next_session_id_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TargetAgent::getNextTargetId() {
|
||||||
|
return next_target_id_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TargetAgent::attachedToTarget(std::shared_ptr<MainThreadHandle> worker,
|
||||||
|
const std::string& target_id,
|
||||||
|
const std::string& type,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& url) {
|
||||||
|
int session_id = getNextSessionId();
|
||||||
|
target_session_id_worker_map_[session_id] = worker;
|
||||||
|
worker->SetTargetSessionId(session_id);
|
||||||
|
frontend_->attachedToTarget(std::to_string(session_id),
|
||||||
|
createTargetInfo(target_id, type, title, url),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(islandryu): Currently, setAutoAttach applies the main thread's value to
|
||||||
|
// all threads. Modify it to be managed per worker thread.
|
||||||
|
crdtp::DispatchResponse TargetAgent::setAutoAttach(
|
||||||
|
bool auto_attach, bool wait_for_debugger_on_start) {
|
||||||
|
auto_attach_ = auto_attach;
|
||||||
|
wait_for_debugger_on_start_ = wait_for_debugger_on_start;
|
||||||
|
|
||||||
|
if (auto_attach) {
|
||||||
|
for (auto& target : targets_) {
|
||||||
|
if (!target.attached) {
|
||||||
|
target.attached = true;
|
||||||
|
attachedToTarget(target.worker,
|
||||||
|
target.target_id,
|
||||||
|
target.type,
|
||||||
|
target.title,
|
||||||
|
target.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DispatchResponse::Success();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace protocol
|
||||||
|
} // namespace inspector
|
||||||
|
} // namespace node
|
||||||
75
src/inspector/target_agent.h
Normal file
75
src/inspector/target_agent.h
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
#ifndef SRC_INSPECTOR_TARGET_AGENT_H_
|
||||||
|
#define SRC_INSPECTOR_TARGET_AGENT_H_
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include "inspector/worker_inspector.h"
|
||||||
|
#include "node/inspector/protocol/Target.h"
|
||||||
|
|
||||||
|
namespace node {
|
||||||
|
|
||||||
|
namespace inspector {
|
||||||
|
class TargetInspector;
|
||||||
|
|
||||||
|
namespace protocol {
|
||||||
|
|
||||||
|
struct TargetInfo {
|
||||||
|
std::string target_id;
|
||||||
|
std::string type;
|
||||||
|
std::string title;
|
||||||
|
std::string url;
|
||||||
|
std::shared_ptr<MainThreadHandle> worker;
|
||||||
|
bool attached;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TargetAgent : public Target::Backend,
|
||||||
|
public std::enable_shared_from_this<TargetAgent> {
|
||||||
|
public:
|
||||||
|
void Wire(UberDispatcher* dispatcher);
|
||||||
|
|
||||||
|
void createAndAttachIfNecessary(std::shared_ptr<MainThreadHandle> worker,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& url);
|
||||||
|
|
||||||
|
DispatchResponse setAutoAttach(bool auto_attach,
|
||||||
|
bool wait_for_debugger_on_start) override;
|
||||||
|
|
||||||
|
void listenWorker(std::weak_ptr<WorkerManager> worker_manager);
|
||||||
|
void reset();
|
||||||
|
static std::unordered_map<int, std::shared_ptr<MainThreadHandle>>
|
||||||
|
target_session_id_worker_map_;
|
||||||
|
|
||||||
|
bool isThisThread(MainThreadHandle* worker) { return worker == main_thread_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int getNextTargetId();
|
||||||
|
int getNextSessionId();
|
||||||
|
void targetCreated(const std::string_view target_id,
|
||||||
|
const std::string_view type,
|
||||||
|
const std::string_view title,
|
||||||
|
const std::string_view url);
|
||||||
|
void attachedToTarget(std::shared_ptr<MainThreadHandle> worker,
|
||||||
|
const std::string& target_id,
|
||||||
|
const std::string& type,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& url);
|
||||||
|
|
||||||
|
std::shared_ptr<Target::Frontend> frontend_;
|
||||||
|
std::weak_ptr<WorkerManager> worker_manager_;
|
||||||
|
static int next_session_id_;
|
||||||
|
int next_target_id_ = 1;
|
||||||
|
std::unique_ptr<WorkerManagerEventHandle> worker_event_handle_ = nullptr;
|
||||||
|
bool auto_attach_ = false;
|
||||||
|
// TODO(islandryu): If false, implement it so that each thread does not wait
|
||||||
|
// for the worker to execute.
|
||||||
|
bool wait_for_debugger_on_start_ = true;
|
||||||
|
std::vector<TargetInfo> targets_;
|
||||||
|
MainThreadHandle* main_thread_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace protocol
|
||||||
|
} // namespace inspector
|
||||||
|
} // namespace node
|
||||||
|
|
||||||
|
#endif // SRC_INSPECTOR_TARGET_AGENT_H_
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "inspector/node_string.h"
|
#include "inspector/node_string.h"
|
||||||
#include "inspector/protocol_helper.h"
|
#include "inspector/protocol_helper.h"
|
||||||
#include "inspector/runtime_agent.h"
|
#include "inspector/runtime_agent.h"
|
||||||
|
#include "inspector/target_agent.h"
|
||||||
#include "inspector/tracing_agent.h"
|
#include "inspector/tracing_agent.h"
|
||||||
#include "inspector/worker_agent.h"
|
#include "inspector/worker_agent.h"
|
||||||
#include "inspector/worker_inspector.h"
|
#include "inspector/worker_inspector.h"
|
||||||
|
|
@ -218,9 +219,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
const std::unique_ptr<V8Inspector>& inspector,
|
const std::unique_ptr<V8Inspector>& inspector,
|
||||||
std::shared_ptr<WorkerManager> worker_manager,
|
std::shared_ptr<WorkerManager> worker_manager,
|
||||||
std::unique_ptr<InspectorSessionDelegate> delegate,
|
std::unique_ptr<InspectorSessionDelegate> delegate,
|
||||||
std::shared_ptr<MainThreadHandle> main_thread_,
|
std::shared_ptr<MainThreadHandle> main_thread,
|
||||||
bool prevent_shutdown)
|
bool prevent_shutdown)
|
||||||
: delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown),
|
: delegate_(std::move(delegate)),
|
||||||
|
main_thread_(main_thread),
|
||||||
|
prevent_shutdown_(prevent_shutdown),
|
||||||
retaining_context_(false) {
|
retaining_context_(false) {
|
||||||
session_ = inspector->connect(CONTEXT_GROUP_ID,
|
session_ = inspector->connect(CONTEXT_GROUP_ID,
|
||||||
this,
|
this,
|
||||||
|
|
@ -239,6 +242,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
network_inspector_ =
|
network_inspector_ =
|
||||||
std::make_unique<NetworkInspector>(env, inspector.get());
|
std::make_unique<NetworkInspector>(env, inspector.get());
|
||||||
network_inspector_->Wire(node_dispatcher_.get());
|
network_inspector_->Wire(node_dispatcher_.get());
|
||||||
|
if (env->options()->experimental_worker_inspection) {
|
||||||
|
target_agent_ = std::make_shared<protocol::TargetAgent>();
|
||||||
|
target_agent_->Wire(node_dispatcher_.get());
|
||||||
|
target_agent_->listenWorker(worker_manager);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~ChannelImpl() override {
|
~ChannelImpl() override {
|
||||||
|
|
@ -252,6 +260,9 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
runtime_agent_.reset(); // Dispose before the dispatchers
|
runtime_agent_.reset(); // Dispose before the dispatchers
|
||||||
network_inspector_->Disable();
|
network_inspector_->Disable();
|
||||||
network_inspector_.reset(); // Dispose before the dispatchers
|
network_inspector_.reset(); // Dispose before the dispatchers
|
||||||
|
if (target_agent_) {
|
||||||
|
target_agent_->reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void emitNotificationFromBackend(v8::Local<v8::Context> context,
|
void emitNotificationFromBackend(v8::Local<v8::Context> context,
|
||||||
|
|
@ -334,6 +345,15 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
// crdtp::FrontendChannel
|
// crdtp::FrontendChannel
|
||||||
void FlushProtocolNotifications() override {}
|
void FlushProtocolNotifications() override {}
|
||||||
|
|
||||||
|
std::string serializeToJSON(std::unique_ptr<Serializable> message) {
|
||||||
|
std::vector<uint8_t> cbor = message->Serialize();
|
||||||
|
std::string json;
|
||||||
|
crdtp::Status status = ConvertCBORToJSON(crdtp::SpanFrom(cbor), &json);
|
||||||
|
CHECK(status.ok());
|
||||||
|
USE(status);
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
void sendMessageToFrontend(const StringView& message) {
|
void sendMessageToFrontend(const StringView& message) {
|
||||||
if (per_process::enabled_debug_list.enabled(
|
if (per_process::enabled_debug_list.enabled(
|
||||||
DebugCategory::INSPECTOR_SERVER)) {
|
DebugCategory::INSPECTOR_SERVER)) {
|
||||||
|
|
@ -342,7 +362,18 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
"[inspector send] %s\n",
|
"[inspector send] %s\n",
|
||||||
raw_message);
|
raw_message);
|
||||||
}
|
}
|
||||||
delegate_->SendMessageToFrontend(message);
|
std::optional<int> target_session_id = main_thread_->GetTargetSessionId();
|
||||||
|
if (target_session_id.has_value()) {
|
||||||
|
std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
|
||||||
|
std::unique_ptr<protocol::DictionaryValue> value =
|
||||||
|
protocol::DictionaryValue::cast(JsonUtil::parseJSON(raw_message));
|
||||||
|
std::string target_session_id_str = std::to_string(*target_session_id);
|
||||||
|
value->setString("sessionId", target_session_id_str);
|
||||||
|
std::string json = serializeToJSON(std::move(value));
|
||||||
|
delegate_->SendMessageToFrontend(Utf8ToStringView(json)->string());
|
||||||
|
} else {
|
||||||
|
delegate_->SendMessageToFrontend(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMessageToFrontend(const std::string& message) {
|
void sendMessageToFrontend(const std::string& message) {
|
||||||
|
|
@ -352,24 +383,14 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
// crdtp::FrontendChannel
|
// crdtp::FrontendChannel
|
||||||
void SendProtocolResponse(int callId,
|
void SendProtocolResponse(int callId,
|
||||||
std::unique_ptr<Serializable> message) override {
|
std::unique_ptr<Serializable> message) override {
|
||||||
std::vector<uint8_t> cbor = message->Serialize();
|
std::string json = serializeToJSON(std::move(message));
|
||||||
std::string json;
|
|
||||||
crdtp::Status status = ConvertCBORToJSON(crdtp::SpanFrom(cbor), &json);
|
|
||||||
DCHECK(status.ok());
|
|
||||||
USE(status);
|
|
||||||
|
|
||||||
sendMessageToFrontend(json);
|
sendMessageToFrontend(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
// crdtp::FrontendChannel
|
// crdtp::FrontendChannel
|
||||||
void SendProtocolNotification(
|
void SendProtocolNotification(
|
||||||
std::unique_ptr<Serializable> message) override {
|
std::unique_ptr<Serializable> message) override {
|
||||||
std::vector<uint8_t> cbor = message->Serialize();
|
std::string json = serializeToJSON(std::move(message));
|
||||||
std::string json;
|
|
||||||
crdtp::Status status = ConvertCBORToJSON(crdtp::SpanFrom(cbor), &json);
|
|
||||||
DCHECK(status.ok());
|
|
||||||
USE(status);
|
|
||||||
|
|
||||||
sendMessageToFrontend(json);
|
sendMessageToFrontend(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -383,10 +404,12 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
||||||
std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
|
std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
|
||||||
std::unique_ptr<protocol::TracingAgent> tracing_agent_;
|
std::unique_ptr<protocol::TracingAgent> tracing_agent_;
|
||||||
std::unique_ptr<protocol::WorkerAgent> worker_agent_;
|
std::unique_ptr<protocol::WorkerAgent> worker_agent_;
|
||||||
|
std::shared_ptr<protocol::TargetAgent> target_agent_;
|
||||||
std::unique_ptr<NetworkInspector> network_inspector_;
|
std::unique_ptr<NetworkInspector> network_inspector_;
|
||||||
std::unique_ptr<InspectorSessionDelegate> delegate_;
|
std::unique_ptr<InspectorSessionDelegate> delegate_;
|
||||||
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
||||||
std::unique_ptr<UberDispatcher> node_dispatcher_;
|
std::unique_ptr<UberDispatcher> node_dispatcher_;
|
||||||
|
std::shared_ptr<MainThreadHandle> main_thread_;
|
||||||
bool prevent_shutdown_;
|
bool prevent_shutdown_;
|
||||||
bool retaining_context_;
|
bool retaining_context_;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@
|
||||||
#include "crypto/crypto_util.h"
|
#include "crypto/crypto_util.h"
|
||||||
#include "debug_utils-inl.h"
|
#include "debug_utils-inl.h"
|
||||||
#include "inspector/main_thread_interface.h"
|
#include "inspector/main_thread_interface.h"
|
||||||
|
#include "inspector/node_json.h"
|
||||||
#include "inspector/node_string.h"
|
#include "inspector/node_string.h"
|
||||||
|
#include "inspector/target_agent.h"
|
||||||
#include "inspector_socket_server.h"
|
#include "inspector_socket_server.h"
|
||||||
#include "ncrypto.h"
|
#include "ncrypto.h"
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
|
@ -218,6 +220,7 @@ class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
|
||||||
void StartSession(int session_id, const std::string& target_id) override;
|
void StartSession(int session_id, const std::string& target_id) override;
|
||||||
void MessageReceived(int session_id, const std::string& message) override;
|
void MessageReceived(int session_id, const std::string& message) override;
|
||||||
void EndSession(int session_id) override;
|
void EndSession(int session_id) override;
|
||||||
|
std::optional<std::string> GetTargetSessionId(const std::string& message);
|
||||||
|
|
||||||
std::vector<std::string> GetTargetIds() override;
|
std::vector<std::string> GetTargetIds() override;
|
||||||
std::string GetTargetTitle(const std::string& id) override;
|
std::string GetTargetTitle(const std::string& id) override;
|
||||||
|
|
@ -342,20 +345,72 @@ InspectorIoDelegate::InspectorIoDelegate(
|
||||||
|
|
||||||
void InspectorIoDelegate::StartSession(int session_id,
|
void InspectorIoDelegate::StartSession(int session_id,
|
||||||
const std::string& target_id) {
|
const std::string& target_id) {
|
||||||
auto session = main_thread_->Connect(
|
fprintf(stderr, "Debugger attached.\n");
|
||||||
std::unique_ptr<InspectorSessionDelegate>(
|
}
|
||||||
new IoSessionDelegate(request_queue_->handle(), session_id)), true);
|
|
||||||
if (session) {
|
std::optional<std::string> InspectorIoDelegate::GetTargetSessionId(
|
||||||
sessions_[session_id] = std::move(session);
|
const std::string& message) {
|
||||||
fprintf(stderr, "Debugger attached.\n");
|
std::string_view view(message.data(), message.size());
|
||||||
|
std::unique_ptr<protocol::DictionaryValue> value =
|
||||||
|
protocol::DictionaryValue::cast(JsonUtil::parseJSON(view));
|
||||||
|
protocol::String target_session_id;
|
||||||
|
protocol::Value* target_session_id_value = value->get("sessionId");
|
||||||
|
if (target_session_id_value) {
|
||||||
|
target_session_id_value->asString(&target_session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!target_session_id.empty()) {
|
||||||
|
return target_session_id;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorIoDelegate::MessageReceived(int session_id,
|
void InspectorIoDelegate::MessageReceived(int session_id,
|
||||||
const std::string& message) {
|
const std::string& message) {
|
||||||
auto session = sessions_.find(session_id);
|
std::optional<std::string> target_session_id_str =
|
||||||
if (session != sessions_.end())
|
GetTargetSessionId(message);
|
||||||
|
std::shared_ptr<MainThreadHandle> worker = nullptr;
|
||||||
|
int merged_session_id = session_id;
|
||||||
|
if (target_session_id_str) {
|
||||||
|
bool is_number = std::all_of(target_session_id_str->begin(),
|
||||||
|
target_session_id_str->end(),
|
||||||
|
::isdigit);
|
||||||
|
if (is_number) {
|
||||||
|
int target_session_id = std::stoi(*target_session_id_str);
|
||||||
|
worker = protocol::TargetAgent::target_session_id_worker_map_
|
||||||
|
[target_session_id];
|
||||||
|
if (worker) {
|
||||||
|
merged_session_id += target_session_id << 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto session = sessions_.find(merged_session_id);
|
||||||
|
|
||||||
|
if (session == sessions_.end()) {
|
||||||
|
std::unique_ptr<InspectorSession> session;
|
||||||
|
if (worker) {
|
||||||
|
session = worker->Connect(
|
||||||
|
std::unique_ptr<InspectorSessionDelegate>(
|
||||||
|
new IoSessionDelegate(request_queue_->handle(), session_id)),
|
||||||
|
true);
|
||||||
|
} else {
|
||||||
|
session = main_thread_->Connect(
|
||||||
|
std::unique_ptr<InspectorSessionDelegate>(
|
||||||
|
new IoSessionDelegate(request_queue_->handle(), session_id)),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
sessions_[merged_session_id] = std::move(session);
|
||||||
|
sessions_[merged_session_id]->Dispatch(
|
||||||
|
Utf8ToStringView(message)->string());
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Failed to connect to inspector session.\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
session->second->Dispatch(Utf8ToStringView(message)->string());
|
session->second->Dispatch(Utf8ToStringView(message)->string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InspectorIoDelegate::EndSession(int session_id) {
|
void InspectorIoDelegate::EndSession(int session_id) {
|
||||||
|
|
|
||||||
|
|
@ -642,6 +642,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
||||||
AddOption("--experimental-network-inspection",
|
AddOption("--experimental-network-inspection",
|
||||||
"experimental network inspection support",
|
"experimental network inspection support",
|
||||||
&EnvironmentOptions::experimental_network_inspection);
|
&EnvironmentOptions::experimental_network_inspection);
|
||||||
|
AddOption("--experimental-worker-inspection",
|
||||||
|
"experimental worker inspection support",
|
||||||
|
&EnvironmentOptions::experimental_worker_inspection);
|
||||||
AddOption(
|
AddOption(
|
||||||
"--heap-prof",
|
"--heap-prof",
|
||||||
"Start the V8 heap profiler on start up, and write the heap profile "
|
"Start the V8 heap profiler on start up, and write the heap profile "
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ class EnvironmentOptions : public Options {
|
||||||
std::string cpu_prof_name;
|
std::string cpu_prof_name;
|
||||||
bool cpu_prof = false;
|
bool cpu_prof = false;
|
||||||
bool experimental_network_inspection = false;
|
bool experimental_network_inspection = false;
|
||||||
|
bool experimental_worker_inspection = false;
|
||||||
std::string heap_prof_dir;
|
std::string heap_prof_dir;
|
||||||
std::string heap_prof_name;
|
std::string heap_prof_name;
|
||||||
static const uint64_t kDefaultHeapProfInterval = 512 * 1024;
|
static const uint64_t kDefaultHeapProfInterval = 512 * 1024;
|
||||||
|
|
|
||||||
3
test/fixtures/inspect-worker/index.js
vendored
Normal file
3
test/fixtures/inspect-worker/index.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
const { Worker } = require('worker_threads');
|
||||||
|
|
||||||
|
new Worker(__dirname + '/worker.js', { type: 'module' });
|
||||||
4
test/fixtures/inspect-worker/worker.js
vendored
Normal file
4
test/fixtures/inspect-worker/worker.js
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
console.log("worker thread");
|
||||||
|
process.on('exit', () => {
|
||||||
|
console.log('Worker1: Exiting...');
|
||||||
|
});
|
||||||
75
test/parallel/test-inspector-worker-target.js
Normal file
75
test/parallel/test-inspector-worker-target.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
|
||||||
|
common.skipIfInspectorDisabled();
|
||||||
|
|
||||||
|
const { NodeInstance } = require('../common/inspector-helper.js');
|
||||||
|
|
||||||
|
async function setupInspector(session, sessionId = undefined) {
|
||||||
|
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 });
|
||||||
|
await session.send({ method: 'NodeRuntime.disable', sessionId });
|
||||||
|
await session.waitForNotification((notification) => {
|
||||||
|
return notification.method === 'Debugger.scriptParsed' &&
|
||||||
|
notification.params.url === 'node:internal/bootstrap/realm' &&
|
||||||
|
notification.sessionId === sessionId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function test(isSetAutoAttachBeforeExecution) {
|
||||||
|
const child = new NodeInstance(['--inspect-brk=0', '--experimental-worker-inspection'],
|
||||||
|
'',
|
||||||
|
fixtures.path('inspect-worker/index.js')
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const session = await child.connectInspectorSession();
|
||||||
|
await setupInspector(session);
|
||||||
|
|
||||||
|
if (isSetAutoAttachBeforeExecution) {
|
||||||
|
await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } });
|
||||||
|
}
|
||||||
|
await session.waitForNotification('Debugger.paused');
|
||||||
|
await session.send({ method: 'Debugger.resume' });
|
||||||
|
|
||||||
|
const sessionId = '1';
|
||||||
|
await session.waitForNotification('Target.targetCreated');
|
||||||
|
|
||||||
|
if (!isSetAutoAttachBeforeExecution) {
|
||||||
|
await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } });
|
||||||
|
}
|
||||||
|
await session.waitForNotification((notification) => {
|
||||||
|
return notification.method === 'Target.attachedToTarget' &&
|
||||||
|
notification.params.sessionId === sessionId;
|
||||||
|
});
|
||||||
|
await setupInspector(session, sessionId);
|
||||||
|
await session.waitForNotification('Debugger.paused');
|
||||||
|
await session.send({ method: 'Debugger.resume', sessionId });
|
||||||
|
await session.waitForDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
test(true).then(common.mustCall());
|
||||||
|
test(false).then(common.mustCall());
|
||||||
|
|
||||||
|
function withPermissionOptionTest() {
|
||||||
|
const permissionErrorThrow = common.mustCall();
|
||||||
|
const child = new NodeInstance(['--inspect-brk=0', '--experimental-worker-inspection', '--permission'],
|
||||||
|
'',
|
||||||
|
fixtures.path('inspect-worker/index.js'),
|
||||||
|
{
|
||||||
|
log: (_, msg) => {
|
||||||
|
if (msg.includes('Access to this API has been restricted')) {
|
||||||
|
permissionErrorThrow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
child.connectInspectorSession();
|
||||||
|
}
|
||||||
|
withPermissionOptionTest();
|
||||||
Loading…
Reference in New Issue
Block a user