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
1 change: 1 addition & 0 deletions node.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
'sources': [
'src/inspector_agent.cc',
'src/inspector_io.cc',
'src/inspector_js_api.cc',
'src/inspector_socket.cc',
'src/inspector_socket_server.cc',
'src/inspector_agent.h',
Expand Down
334 changes: 5 additions & 329 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#include "inspector_agent.h"

#include "inspector_io.h"
#include "base-object.h"
#include "base-object-inl.h"
#include "node_internals.h"
#include "v8-inspector.h"
#include "v8-platform.h"
#include "zlib.h"

#include "libplatform/libplatform.h"

Expand All @@ -16,6 +13,7 @@
#include <vector>

#ifdef __POSIX__
#include <limits.h>
#include <unistd.h> // setuid, getuid
#endif // __POSIX__

Expand All @@ -26,23 +24,12 @@ namespace {
using node::FatalError;

using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::NewStringType;
using v8::Object;
using v8::Persistent;
using v8::String;
using v8::Undefined;
using v8::Value;

using v8_inspector::StringBuffer;
Expand Down Expand Up @@ -193,173 +180,6 @@ static int StartDebugSignalHandler() {
}
#endif // _WIN32

class JSBindingsConnection : public AsyncWrap {
public:
class JSBindingsSessionDelegate : public InspectorSessionDelegate {
public:
JSBindingsSessionDelegate(Environment* env,
JSBindingsConnection* connection)
: env_(env),
connection_(connection) {
}

bool WaitForFrontendMessageWhilePaused() override {
return false;
}

void SendMessageToFrontend(const v8_inspector::StringView& message)
override {
Isolate* isolate = env_->isolate();
HandleScope handle_scope(isolate);
Context::Scope context_scope(env_->context());
MaybeLocal<String> v8string =
String::NewFromTwoByte(isolate, message.characters16(),
NewStringType::kNormal, message.length());
Local<Value> argument = v8string.ToLocalChecked().As<Value>();
connection_->OnMessage(argument);
}

void Disconnect() {
Agent* agent = env_->inspector_agent();
if (agent->delegate() == this)
agent->Disconnect();
}

private:
Environment* env_;
JSBindingsConnection* connection_;
};

JSBindingsConnection(Environment* env,
Local<Object> wrap,
Local<Function> callback)
: AsyncWrap(env, wrap, PROVIDER_INSPECTORJSBINDING),
delegate_(env, this),
callback_(env->isolate(), callback) {
Wrap(wrap, this);

Agent* inspector = env->inspector_agent();
if (inspector->delegate() != nullptr) {
env->ThrowTypeError("Session is already attached");
return;
}
inspector->Connect(&delegate_);
}

~JSBindingsConnection() override {
callback_.Reset();
}

void OnMessage(Local<Value> value) {
MakeCallback(callback_.Get(env()->isolate()), 1, &value);
}

void CheckIsCurrent() {
Agent* inspector = env()->inspector_agent();
CHECK_EQ(&delegate_, inspector->delegate());
}

static void New(const FunctionCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
if (!info[0]->IsFunction()) {
env->ThrowTypeError("Message callback is required");
return;
}
Local<Function> callback = info[0].As<Function>();
new JSBindingsConnection(env, info.This(), callback);
}

void Disconnect() {
delegate_.Disconnect();
if (!persistent().IsEmpty()) {
ClearWrap(object());
persistent().Reset();
}
delete this;
}

static void Disconnect(const FunctionCallbackInfo<Value>& info) {
JSBindingsConnection* session;
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
session->Disconnect();
}

static void Dispatch(const FunctionCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
JSBindingsConnection* session;
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
if (!info[0]->IsString()) {
env->ThrowTypeError("Inspector message must be a string");
return;
}

session->CheckIsCurrent();
Agent* inspector = env->inspector_agent();
inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string());
}

size_t self_size() const override { return sizeof(*this); }

private:
JSBindingsSessionDelegate delegate_;
Persistent<Function> callback_;
};

void InspectorConsoleCall(const v8::FunctionCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
CHECK_LT(2, info.Length());
std::vector<Local<Value>> call_args;
for (int i = 3; i < info.Length(); ++i) {
call_args.push_back(info[i]);
}
Environment* env = Environment::GetCurrent(isolate);
if (env->inspector_agent()->enabled()) {
Local<Value> inspector_method = info[0];
CHECK(inspector_method->IsFunction());
Local<Value> config_value = info[2];
CHECK(config_value->IsObject());
Local<Object> config_object = config_value.As<Object>();
Local<String> in_call_key = FIXED_ONE_BYTE_STRING(isolate, "in_call");
if (!config_object->Has(context, in_call_key).FromMaybe(false)) {
CHECK(config_object->Set(context,
in_call_key,
v8::True(isolate)).FromJust());
CHECK(!inspector_method.As<Function>()->Call(context,
info.Holder(),
call_args.size(),
call_args.data()).IsEmpty());
}
CHECK(config_object->Delete(context, in_call_key).FromJust());
}

Local<Value> node_method = info[1];
CHECK(node_method->IsFunction());
node_method.As<Function>()->Call(context,
info.Holder(),
call_args.size(),
call_args.data()).FromMaybe(Local<Value>());
}

void CallAndPauseOnStart(
const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK_GT(args.Length(), 1);
CHECK(args[0]->IsFunction());
std::vector<v8::Local<v8::Value>> call_args;
for (int i = 2; i < args.Length(); i++) {
call_args.push_back(args[i]);
}

env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start");
v8::MaybeLocal<v8::Value> retval =
args[0].As<v8::Function>()->Call(env->context(), args[1],
call_args.size(), call_args.data());
if (!retval.IsEmpty()) {
args.GetReturnValue().Set(retval.ToLocalChecked());
}
}

// Used in NodeInspectorClient::currentTimeMS() below.
const int NANOS_PER_MSEC = 1000000;
Expand Down Expand Up @@ -710,20 +530,6 @@ bool Agent::StartIoThread(bool wait_for_connect) {
return true;
}

static void AddCommandLineAPI(
const FunctionCallbackInfo<Value>& info) {
auto env = Environment::GetCurrent(info);
Local<Context> context = env->context();

if (info.Length() != 2 || !info[0]->IsString()) {
return env->ThrowTypeError("inspector.addCommandLineAPI takes "
"exactly 2 arguments: a string and a value.");
}

Local<Object> console_api = env->inspector_console_api_object();
console_api->Set(context, info[0], info[1]).FromJust();
}

void Agent::Stop() {
if (io_ != nullptr) {
io_->Stop();
Expand Down Expand Up @@ -842,138 +648,6 @@ void Agent::AllAsyncTasksCanceled() {
client_->AllAsyncTasksCanceled();
}

void Open(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
inspector::Agent* agent = env->inspector_agent();
bool wait_for_connect = false;

if (args.Length() > 0 && args[0]->IsUint32()) {
uint32_t port = args[0]->Uint32Value();
agent->options().set_port(static_cast<int>(port));
}

if (args.Length() > 1 && args[1]->IsString()) {
node::Utf8Value host(env->isolate(), args[1].As<String>());
agent->options().set_host_name(*host);
}

if (args.Length() > 2 && args[2]->IsBoolean()) {
wait_for_connect = args[2]->BooleanValue();
}

agent->StartIoThread(wait_for_connect);
}

void Url(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
inspector::Agent* agent = env->inspector_agent();
inspector::InspectorIo* io = agent->io();

if (!io) return;

std::vector<std::string> ids = io->GetTargetIds();

if (ids.empty()) return;

std::string url = FormatWsAddress(io->host(), io->port(), ids[0], true);
args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str()));
}

static void* GetAsyncTask(int64_t asyncId) {
// The inspector assumes that when other clients use its asyncTask* API,
// they use real pointers, or at least something aligned like real pointer.
// In general it means that our task_id should always be even.
//
// On 32bit platforms, the 64bit asyncId would get truncated when converted
// to a 32bit pointer. However, the javascript part will never enable
// the async_hook on 32bit platforms, therefore the truncation will never
// happen in practice.
return reinterpret_cast<void*>(asyncId << 1);
}

template<void (Agent::*asyncTaskFn)(void*)>
static void InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsNumber());
int64_t task_id = args[0]->IntegerValue(env->context()).FromJust();
(env->inspector_agent()->*asyncTaskFn)(GetAsyncTask(task_id));
}

