@ 261106.2
current
#include "node_diagnostics_channel.h"

#include "base_object-inl.h"
#include "env-inl.h"
#include "node_external_reference.h"
#include "util-inl.h"
#include "v8.h"

#include <cstdint>

namespace node {
namespace diagnostics_channel {

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::Object;
using v8::ObjectTemplate;
using v8::SnapshotCreator;
using v8::String;
using v8::Value;

BindingData::BindingData(Realm* realm,
                         Local<Object> wrap,
                         InternalFieldInfo* info)
    : SnapshotableObject(realm, wrap, type_int),
      subscribers_(realm->isolate(),
                   info == nullptr ? kInitialChannelCapacity
                                   : info->subscribers_capacity,
                   MAYBE_FIELD_PTR(info, subscribers)) {
  if (info == nullptr) {
    wrap->Set(realm->context(),
              FIXED_ONE_BYTE_STRING(realm->isolate(), "subscribers"),
              subscribers_.GetJSArray())
        .Check();
  } else {
    subscribers_.Deserialize(realm->context());
  }
  subscribers_.MakeWeak();
}

void BindingData::DetachChannels() {
  // A native holder may keep a strong reference to a Channel that outlives this
  // binding's ownership of it, and the binding is destroyed during environment
  // cleanup while BaseObject destructors still run after it. Drop the
  // back-pointer so that the null check in HasSubscribers() stops those holders
  // from reading the subscribers_ array of a binding that no longer owns them.
  for (auto& channel : channels_) {
    if (channel) channel->binding_data_ = nullptr;
  }
}

BindingData::~BindingData() {
  DetachChannels();
}

void BindingData::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("subscribers", subscribers_);
}

uint32_t BindingData::GetOrCreateChannelIndex(const std::string& name) {
  auto it = channel_indices_.find(name);
  if (it != channel_indices_.end()) {
    return it->second;
  }
  if (next_channel_index_ == subscribers_.Length()) {
    subscribers_.reserve(subscribers_.Length() * 2);
    object()
        ->Set(realm()->context(),
              FIXED_ONE_BYTE_STRING(realm()->isolate(), "subscribers"),
              subscribers_.GetJSArray())
        .Check();
    subscribers_.MakeWeak();
  }
  uint32_t index = next_channel_index_++;
  channel_indices_.emplace(name, index);
  return index;
}

void BindingData::LinkNativeChannel(const FunctionCallbackInfo<Value>& args) {
  Realm* realm = Realm::GetCurrent(args);
  BindingData* binding = realm->GetBindingData<BindingData>();
  CHECK_NOT_NULL(binding);

  CHECK(args[0]->IsFunction());
  Isolate* isolate = realm->isolate();
  Local<Context> context = realm->context();
  binding->link_callback_.Reset(isolate, args[0].As<Function>());

  // Resolve channels created before the link callback was available.
  for (auto& channel_ptr : binding->channels_) {
    if (channel_ptr && !channel_ptr->IsLinked()) {
      Local<String> name =
          String::NewFromUtf8(isolate, channel_ptr->name_.c_str())
              .ToLocalChecked();
      Local<Value> argv[] = {
          name, Integer::NewFromUnsigned(isolate, channel_ptr->index_)};
      Local<Value> result;
      if (binding->link_callback_.Get(isolate)
              ->Call(context, v8::Undefined(isolate), arraysize(argv), argv)
              .ToLocal(&result) &&
          result->IsObject()) {
        channel_ptr->Link(isolate, result.As<Object>());
      }
    }
  }
}

bool BindingData::PrepareForSerialization(Local<Context> context,
                                          SnapshotCreator* creator) {
  DCHECK_NULL(internal_field_info_);
  internal_field_info_ = InternalFieldInfoBase::New<InternalFieldInfo>(type());
  internal_field_info_->subscribers = subscribers_.Serialize(context, creator);
  internal_field_info_->subscribers_capacity = subscribers_.Length();
  link_callback_.Reset();
  channel_wrap_template_.Reset();
  DetachChannels();
  channels_.clear();
  return true;
}

InternalFieldInfoBase* BindingData::Serialize(int index) {
  DCHECK_IS_SNAPSHOT_SLOT(index);
  InternalFieldInfo* info = internal_field_info_;
  internal_field_info_ = nullptr;
  return info;
}

void BindingData::Deserialize(Local<Context> context,
                              Local<Object> holder,
                              int index,
                              InternalFieldInfoBase* info) {
  DCHECK_IS_SNAPSHOT_SLOT(index);
  HandleScope scope(Isolate::GetCurrent());
  Realm* realm = Realm::GetCurrent(context);
  BindingData* binding = realm->AddBindingData<BindingData>(
      holder, static_cast<InternalFieldInfo*>(info));
  CHECK_NOT_NULL(binding);
}

void BindingData::SetChannelStatusCallback(uint32_t index,
                                           ChannelStatusCallback cb) {
  channel_status_callbacks_[index] = std::move(cb);
}

void BindingData::NotifyChannelActive(const FunctionCallbackInfo<Value>& args) {
  Realm* realm = Realm::GetCurrent(args);
  BindingData* binding = realm->GetBindingData<BindingData>();
  if (binding == nullptr) return;
  CHECK(args[0]->IsUint32());
  uint32_t index = args[0].As<v8::Uint32>()->Value();
  auto it = binding->channel_status_callbacks_.find(index);
  if (it != binding->channel_status_callbacks_.end()) it->second(true);
}

void BindingData::NotifyChannelInactive(
    const FunctionCallbackInfo<Value>& args) {
  Realm* realm = Realm::GetCurrent(args);
  BindingData* binding = realm->GetBindingData<BindingData>();
  if (binding == nullptr) return;
  CHECK(args[0]->IsUint32());
  uint32_t index = args[0].As<v8::Uint32>()->Value();
  auto it = binding->channel_status_callbacks_.find(index);
  if (it != binding->channel_status_callbacks_.end()) it->second(false);
}

void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data,
                                             Local<ObjectTemplate> target) {
  Isolate* isolate = isolate_data->isolate();
  SetMethod(isolate, target, "linkNativeChannel", LinkNativeChannel);
  SetMethod(isolate, target, "notifyChannelActive", NotifyChannelActive);
  SetMethod(isolate, target, "notifyChannelInactive", NotifyChannelInactive);
}

