// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/agent_scheduling_group_host.h"

#include <memory>

#include "base/containers/contains.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/state_transitions.h"
#include "base/supports_user_data.h"
#include "base/task/single_thread_task_runner.h"
#include "content/browser/bad_message.h"
#include "content/browser/renderer_host/agent_scheduling_group_host_factory.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/common/agent_scheduling_group.mojom.h"
#include "content/common/renderer.mojom.h"
#include "content/public/browser/render_process_host.h"
#include "ipc/constants.mojom.h"
#include "ipc/ipc_channel_factory.h"
#include "ipc/ipc_channel_proxy.h"
#if BUILDFLAG(ARKWEB_RENDER_PROCESS_MODE)
#include "third_party/ohos_ndk/includes/ohos_adapter/res_sched_client_adapter.h"
#include "arkweb/chromium_ext/base/ohos/sys_info_utils_ext.h"
#endif
#include "third_party/blink/public/mojom/shared_storage/shared_storage_worklet_service.mojom.h"
#include "third_party/blink/public/mojom/worker/worklet_global_scope_creation_params.mojom.h"

namespace content {

namespace {

using ::IPC::ChannelFactory;
using ::IPC::ChannelProxy;
using ::IPC::Listener;
using ::mojo::AssociatedReceiver;
using ::mojo::AssociatedRemote;
using ::mojo::PendingAssociatedReceiver;
using ::mojo::PendingAssociatedRemote;
using ::mojo::PendingReceiver;
using ::mojo::PendingRemote;
using ::mojo::Receiver;
using ::mojo::Remote;

static constexpr char kAgentSchedulingGroupHostDataKey[] =
    "AgentSchedulingGroupHostUserDataKey";

AgentSchedulingGroupHostFactory* g_agent_scheduling_group_host_factory_ =
    nullptr;

// This is a struct that is owned by RenderProcessHost. It carries data
// structures that store the AgentSchedulingGroups associated with a
// RenderProcessHost.
struct AgentSchedulingGroupHostUserData : public base::SupportsUserData::Data {
 public:
  AgentSchedulingGroupHostUserData() = default;
  ~AgentSchedulingGroupHostUserData() override = default;