static void AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CHECK(args[0]->IsString());
Local<String> task_name = args[0].As<String>();
String::Value task_name_value(task_name);
StringView task_name_view(*task_name_value, task_name_value.length());

CHECK(args[1]->IsNumber());
int64_t task_id = args[1]->IntegerValue(env->context()).FromJust();
void* task = GetAsyncTask(task_id);

CHECK(args[2]->IsBoolean());
bool recurring = args[2]->BooleanValue(env->context()).FromJust();

env->inspector_agent()->AsyncTaskScheduled(task_name_view, task, recurring);
}

static void RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

CHECK(args[0]->IsFunction());
v8::Local<v8::Function> enable_function = args[0].As<Function>();
CHECK(args[1]->IsFunction());
v8::Local<v8::Function> disable_function = args[1].As<Function>();
env->inspector_agent()->RegisterAsyncHook(env->isolate(),
enable_function, disable_function);
}

static void IsEnabled(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
args.GetReturnValue().Set(env->inspector_agent()->enabled());
}

// static
void Agent::InitInspector(Local<Object> target, Local<Value> unused,
Local<Context> context, void* priv) {
Environment* env = Environment::GetCurrent(context);
{
auto obj = Object::New(env->isolate());
auto null = Null(env->isolate());
CHECK(obj->SetPrototype(context, null).FromJust());
env->set_inspector_console_api_object(obj);
}

Agent* agent = env->inspector_agent();
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
env->SetMethod(target, "addCommandLineAPI", AddCommandLineAPI);
if (agent->debug_options_.wait_for_connect())
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
env->SetMethod(target, "open", Open);
env->SetMethod(target, "url", Url);

env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper);
env->SetMethod(target, "asyncTaskCanceled",
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>);
env->SetMethod(target, "asyncTaskStarted",
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>);
env->SetMethod(target, "asyncTaskFinished",
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>);