void BindingData::CreatePerContextProperties(Local<Object> target,
                                             Local<Value> unused,
                                             Local<Context> context,
                                             void* priv) {
  Realm* realm = Realm::GetCurrent(context);
  BindingData* const binding = realm->AddBindingData<BindingData>(target);
  if (binding == nullptr) return;
}

void BindingData::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(LinkNativeChannel);
  registry->Register(NotifyChannelActive);
  registry->Register(NotifyChannelInactive);
}

Channel::Channel(Environment* env,
                 Local<Object> wrap,
                 BindingData* binding_data,
                 uint32_t index,
                 std::string name)
    : BaseObject(env, wrap),
      binding_data_(binding_data),
      index_(index),
      name_(std::move(name)) {
  MakeWeak();
}

void Channel::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("js_channel", js_channel_);
  tracker->TrackField("publish_fn", publish_fn_);
}

void Channel::Link(Isolate* isolate, Local<Object> js_channel) {
  js_channel_.Reset(isolate, js_channel);
  // If the channel already has subscribers at link time (JS subscribed before
  // C++ called Get()), cache publish eagerly so Publish() has no extra work.
  if (HasSubscribers()) {
    CachePublishFn(isolate, js_channel);
  }
}

void Channel::Unlink() {
  js_channel_.Reset();
  publish_fn_.Reset();
}