  std::set<std::unique_ptr<AgentSchedulingGroupHost>, base::UniquePtrComparator>
      owned_host_set;
  // This is used solely to DCHECK the invariant that a SiteInstanceGroup cannot
  // request an AgentSchedulingGroup twice from the same RenderProcessHost.
#if DCHECK_IS_ON()
  std::set<raw_ptr<const SiteInstanceGroup, SetExperimental>>
      site_instance_groups;
#endif
};

static features::MBIMode GetMBIMode() {
  return base::FeatureList::IsEnabled(features::kMBIMode)
             ? features::kMBIModeParam.Get()
             : features::MBIMode::kLegacy;
}

}  // namespace

// static
AgentSchedulingGroupHost* AgentSchedulingGroupHost::GetOrCreate(
    const SiteInstanceGroup& site_instance_group,
    RenderProcessHost& process) {
  AgentSchedulingGroupHostUserData* data =
      static_cast<AgentSchedulingGroupHostUserData*>(
          process.GetUserData(kAgentSchedulingGroupHostDataKey));

  if (!data) {
    process.SetUserData(kAgentSchedulingGroupHostDataKey,
                        std::make_unique<AgentSchedulingGroupHostUserData>());
    data = static_cast<AgentSchedulingGroupHostUserData*>(
        process.GetUserData(kAgentSchedulingGroupHostDataKey));
  }

  DCHECK(data);

  if (GetMBIMode() == features::MBIMode::kLegacy ||
      GetMBIMode() == features::MBIMode::kEnabledPerRenderProcessHost) {
    // We don't use |data->site_instance_groups| at all when
    // AgentSchedulingGroupHost is 1:1 with RenderProcessHost.
#if DCHECK_IS_ON()
    DCHECK(data->site_instance_groups.empty());
#endif

    if (data->owned_host_set.empty()) {
      std::unique_ptr<AgentSchedulingGroupHost> host =
          g_agent_scheduling_group_host_factory_
              ? g_agent_scheduling_group_host_factory_
                    ->CreateAgentSchedulingGroupHost(process)
              : std::make_unique<AgentSchedulingGroupHost>(process);
      data->owned_host_set.insert(std::move(host));
    }

    // When we are in an MBI mode that creates AgentSchedulingGroups 1:1 with
    // RenderProcessHosts, we expect to know about at most one
    // AgentSchedulingGroupHost, since it should be the only one associated
    // with the RenderProcessHost.
    DCHECK_EQ(data->owned_host_set.size(), 1ul);
    return data->owned_host_set.begin()->get();
  }

  DCHECK_EQ(GetMBIMode(), features::MBIMode::kEnabledPerSiteInstance);

  // If we're in an MBI mode that creates multiple AgentSchedulingGroupHosts
  // per RenderProcessHost, then this will be called whenever SiteInstance needs
  // a newly-created AgentSchedulingGroupHost, so we create it here.
  std::unique_ptr<AgentSchedulingGroupHost> host =
      std::make_unique<AgentSchedulingGroupHost>(process);
  AgentSchedulingGroupHost* return_host = host.get();

  // In the MBI mode where AgentSchedulingGroupHosts are 1:1 with
  // SiteInstanceGroups, a SiteInstanceGroup may see different
  // RenderProcessHosts throughout its lifetime, but it should only ever see a
  // single AgentSchedulingGroupHost for a given RenderProcessHost.
#if DCHECK_IS_ON()
  DCHECK(!base::Contains(data->site_instance_groups, &site_instance_group));
  data->site_instance_groups.insert(&site_instance_group);
#endif

  data->owned_host_set.insert(std::move(host));
  return return_host;
}

int32_t AgentSchedulingGroupHost::GetNextID() {
  static int32_t next_id = 0;
  return next_id++;
}

AgentSchedulingGroupHost::AgentSchedulingGroupHost(RenderProcessHost& process)
    : process_(process), receiver_(this) {
  process_->AddObserver(this);

  // The RenderProcessHost's channel and other mojo interfaces are bound by the
  // time this class is constructed, so we eagerly initialize this class's IPC
  // so they have the same bind lifetime as those of the RenderProcessHost.
  // Furthermore, when the RenderProcessHost's channel and mojo interfaces get
  // reset and reinitialized, we'll be notified so that we can reset and
  // reinitialize ours as well.
  SetUpIPC();
  implUtils = std::make_unique<AgentSchedulingGroupHostUtils>(this);
}

// DO NOT USE |process_| HERE! At this point it (or at least parts of it) is no
// longer valid.
AgentSchedulingGroupHost::~AgentSchedulingGroupHost() {
  DCHECK_EQ(state_, LifecycleState::kRenderProcessHostDestroyed);
}

void AgentSchedulingGroupHost::RenderProcessExited(
    RenderProcessHost* host,
    const ChildProcessTerminationInfo& info) {
  SetState(LifecycleState::kRenderProcessExited);
  DCHECK_EQ(host, &*process_);

  // We mirror the RenderProcessHost flow here by resetting our mojos, and
  // reinitializing them once the process's IPC::ChannelProxy and renderer
  // interface are reinitialized.
  ResetIPC();

  // We don't want to reinitialize the RenderProcessHost's IPC channel when
  // we are going to immediately get a call to RenderProcessHostDestroyed.
  if (!process_->IsDeletingSoon()) {
    // RenderProcessHostImpl will attempt to call this method later if it has
    // not already been called. We call it now since `SetUpIPC()` relies on it
    // being called, thus setting up the IPC channel and mojom::Renderer
    // interface.
    process_->EnableSendQueue();

    // We call this so that we can immediately queue IPC and mojo messages on
    // the new channel/interfaces that are bound for the next renderer process,
    // should one eventually be spun up.
    SetUpIPC();
  }
}

void AgentSchedulingGroupHost::RenderProcessHostDestroyed(
    RenderProcessHost* host) {
  if (RenderProcessHost::run_renderer_in_process()) {
    // In single process mode, RenderProcessExited call is sometimes omitted.
    if (state_ != LifecycleState::kBound) {
      RenderProcessExited(host, ChildProcessTerminationInfo());
    }
  }
  DCHECK(state_ == LifecycleState::kBound ||
         state_ == LifecycleState::kRenderProcessExited);

  DCHECK_EQ(host, &*process_);
  process_->RemoveObserver(this);
  SetState(LifecycleState::kRenderProcessHostDestroyed);
}

void AgentSchedulingGroupHost::OnBadMessageReceived() {
  // If a bad message is received, it should be treated the same as a bad
  // message on the renderer-wide channel (i.e., kill the renderer).
  bad_message::ReceivedBadMessage(&*process_,
                                  bad_message::RPH_DESERIALIZATION_FAILED);
}

void AgentSchedulingGroupHost::OnAssociatedInterfaceRequest(
    const std::string& interface_name,
    mojo::ScopedInterfaceEndpointHandle handle) {
  // There shouldn't be any interfaces requested this way - process-wide
  // interfaces should be requested via the process-wide channel, and
  // ASG-related interfaces should go through `RouteProvider`.
  bad_message::ReceivedBadMessage(
      &*process_, bad_message::ASGH_ASSOCIATED_INTERFACE_REQUEST);
}

RenderProcessHost* AgentSchedulingGroupHost::GetProcess() {
  // `process_` can still be accessed here even if `state_` has been set to
  // `kRenderProcessHostDestroyed`. This is because a `RenderProcessHostImpl` is
  // scheduled to be destroyed asynchronously after the
  // `RenderProcessHostDestroyed()` observer notification is dispatched, so
  // `process_` and `this` may still be around within that gap.
  return &*process_;
}

bool AgentSchedulingGroupHost::Init() {
  // If we are about to initialize the RenderProcessHost, it is expected that
  // `RenderProcessHost::InitializeChannelProxy()` has already been called, and
  // thus its IPC::ChannelProxy and renderer interface are usable, as are our
  // own mojos. This is because the lifetime of our mojos should match the
  // lifetime of the RenderProcessHost's IPC::ChannelProxy and renderer
  // interfaces.
  DCHECK(process_->GetRendererInterface());
  DCHECK(mojo_remote_.is_bound());
  DCHECK_EQ(state_, LifecycleState::kBound);

  return process_->Init();
}

base::SafeRef<AgentSchedulingGroupHost> AgentSchedulingGroupHost::GetSafeRef()
    const {
  return weak_ptr_factory_.GetSafeRef();
}

ChannelProxy* AgentSchedulingGroupHost::GetChannel() {
  DCHECK_EQ(state_, LifecycleState::kBound);

  if (GetMBIMode() == features::MBIMode::kLegacy)
    return process_->GetChannel();

  DCHECK(channel_);
  return channel_.get();
}

void AgentSchedulingGroupHost::AddRoute(int32_t routing_id,
                                        Listener* listener) {
  DCHECK_EQ(state_, LifecycleState::kBound);
  DCHECK(!listener_map_.Lookup(routing_id));
  listener_map_.AddWithID(listener, routing_id);
  process_->AddRoute(routing_id, listener);
}

void AgentSchedulingGroupHost::RemoveRoute(int32_t routing_id) {
  TRACE_EVENT0("navigation", "AgentSchedulingGroupHost::RemoveRoute");
  base::ScopedUmaHistogramTimer histogram_timer(
      "Navigation.AgentSchedulingGroupHost.RemoveRoute");
  DCHECK_EQ(state_, LifecycleState::kBound);
  listener_map_.Remove(routing_id);
  process_->RemoveRoute(routing_id);
}
mojom::RouteProvider* AgentSchedulingGroupHost::GetRemoteRouteProvider() {
  DCHECK_EQ(state_, LifecycleState::kBound);
  return remote_route_provider_.get();
}

void AgentSchedulingGroupHost::CreateFrame(mojom::CreateFrameParamsPtr params) {
  DCHECK_EQ(state_, LifecycleState::kBound);
  DCHECK(process_->IsInitializedAndNotDead());
  DCHECK(mojo_remote_.is_bound());
  mojo_remote_.get()->CreateFrame(std::move(params));
}

void AgentSchedulingGroupHost::CreateView(mojom::CreateViewParamsPtr params) {
  DCHECK_EQ(state_, LifecycleState::kBound);
  DCHECK(process_->IsInitializedAndNotDead());
  DCHECK(mojo_remote_.is_bound());
  mojo_remote_.get()->CreateView(std::move(params));
}

void AgentSchedulingGroupHost::CreateSharedStorageWorkletService(
    mojo::PendingReceiver<blink::mojom::SharedStorageWorkletService> receiver,
    blink::mojom::WorkletGlobalScopeCreationParamsPtr
        global_scope_creation_params) {
  DCHECK_EQ(state_, LifecycleState::kBound);
  DCHECK(process_->IsInitializedAndNotDead());
  DCHECK(mojo_remote_.is_bound());
  mojo_remote_.get()->CreateSharedStorageWorkletService(
      std::move(receiver), std::move(global_scope_creation_params));
}

// static
void AgentSchedulingGroupHost::
    set_agent_scheduling_group_host_factory_for_testing(
        AgentSchedulingGroupHostFactory* asgh_factory) {
  g_agent_scheduling_group_host_factory_ = asgh_factory;
}

// static
AgentSchedulingGroupHostFactory* AgentSchedulingGroupHost::
    get_agent_scheduling_group_host_factory_for_testing() {
  DCHECK(g_agent_scheduling_group_host_factory_);
  return g_agent_scheduling_group_host_factory_;
}

void AgentSchedulingGroupHost::DidUnloadRenderFrame(
    const blink::LocalFrameToken& frame_token) {
  // |frame_host| could be null if we decided to remove the RenderFrameHostImpl
  // because the Unload request took too long.
  if (auto* frame_host = RenderFrameHostImpl::FromFrameToken(
          process_->GetDeprecatedID(), frame_token)) {
    frame_host->OnUnloadACK();
  }
}

#if BUILDFLAG(ARKWEB_RENDER_PROCESS_MODE)
void AgentSchedulingGroupHost::ReportCreateView(int32_t process_id) {
  if (implUtils) {
    implUtils->ReportCreateView(process_id);
  }
}
#endif

void AgentSchedulingGroupHost::ResetIPC() {
  DCHECK_EQ(state_, LifecycleState::kRenderProcessExited);
  receiver_.reset();
  mojo_remote_.reset();
  remote_route_provider_.reset();
  channel_ = nullptr;
}

void AgentSchedulingGroupHost::SetUpIPC() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(state_ == LifecycleState::kNewborn ||
         state_ == LifecycleState::kRenderProcessExited);

