@ 261106.2
current
#include "base_object-inl.h"
#include "inspector/network_resource_manager.h"
#include "inspector/protocol_helper.h"
#include "inspector_agent.h"
#include "inspector_io.h"
#include "memory_tracker-inl.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_watchdog.h"
#include "util-inl.h"
#include "v8-inspector.h"
#include "v8.h"

#include <memory>
#include <string>
#include <string_view>
#include <vector>

namespace node {
namespace inspector {
namespace {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::GCCallbackFlags;
using v8::GCType;
using v8::Global;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
using v8_inspector::StringView;

struct LocalConnection {
  static std::unique_ptr<InspectorSession> Connect(
      Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
    return inspector->Connect(std::move(delegate), false);
  }

  static Local<String> GetClassName(Environment* env) {
    return FIXED_ONE_BYTE_STRING(env->isolate(), "Connection");
  }
};

struct MainThreadConnection {
  static std::unique_ptr<InspectorSession> Connect(
      Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) {
    return inspector->ConnectToMainThread(std::move(delegate), true);
  }

  static Local<String> GetClassName(Environment* env) {
    return FIXED_ONE_BYTE_STRING(env->isolate(), "MainThreadConnection");
  }
};

template <typename ConnectionType>
class JSBindingsConnection : public BaseObject {
 public:
  class JSBindingsSessionDelegate : public InspectorSessionDelegate {
   public:
    JSBindingsSessionDelegate(Environment* env,
                              JSBindingsConnection* connection)
                              : env_(env),
                                connection_(connection) {
    }

    void SendMessageToFrontend(const v8_inspector::StringView& message)
        override {
      connection_->SendMessageToFrontend(message);
    }

   private:
    Environment* env_;
    BaseObjectPtr<JSBindingsConnection> connection_;
  };

  JSBindingsConnection(Environment* env,
                       Local<Object> wrap,
                       Local<Function> callback)
      : BaseObject(env, wrap), callback_(env->isolate(), callback) {
    Agent* inspector = env->inspector_agent();
    session_ = ConnectionType::Connect(
        inspector, std::make_unique<JSBindingsSessionDelegate>(env, this));
    // Inspector responses may be produced from a GC weak callback, where
    // invoking the JavaScript session callback is forbidden. Defer delivery
    // until the GC has completed.
    env->isolate()->AddGCPrologueCallback(GCPrologueCallback, this);
    env->isolate()->AddGCEpilogueCallback(GCEpilogueCallback, this);
  }

  ~JSBindingsConnection() override {
    env()->isolate()->RemoveGCPrologueCallback(GCPrologueCallback, this);
    env()->isolate()->RemoveGCEpilogueCallback(GCEpilogueCallback, this);
  }

  void SendMessageToFrontend(const v8_inspector::StringView& message) {
    if (in_gc_ || delivering_ || !pending_messages_.empty()) {
      pending_messages_.emplace_back(
          message.is8Bit()
              ? std::u16string(message.characters8(),
                               message.characters8() + message.length())
              : std::u16string(message.characters16(),
                               message.characters16() + message.length()));
      ScheduleFlush();
      return;
    }
    Isolate* isolate = env()->isolate();
    HandleScope handle_scope(isolate);
    Context::Scope context_scope(env()->context());
    Local<Value> argument;
    if (!ToV8Value(env()->context(), message, isolate).ToLocal(&argument))
      return;
    OnMessage(argument);
  }

  void OnMessage(Local<Value> value) {
    auto result = callback_.Get(env()->isolate())
                      ->Call(env()->context(), object(), 1, &value);
    (void)result;
  }

  void ScheduleFlush() {
    if (flush_scheduled_ || delivering_) return;
    flush_scheduled_ = true;
    BaseObjectPtr<JSBindingsConnection> strong_ref{this};
    env()->SetImmediate(
        [strong_ref](Environment*) { strong_ref->FlushPendingMessages(); },
        CallbackFlags::kUnrefed);
  }

  void FlushPendingMessages() {
    flush_scheduled_ = false;
    if (pending_messages_.empty()) return;
    Isolate* isolate = env()->isolate();
    HandleScope handle_scope(isolate);
    Context::Scope context_scope(env()->context());
    delivering_ = true;
    while (!pending_messages_.empty()) {
      std::u16string message = std::move(pending_messages_.front());
      pending_messages_.erase(pending_messages_.begin());
      Local<Value> argument;
      if (ToV8Value(env()->context(), std::u16string_view(message), isolate)
              .ToLocal(&argument)) {
        OnMessage(argument);
      }
    }
    delivering_ = false;
  }

  static void GCPrologueCallback(Isolate*,
                                 GCType,
                                 GCCallbackFlags,
                                 void* data) {
    static_cast<JSBindingsConnection*>(data)->in_gc_ = true;
  }