env->SetMethod(target, "registerAsyncHook", RegisterAsyncHookWrapper);
env->SetMethod(target, "isEnabled", IsEnabled);

auto conn_str = FIXED_ONE_BYTE_STRING(env->isolate(), "Connection");
Local<FunctionTemplate> tmpl =
env->NewFunctionTemplate(JSBindingsConnection::New);
tmpl->InstanceTemplate()->SetInternalFieldCount(1);
tmpl->SetClassName(conn_str);
AsyncWrap::AddWrapMethods(env, tmpl);
env->SetProtoMethod(tmpl, "dispatch", JSBindingsConnection::Dispatch);
env->SetProtoMethod(tmpl, "disconnect", JSBindingsConnection::Disconnect);
target->Set(env->context(), conn_str, tmpl->GetFunction()).ToChecked();
}

void Agent::RequestIoThreadStart() {
// We need to attempt to interrupt V8 flow (in case Node is running
// continuous JS code) and to wake up libuv thread (in case Node is waiting
Expand All @@ -993,8 +667,10 @@ void Agent::ContextCreated(Local<Context> context) {
client_->contextCreated(context, name.str());
}

bool Agent::IsWaitingForConnect() {
return debug_options_.wait_for_connect();
}

} // namespace inspector
} // namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(inspector,
node::inspector::Agent::InitInspector);
Loading