  // The RenderProcessHostImpl's renderer interface must be initialized at this
  // point. We don't DCHECK |process_.IsInitializedAndNotDead()| here because
  // we may end up here after the renderer process has died but before
  // RenderProcessHostImpl::Init() is called. Therefore, the process can accept
  // IPCs that will be queued for the next renderer process if one is spun up.
  DCHECK(process_->GetRendererInterface());

  DCHECK(!channel_);
  DCHECK(!mojo_remote_.is_bound());
  DCHECK(!receiver_.is_bound());
  DCHECK(!remote_route_provider_.is_bound());

  // After this function returns, all of `this`'s mojo interfaces need to be
  // bound, and associated interfaces need to be associated "properly" - in
  // `features::MBIMode::kEnabledPerRenderProcessHost` and
  // `features::MBIMode::kEnabledPerSiteInstance` mode that means they are
  // associated with the ASG's legacy IPC channel, and in
  // `features::MBIMode::kLegacy` mode, with the process-global legacy IPC
  // channel. This initialization is done in a number of steps:
  // 1. If we're not in `kLegacy` mode, create an IPC Channel (i.e., initialize
  //    `channel_`). After this, regardless of which mode we're in, the
  //    ASGH would have a channel.
  // 2. Initialize `mojo_remote_`. In `kLegacy` mode, this can be done via the
  //    `mojom::Renderer` interface, but otherwise this *has* to be done via the
  //     just-created channel (so the interface is associated with the correct
  //     pipe).
  // 3. All the ASGH's other associated interfaces can now be initialized via
  //    `mojo_remote_`, and will be transitively associated with the appropriate
  //    IPC channel/pipe.
  if (GetMBIMode() == features::MBIMode::kLegacy) {
    process_->GetRendererInterface()->CreateAssociatedAgentSchedulingGroup(
        mojo_remote_.BindNewEndpointAndPassReceiver());
  } else {
    auto io_task_runner = GetIOThreadTaskRunner({});

    // Empty interface endpoint to pass pipes more easily.
    PendingRemote<IPC::mojom::ChannelBootstrap> bootstrap;

    process_->GetRendererInterface()->CreateAgentSchedulingGroup(
        bootstrap.InitWithNewPipeAndPassReceiver());

    auto channel_factory = ChannelFactory::CreateServerFactory(
        bootstrap.PassPipe(), /*ipc_task_runner=*/io_task_runner,
        /*proxy_task_runner=*/
        base::SingleThreadTaskRunner::GetCurrentDefault());

    channel_ =
        ChannelProxy::Create(std::move(channel_factory), /*listener=*/this,
                             /*ipc_task_runner=*/io_task_runner,
                             /*listener_task_runner=*/
                             base::SingleThreadTaskRunner::GetCurrentDefault());

    // TODO(crbug.com/40142495): Add necessary filters.
    // Most of the filters currently installed on the process-wide channel are:
    // 1. "Process-bound", that is, they do not handle messages sent using ASG,
    // 2. Related to Android WebViews, which are not currently supported.

    channel_->GetRemoteAssociatedInterface(&mojo_remote_);
  }