  static void GCEpilogueCallback(Isolate*,
                                 GCType,
                                 GCCallbackFlags,
                                 void* data) {
    auto* connection = static_cast<JSBindingsConnection*>(data);
    connection->in_gc_ = false;
    if (!connection->pending_messages_.empty()) connection->ScheduleFlush();
  }

  static void Bind(Environment* env, Local<Object> target) {
    Isolate* isolate = env->isolate();
    Local<FunctionTemplate> tmpl =
        NewFunctionTemplate(isolate, JSBindingsConnection::New);
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        JSBindingsConnection::kInternalFieldCount);
    SetProtoMethod(isolate, tmpl, "dispatch", JSBindingsConnection::Dispatch);
    SetProtoMethod(
        isolate, tmpl, "disconnect", JSBindingsConnection::Disconnect);
    SetConstructorFunction(
        env->context(), target, ConnectionType::GetClassName(env), tmpl);
  }

  static void New(const FunctionCallbackInfo<Value>& info) {
    Environment* env = Environment::GetCurrent(info);
    CHECK(info[0]->IsFunction());
    Local<Function> callback = info[0].As<Function>();
    new JSBindingsConnection(env, info.This(), callback);
  }

  // See https://github.com/nodejs/node/pull/46942
  void Disconnect() {
    BaseObjectPtr<JSBindingsConnection> strong_ref{this};
    session_.reset();
    Detach();
  }

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

  static void Dispatch(const FunctionCallbackInfo<Value>& info) {
    JSBindingsConnection* session;
    ASSIGN_OR_RETURN_UNWRAP(&session, info.This());
    CHECK(info[0]->IsString());

    if (session->session_) {
      session->session_->Dispatch(
          ToInspectorString(info.GetIsolate(), info[0])->string());
    }
  }

  void MemoryInfo(MemoryTracker* tracker) const override {
    tracker->TrackField("callback", callback_);
    tracker->TrackFieldWithSize(
        "session", sizeof(*session_), "InspectorSession");
  }

  SET_MEMORY_INFO_NAME(JSBindingsConnection)
  SET_SELF_SIZE(JSBindingsConnection)

  bool IsNotIndicativeOfMemoryLeakAtExit() const override {
    return true;  // Binding connections emit events on their own.
  }

 private:
  std::unique_ptr<InspectorSession> session_;
  Global<Function> callback_;
  std::vector<std::u16string> pending_messages_;
  bool in_gc_ = false;
  bool delivering_ = false;
  bool flush_scheduled_ = false;
};

static bool InspectorEnabled(Environment* env) {
  Agent* agent = env->inspector_agent();
  return agent->IsActive();
}

void SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value>& info) {
  Realm* realm = Realm::GetCurrent(info);

  CHECK_EQ(info.Length(), 1);
  CHECK(info[0]->IsFunction());

  realm->set_inspector_console_extension_installer(info[0].As<Function>());
}

void CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  THROW_IF_INSUFFICIENT_PERMISSIONS(env,
                                    permission::PermissionScope::kInspector,
                                    "PauseOnNextJavascriptStatement");
  CHECK_GT(args.Length(), 1);
  CHECK(args[0]->IsFunction());
  SlicedArguments call_args(args, /* start */ 2);
  env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start");
  Local<Value> ret;
  if (args[0]
          .As<v8::Function>()
          ->Call(env->context(), args[1], call_args.length(), call_args.out())
          .ToLocal(&ret)) {
    args.GetReturnValue().Set(ret);
  }
}

void InspectorConsoleCall(const FunctionCallbackInfo<Value>& info) {
  Environment* env = Environment::GetCurrent(info);
  Isolate* isolate = env->isolate();
  Local<Context> context = isolate->GetCurrentContext();
  CHECK_GE(info.Length(), 2);
  SlicedArguments call_args(info, /* start */ 2);
  if (InspectorEnabled(env)) {
    Local<Value> inspector_method = info[0];
    CHECK(inspector_method->IsFunction());
    if (!env->is_in_inspector_console_call()) {
      env->set_is_in_inspector_console_call(true);
      MaybeLocal<Value> ret = inspector_method.As<Function>()->Call(
          context, info.This(), call_args.length(), call_args.out());
      env->set_is_in_inspector_console_call(false);
      if (ret.IsEmpty())
        return;
    }
  }

  Local<Value> node_method = info[1];
  CHECK(node_method->IsFunction());
  USE(node_method.As<Function>()->Call(
      context, info.This(), call_args.length(), call_args.out()));
}

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;
  if (args[0]->IntegerValue(env->context()).To(&task_id)) {
    (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>();
  TwoByteValue task_name_value(args.GetIsolate(), task_name);
  StringView task_name_view(task_name_value.out(), task_name_value.length());

  CHECK(args[1]->IsNumber());
  int64_t task_id;
  if (!args[1]->IntegerValue(env->context()).To(&task_id)) {
    return;
  }
  void* task = GetAsyncTask(task_id);

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

  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());
  Local<Function> enable_function = args[0].As<Function>();
  CHECK(args[1]->IsFunction());
  Local<Function> disable_function = args[1].As<Function>();
  env->inspector_agent()->RegisterAsyncHook(env->isolate(),
    enable_function, disable_function);
}