BaseObjectPtr<Channel> Channel::Get(Environment* env, std::string_view name) {
  Realm* realm = env->principal_realm();
  BindingData* binding = realm->GetBindingData<BindingData>();
  if (binding == nullptr) {
    return {};
  }
  uint32_t index = binding->GetOrCreateChannelIndex(std::string(name));

  if (index >= binding->channels_.size()) {
    binding->channels_.resize(index + 1);
  }

  if (!binding->channels_[index]) {
    Isolate* isolate = env->isolate();
    HandleScope handle_scope(isolate);
    Local<Context> context = env->context();

    // Lazily create the wrap template for Channel objects.
    if (binding->channel_wrap_template_.IsEmpty()) {
      Local<FunctionTemplate> tmpl =
          BaseObject::MakeLazilyInitializedJSTemplate(env);
      binding->channel_wrap_template_.Reset(isolate, tmpl);
    }

    Local<Object> wrap;
    if (!binding->channel_wrap_template_.Get(isolate)
             ->GetFunction(context)
             .ToLocalChecked()
             ->NewInstance(context)
             .ToLocal(&wrap)) {
      return {};
    }

    binding->channels_[index] = MakeDetachedBaseObject<Channel>(
        env, wrap, binding, index, std::string(name));
  }

  auto& channel = binding->channels_[index];

  // Late-bind: link to the JS channel when the callback is available.
  if (!binding->link_callback_.IsEmpty() && !channel->IsLinked()) {
    Isolate* isolate = env->isolate();
    HandleScope handle_scope(isolate);
    Local<Context> context = env->context();
    Local<Value> argv[] = {
        ToV8Value(context, name).ToLocalChecked(),
        Integer::NewFromUnsigned(isolate, index),
    };
    Local<Value> result;
    if (binding->link_callback_.Get(isolate)
            ->Call(context, v8::Undefined(isolate), arraysize(argv), argv)
            .ToLocal(&result) &&
        result->IsObject()) {
      channel->Link(isolate, result.As<Object>());
    }
  }

  return channel;
}

void Channel::CachePublishFn(Isolate* isolate, Local<Object> js_channel) {
  Local<Value> publish_val;
  Local<Context> context = isolate->GetCurrentContext();
  if (js_channel->Get(context, FIXED_ONE_BYTE_STRING(isolate, "publish"))
          .ToLocal(&publish_val) &&
      publish_val->IsFunction()) {
    publish_fn_.Reset(isolate, publish_val.As<Function>());
  }
}

void Channel::Publish(Environment* env, Local<Value> message) {
  if (!HasSubscribers()) return;

  if (js_channel_.IsEmpty()) return;

  // Publishing is not possible during shutdown or GC.
  DCHECK(env->can_call_into_js());
  if (!env->can_call_into_js()) return;

  Isolate* isolate = env->isolate();
  HandleScope handle_scope(isolate);
  Local<Context> context = env->context();
  Context::Scope context_scope(context);

  Local<Object> js_channel = js_channel_.Get(isolate);

  // publish_fn_ is eagerly cached by Link() when the channel already has
  // subscribers at link time. For channels linked before any JS subscriber
  // existed, cache it here on the first publish — happens exactly once.
  if (publish_fn_.IsEmpty()) {
    CachePublishFn(isolate, js_channel);
    if (publish_fn_.IsEmpty()) return;
  }

  Local<Value> argv[] = {message};
  USE(publish_fn_.Get(isolate)->Call(context, js_channel, 1, argv));
}

}  // namespace diagnostics_channel
}  // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(
    diagnostics_channel,
    node::diagnostics_channel::BindingData::CreatePerContextProperties)
NODE_BINDING_PER_ISOLATE_INIT(
    diagnostics_channel,
    node::diagnostics_channel::BindingData::CreatePerIsolateProperties)
NODE_BINDING_EXTERNAL_REFERENCE(
    diagnostics_channel,
    node::diagnostics_channel::BindingData::RegisterExternalReferences)