  DCHECK(mojo_remote_.is_bound());

  mojo_remote_.get()->BindAssociatedInterfaces(
      receiver_.BindNewEndpointAndPassRemote(),
      remote_route_provider_.BindNewEndpointAndPassReceiver());
  SetState(LifecycleState::kBound);
}

void AgentSchedulingGroupHost::SetState(
    AgentSchedulingGroupHost::LifecycleState state) {
  static const base::NoDestructor<base::StateTransitions<LifecycleState>>
      transitions(base::StateTransitions<LifecycleState>({
          {LifecycleState::kNewborn, {LifecycleState::kBound}},
          {LifecycleState::kBound,
           {LifecycleState::kRenderProcessExited,
            // Note: If a renderer process is never spawned to claim the
            //       mojo endpoint created at initialization, then we will
            //       skip straight to the destroyed state.
            LifecycleState::kRenderProcessHostDestroyed}},
          {LifecycleState::kRenderProcessExited,
           {LifecycleState::kBound,
            LifecycleState::kRenderProcessHostDestroyed}},
      }));

  DCHECK_STATE_TRANSITION(transitions, state_, state);
  state_ = state;
}

std::ostream& operator<<(std::ostream& os,
                         AgentSchedulingGroupHost::LifecycleState state) {
  switch (state) {
    case AgentSchedulingGroupHost::LifecycleState::kNewborn:
      os << "Newborn";
      break;
    case AgentSchedulingGroupHost::LifecycleState::kBound:
      os << "Bound";
      break;
    case AgentSchedulingGroupHost::LifecycleState::kRenderProcessExited:
      os << "RenderProcessExited";
      break;
    case AgentSchedulingGroupHost::LifecycleState::kRenderProcessHostDestroyed:
      os << "RenderProcessHostDestroyed";
      break;
    default:
      os << "<invalid value: " << static_cast<int>(state) << ">";
  }
  return os;
}

Listener* AgentSchedulingGroupHost::GetListener(int32_t routing_id) {
  DCHECK_NE(routing_id, IPC::mojom::kRoutingIdControl);

  return listener_map_.Lookup(routing_id);
}

}  // namespace content