void EmitProtocolEvent(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args[0]->IsString());
  Local<String> eventName = args[0].As<String>();
  CHECK(args[1]->IsObject());
  Local<Object> params = args[1].As<Object>();

  env->inspector_agent()->EmitProtocolEvent(
      args.GetIsolate()->GetCurrentContext(),
      ToInspectorString(env->isolate(), eventName)->string(),
      params);
}

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

  CHECK(args[0]->IsFunction());
  Local<Function> enable_function = args[0].As<Function>();
  CHECK(args[1]->IsFunction());
  Local<Function> disable_function = args[1].As<Function>();

  env->inspector_agent()->SetupNetworkTracking(enable_function,
                                               disable_function);
}

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

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

  if (ProcessTimeoutWatchdog::IsEnabled()) {
    return THROW_ERR_INSPECTOR_NOT_AVAILABLE(
        env,
        "The inspector cannot be activated when --process-timeout is used");
  }

  if (args.Length() > 0 && args[0]->IsUint32()) {
    uint32_t port = args[0].As<Uint32>()->Value();
    CHECK_LE(port, std::numeric_limits<uint16_t>::max());
    ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
    host_port->set_port(static_cast<int>(port));
  }

  if (args.Length() > 1 && args[1]->IsString()) {
    Utf8Value host(env->isolate(), args[1].As<String>());
    ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port());
    host_port->set_host(*host);
  }

  agent->StartIoThread();
}

void WaitForDebugger(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  Agent* agent = env->inspector_agent();
  if (agent->IsActive())
    agent->WaitForConnect();
  args.GetReturnValue().Set(agent->IsActive());
}

void Url(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  std::string url = env->inspector_agent()->GetWsUrl();
  if (url.empty()) {
    return;
  }
  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);
  Isolate* isolate = env->isolate();

  v8::Local<v8::Function> consoleCallFunc =
      NewFunctionTemplate(isolate,
                          InspectorConsoleCall,
                          v8::Local<v8::Signature>(),
                          v8::ConstructorBehavior::kThrow,
                          v8::SideEffectType::kHasSideEffect)
          ->GetFunction(context)
          .ToLocalChecked();
  auto name_string = FIXED_ONE_BYTE_STRING(isolate, "consoleCall");
  target->Set(context, name_string, consoleCallFunc).Check();
  consoleCallFunc->SetName(name_string);

  SetMethod(context,
            target,
            "setConsoleExtensionInstaller",
            SetConsoleExtensionInstaller);
  SetMethod(context, target, "callAndPauseOnStart", CallAndPauseOnStart);
  SetMethod(context, target, "open", Open);
  SetMethodNoSideEffect(context, target, "url", Url);
  SetMethod(context, target, "waitForDebugger", WaitForDebugger);

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

  SetMethod(context, target, "registerAsyncHook", RegisterAsyncHookWrapper);
  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");

  // Grab the console from the binding object and expose those to our binding
  // layer.
  Local<Object> binding = context->GetExtrasBindingObject();
  target
      ->Set(context,
            console_string,
            binding->Get(context, console_string).ToLocalChecked())
      .Check();

  JSBindingsConnection<LocalConnection>::Bind(env, target);
  JSBindingsConnection<MainThreadConnection>::Bind(env, target);
}

}  // namespace

void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  registry->Register(InspectorConsoleCall);
  registry->Register(SetConsoleExtensionInstaller);
  registry->Register(CallAndPauseOnStart);
  registry->Register(Open);
  registry->Register(Url);
  registry->Register(WaitForDebugger);

  registry->Register(AsyncTaskScheduledWrapper);
  registry->Register(InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>);
  registry->Register(InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>);
  registry->Register(InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>);

  registry->Register(RegisterAsyncHookWrapper);
  registry->Register(IsEnabled);
  registry->Register(EmitProtocolEvent);
  registry->Register(SetupNetworkTracking);

  registry->Register(JSBindingsConnection<LocalConnection>::New);
  registry->Register(JSBindingsConnection<LocalConnection>::Dispatch);
  registry->Register(JSBindingsConnection<LocalConnection>::Disconnect);
  registry->Register(JSBindingsConnection<MainThreadConnection>::New);
  registry->Register(JSBindingsConnection<MainThreadConnection>::Dispatch);
  registry->Register(JSBindingsConnection<MainThreadConnection>::Disconnect);
  registry->Register(PutNetworkResource);
}

}  // namespace inspector
}  // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(inspector, node::inspector::Initialize)
NODE_BINDING_EXTERNAL_REFERENCE(inspector,
                                node::inspector::RegisterExternalReferences)